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