github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/libnetwork/iptables/iptables_test.go (about) 1 //go:build linux 2 // +build linux 3 4 package iptables 5 6 import ( 7 "net" 8 "os/exec" 9 "strconv" 10 "strings" 11 "testing" 12 13 "golang.org/x/sync/errgroup" 14 ) 15 16 const chainName = "DOCKEREST" 17 18 var natChain *ChainInfo 19 var filterChain *ChainInfo 20 var bridgeName string 21 22 func TestNewChain(t *testing.T) { 23 var err error 24 25 bridgeName = "lo" 26 iptable := GetIptable(IPv4) 27 28 natChain, err = iptable.NewChain(chainName, Nat, false) 29 if err != nil { 30 t.Fatal(err) 31 } 32 err = iptable.ProgramChain(natChain, bridgeName, false, true) 33 if err != nil { 34 t.Fatal(err) 35 } 36 37 filterChain, err = iptable.NewChain(chainName, Filter, false) 38 if err != nil { 39 t.Fatal(err) 40 } 41 err = iptable.ProgramChain(filterChain, bridgeName, false, true) 42 if err != nil { 43 t.Fatal(err) 44 } 45 } 46 47 func TestForward(t *testing.T) { 48 ip := net.ParseIP("192.168.1.1") 49 port := 1234 50 dstAddr := "172.17.0.1" 51 dstPort := 4321 52 proto := "tcp" 53 54 bridgeName := "lo" 55 iptable := GetIptable(IPv4) 56 57 err := natChain.Forward(Insert, ip, port, proto, dstAddr, dstPort, bridgeName) 58 if err != nil { 59 t.Fatal(err) 60 } 61 62 dnatRule := []string{ 63 "-d", ip.String(), 64 "-p", proto, 65 "--dport", strconv.Itoa(port), 66 "-j", "DNAT", 67 "--to-destination", dstAddr + ":" + strconv.Itoa(dstPort), 68 "!", "-i", bridgeName, 69 } 70 71 if !iptable.Exists(natChain.Table, natChain.Name, dnatRule...) { 72 t.Fatal("DNAT rule does not exist") 73 } 74 75 filterRule := []string{ 76 "!", "-i", bridgeName, 77 "-o", bridgeName, 78 "-d", dstAddr, 79 "-p", proto, 80 "--dport", strconv.Itoa(dstPort), 81 "-j", "ACCEPT", 82 } 83 84 if !iptable.Exists(filterChain.Table, filterChain.Name, filterRule...) { 85 t.Fatal("filter rule does not exist") 86 } 87 88 masqRule := []string{ 89 "-d", dstAddr, 90 "-s", dstAddr, 91 "-p", proto, 92 "--dport", strconv.Itoa(dstPort), 93 "-j", "MASQUERADE", 94 } 95 96 if !iptable.Exists(natChain.Table, "POSTROUTING", masqRule...) { 97 t.Fatal("MASQUERADE rule does not exist") 98 } 99 } 100 101 func TestLink(t *testing.T) { 102 var err error 103 104 bridgeName := "lo" 105 iptable := GetIptable(IPv4) 106 ip1 := net.ParseIP("192.168.1.1") 107 ip2 := net.ParseIP("192.168.1.2") 108 port := 1234 109 proto := "tcp" 110 111 err = filterChain.Link(Append, ip1, ip2, port, proto, bridgeName) 112 if err != nil { 113 t.Fatal(err) 114 } 115 116 rule1 := []string{ 117 "-i", bridgeName, 118 "-o", bridgeName, 119 "-p", proto, 120 "-s", ip1.String(), 121 "-d", ip2.String(), 122 "--dport", strconv.Itoa(port), 123 "-j", "ACCEPT"} 124 125 if !iptable.Exists(filterChain.Table, filterChain.Name, rule1...) { 126 t.Fatal("rule1 does not exist") 127 } 128 129 rule2 := []string{ 130 "-i", bridgeName, 131 "-o", bridgeName, 132 "-p", proto, 133 "-s", ip2.String(), 134 "-d", ip1.String(), 135 "--sport", strconv.Itoa(port), 136 "-j", "ACCEPT"} 137 138 if !iptable.Exists(filterChain.Table, filterChain.Name, rule2...) { 139 t.Fatal("rule2 does not exist") 140 } 141 } 142 143 func TestPrerouting(t *testing.T) { 144 args := []string{ 145 "-i", "lo", 146 "-d", "192.168.1.1"} 147 iptable := GetIptable(IPv4) 148 149 err := natChain.Prerouting(Insert, args...) 150 if err != nil { 151 t.Fatal(err) 152 } 153 154 if !iptable.Exists(natChain.Table, "PREROUTING", args...) { 155 t.Fatal("rule does not exist") 156 } 157 158 delRule := append([]string{"-D", "PREROUTING", "-t", string(Nat)}, args...) 159 if _, err = iptable.Raw(delRule...); err != nil { 160 t.Fatal(err) 161 } 162 } 163 164 func TestOutput(t *testing.T) { 165 args := []string{ 166 "-o", "lo", 167 "-d", "192.168.1.1"} 168 iptable := GetIptable(IPv4) 169 170 err := natChain.Output(Insert, args...) 171 if err != nil { 172 t.Fatal(err) 173 } 174 175 if !iptable.Exists(natChain.Table, "OUTPUT", args...) { 176 t.Fatal("rule does not exist") 177 } 178 179 delRule := append([]string{"-D", "OUTPUT", "-t", 180 string(natChain.Table)}, args...) 181 if _, err = iptable.Raw(delRule...); err != nil { 182 t.Fatal(err) 183 } 184 } 185 186 func TestConcurrencyWithWait(t *testing.T) { 187 RunConcurrencyTest(t, true) 188 } 189 190 func TestConcurrencyNoWait(t *testing.T) { 191 RunConcurrencyTest(t, false) 192 } 193 194 // Runs 10 concurrent rule additions. This will fail if iptables 195 // is actually invoked simultaneously without --wait. 196 // Note that if iptables does not support the xtable lock on this 197 // system, then allowXlock has no effect -- it will always be off. 198 func RunConcurrencyTest(t *testing.T, allowXlock bool) { 199 if !allowXlock && supportsXlock { 200 supportsXlock = false 201 defer func() { supportsXlock = true }() 202 } 203 204 ip := net.ParseIP("192.168.1.1") 205 port := 1234 206 dstAddr := "172.17.0.1" 207 dstPort := 4321 208 proto := "tcp" 209 210 group := new(errgroup.Group) 211 for i := 0; i < 10; i++ { 212 group.Go(func() error { 213 return natChain.Forward(Append, ip, port, proto, dstAddr, dstPort, "lo") 214 }) 215 } 216 if err := group.Wait(); err != nil { 217 t.Fatal(err) 218 } 219 } 220 221 func TestCleanup(t *testing.T) { 222 var err error 223 var rules []byte 224 225 // Cleanup filter/FORWARD first otherwise output of iptables-save is dirty 226 link := []string{"-t", string(filterChain.Table), 227 string(Delete), "FORWARD", 228 "-o", bridgeName, 229 "-j", filterChain.Name} 230 iptable := GetIptable(IPv4) 231 232 if _, err = iptable.Raw(link...); err != nil { 233 t.Fatal(err) 234 } 235 filterChain.Remove() 236 237 err = iptable.RemoveExistingChain(chainName, Nat) 238 if err != nil { 239 t.Fatal(err) 240 } 241 242 rules, err = exec.Command("iptables-save").Output() 243 if err != nil { 244 t.Fatal(err) 245 } 246 if strings.Contains(string(rules), chainName) { 247 t.Fatalf("Removing chain failed. %s found in iptables-save", chainName) 248 } 249 } 250 251 func TestExistsRaw(t *testing.T) { 252 testChain1 := "ABCD" 253 testChain2 := "EFGH" 254 255 iptable := GetIptable(IPv4) 256 257 _, err := iptable.NewChain(testChain1, Filter, false) 258 if err != nil { 259 t.Fatal(err) 260 } 261 defer func() { 262 iptable.RemoveExistingChain(testChain1, Filter) 263 }() 264 265 _, err = iptable.NewChain(testChain2, Filter, false) 266 if err != nil { 267 t.Fatal(err) 268 } 269 defer func() { 270 iptable.RemoveExistingChain(testChain2, Filter) 271 }() 272 273 // Test detection over full and truncated rule string 274 input := []struct{ rule []string }{ 275 {[]string{"-s", "172.8.9.9/32", "-j", "ACCEPT"}}, 276 {[]string{"-d", "172.8.9.0/24", "-j", "DROP"}}, 277 {[]string{"-s", "172.0.3.0/24", "-d", "172.17.0.0/24", "-p", "tcp", "-m", "tcp", "--dport", "80", "-j", testChain2}}, 278 {[]string{"-j", "RETURN"}}, 279 } 280 281 for i, r := range input { 282 ruleAdd := append([]string{"-t", string(Filter), "-A", testChain1}, r.rule...) 283 err = iptable.RawCombinedOutput(ruleAdd...) 284 if err != nil { 285 t.Fatalf("i=%d, err: %v", i, err) 286 } 287 if !iptable.existsRaw(Filter, testChain1, r.rule...) { 288 t.Fatalf("Failed to detect rule. i=%d", i) 289 } 290 // Truncate the rule 291 trg := r.rule[len(r.rule)-1] 292 trg = trg[:len(trg)-2] 293 r.rule[len(r.rule)-1] = trg 294 if iptable.existsRaw(Filter, testChain1, r.rule...) { 295 t.Fatalf("Invalid detection. i=%d", i) 296 } 297 } 298 } 299 300 func TestGetVersion(t *testing.T) { 301 mj, mn, mc := parseVersionNumbers("iptables v1.4.19.1-alpha") 302 if mj != 1 || mn != 4 || mc != 19 { 303 t.Fatal("Failed to parse version numbers") 304 } 305 } 306 307 func TestSupportsCOption(t *testing.T) { 308 input := []struct { 309 mj int 310 mn int 311 mc int 312 ok bool 313 }{ 314 {1, 4, 11, true}, 315 {1, 4, 12, true}, 316 {1, 5, 0, true}, 317 {0, 4, 11, false}, 318 {0, 5, 12, false}, 319 {1, 3, 12, false}, 320 {1, 4, 10, false}, 321 } 322 for ind, inp := range input { 323 if inp.ok != supportsCOption(inp.mj, inp.mn, inp.mc) { 324 t.Fatalf("Incorrect check: %d", ind) 325 } 326 } 327 }