github.com/geofffranks/garden-linux@v0.0.0-20160715111146-26c893169cfa/integration/networking/net_out_test.go (about) 1 package networking_test 2 3 import ( 4 "fmt" 5 "io" 6 "net" 7 "net/http" 8 "net/http/httptest" 9 "net/url" 10 "strings" 11 "sync" 12 13 "code.cloudfoundry.org/garden" 14 . "github.com/onsi/ginkgo" 15 . "github.com/onsi/gomega" 16 17 "github.com/onsi/gomega/gbytes" 18 ) 19 20 var _ = Describe("Net Out", func() { 21 var ( 22 container garden.Container 23 otherContainer garden.Container 24 gardenArgs []string 25 26 containerNetwork string 27 denyRange string 28 allowRange string 29 allowHostAccess bool 30 ) 31 32 const containerHandle = "6e4ea858-6b31-4243-5dcc-093cfb83952d" 33 34 BeforeEach(func() { 35 denyRange = "" 36 allowRange = "" 37 allowHostAccess = false 38 gardenArgs = []string{} 39 }) 40 41 JustBeforeEach(func() { 42 gardenArgs = []string{ 43 "-denyNetworks", strings.Join([]string{ 44 denyRange, 45 allowRange, // so that it can be overridden by allowNetworks below 46 }, ","), 47 "-allowNetworks", allowRange, 48 "-iptablesLogMethod", "nflog", // so that we can read logs when running in fly 49 } 50 51 if allowHostAccess { 52 gardenArgs = append(gardenArgs, "-allowHostAccess") 53 } 54 55 client = startGarden(gardenArgs...) 56 57 var err error 58 container, err = client.Create(garden.ContainerSpec{Network: containerNetwork, Privileged: true, Handle: containerHandle}) 59 Expect(err).ToNot(HaveOccurred()) 60 }) 61 62 AfterEach(func() { 63 err := client.Destroy(container.Handle()) 64 Expect(err).ToNot(HaveOccurred()) 65 }) 66 67 runInContainer := func(container garden.Container, script string) (garden.Process, *gbytes.Buffer) { 68 out := gbytes.NewBuffer() 69 process, err := container.Run(garden.ProcessSpec{ 70 User: "alice", 71 Path: "sh", 72 Args: []string{"-c", script}, 73 }, garden.ProcessIO{ 74 Stdout: io.MultiWriter(out, GinkgoWriter), 75 Stderr: GinkgoWriter, 76 }) 77 Expect(err).ToNot(HaveOccurred()) 78 79 return process, out 80 } 81 82 Context("external addresses", func() { 83 var ( 84 ByAllowingTCP, ByRejectingTCP func() 85 ) 86 87 BeforeEach(func() { 88 ByAllowingTCP = func() { 89 By("allowing outbound tcp traffic", func() { 90 Expect(checkInternet(container)).To(Succeed()) 91 }) 92 } 93 94 ByRejectingTCP = func() { 95 By("rejecting outbound tcp traffic", func() { 96 Expect(checkInternet(container)).To(HaveOccurred()) 97 }) 98 } 99 }) 100 101 Context("when the target address is inside DENY_NETWORKS", func() { 102 //The target address is the ip addr of www.example.com in these tests 103 BeforeEach(func() { 104 denyRange = "0.0.0.0/0" 105 allowRange = "9.9.9.9/30" 106 containerNetwork = fmt.Sprintf("10.1%d.0.0/24", GinkgoParallelNode()) 107 }) 108 109 It("disallows TCP connections", func() { 110 ByRejectingTCP() 111 }) 112 113 Context("when a rule that allows all traffic to the target is added", func() { 114 JustBeforeEach(func() { 115 err := container.NetOut(garden.NetOutRule{ 116 Networks: []garden.IPRange{ 117 garden.IPRangeFromIP(externalIP), 118 }, 119 }) 120 Expect(err).ToNot(HaveOccurred()) 121 }) 122 123 It("allows TCP traffic to the target", func() { 124 ByAllowingTCP() 125 }) 126 }) 127 }) 128 129 Context("when the target address is inside ALLOW_NETWORKS", func() { 130 BeforeEach(func() { 131 denyRange = "0.0.0.0/0" 132 allowRange = "0.0.0.0/0" 133 containerNetwork = fmt.Sprintf("10.1%d.0.0/24", GinkgoParallelNode()) 134 }) 135 136 It("allows connections", func() { 137 ByAllowingTCP() 138 }) 139 }) 140 141 Context("when the target address is in neither ALLOW_NETWORKS nor DENY_NETWORKS", func() { 142 BeforeEach(func() { 143 denyRange = "4.4.4.4/30" 144 allowRange = "4.4.4.4/30" 145 containerNetwork = fmt.Sprintf("10.1%d.0.0/24", GinkgoParallelNode()) 146 }) 147 148 It("allows connections", func() { 149 ByAllowingTCP() 150 }) 151 }) 152 153 Context("when there are two containers in the same subnet", func() { 154 BeforeEach(func() { 155 denyRange = "0.0.0.0/0" 156 containerNetwork = fmt.Sprintf("10.1%d.0.0/24", GinkgoParallelNode()) 157 }) 158 159 It("does not allow rules from the second container to affect the first", func() { 160 var err error 161 secondContainer, err := client.Create(garden.ContainerSpec{Network: containerNetwork, Privileged: true}) 162 Expect(err).ToNot(HaveOccurred()) 163 164 ByRejectingTCP() 165 166 Expect(secondContainer.NetOut(garden.NetOutRule{ 167 Networks: []garden.IPRange{ 168 garden.IPRangeFromIP(externalIP), 169 }, 170 })).To(Succeed()) 171 172 By("continuing to reject") 173 ByRejectingTCP() 174 }) 175 }) 176 }) 177 178 Describe("Other Containers", func() { 179 180 const tcpPort = 8080 181 182 targetIP := func(c garden.Container) string { 183 info, err := c.Info() 184 Expect(err).ToNot(HaveOccurred()) 185 return info.ContainerIP 186 } 187 188 ByAllowingTCP := func() { 189 By("allowing tcp traffic to it", func() { 190 Eventually(func() error { 191 return checkConnection(container, targetIP(otherContainer), tcpPort) 192 }).Should(Succeed()) 193 }) 194 } 195 196 Context("containers in the same subnet", func() { 197 BeforeEach(func() { 198 containerNetwork = fmt.Sprintf("10.1%d.0.0/24", GinkgoParallelNode()) 199 allowHostAccess = true 200 }) 201 202 JustBeforeEach(func() { 203 var err error 204 otherContainer, err = client.Create(garden.ContainerSpec{Network: containerNetwork}) 205 Expect(err).ToNot(HaveOccurred()) 206 207 runInContainer(otherContainer, fmt.Sprintf("echo hello | nc -l -p %d", tcpPort)) //tcp 208 }) 209 210 Context("even if the address is in deny networks", func() { 211 BeforeEach(func() { 212 denyRange = "0.0.0.0/8" 213 allowRange = "" 214 }) 215 216 It("can route to each other", func() { 217 ByAllowingTCP() 218 }) 219 }) 220 221 It("can still be contacted while the other one is being destroyed", func() { 222 // this test was introduced to cover a bug where the kernel can change 223 // the bridge mac address when devices are added/removed from it, 224 // causing the networking stack to become confused and drop tcp 225 // connections. It's inherently flakey because the kernel doesn't 226 // always change the mac address, and even if it does tcp is pretty 227 // resilient. Empirically, 10 retries seems to be enough to fairly 228 // consistently fail with the old behaviour. 229 for i := 0; i < 10; i++ { 230 handles := []string{} 231 for j := 0; j < 12; j++ { 232 ctn, err := client.Create(garden.ContainerSpec{Network: containerNetwork}) 233 Expect(err).ToNot(HaveOccurred()) 234 handles = append(handles, ctn.Handle()) 235 } 236 237 respond := make(chan struct{}) 238 server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 239 <-respond 240 fmt.Fprintln(w, "hello") 241 })) 242 243 info, err := container.Info() 244 Expect(err).NotTo(HaveOccurred()) 245 server.Listener, err = net.Listen("tcp", info.ExternalIP+":0") 246 Expect(err).NotTo(HaveOccurred()) 247 248 server.Start() 249 defer server.Close() 250 251 url, err := url.Parse(server.URL) 252 Expect(err).NotTo(HaveOccurred()) 253 port := strings.Split(url.Host, ":")[1] 254 255 stdout := gbytes.NewBuffer() 256 _, err = container.Run(garden.ProcessSpec{ 257 User: "root", 258 Path: "sh", 259 Args: []string{"-c", fmt.Sprintf(`(echo "GET / HTTP/1.1"; echo "Host: foo.com"; echo) | nc %s %s`, info.ExternalIP, port)}, 260 }, garden.ProcessIO{Stdout: stdout, Stderr: stdout}) 261 Expect(err).NotTo(HaveOccurred()) 262 263 var wg sync.WaitGroup 264 for _, handle := range handles { 265 wg.Add(1) 266 go func(handle string) { 267 defer GinkgoRecover() 268 defer wg.Done() 269 Expect(client.Destroy(handle)).To(Succeed()) 270 }(handle) 271 } 272 273 wg.Wait() 274 275 close(respond) 276 Eventually(stdout).Should(gbytes.Say("hello")) 277 } 278 }) 279 }) 280 281 Context("containers in distinct subnets", func() { 282 var otherContainerNetwork string 283 284 JustBeforeEach(func() { 285 otherContainerNetwork = fmt.Sprintf("10.1%d.1.0/24", GinkgoParallelNode()) 286 var err error 287 otherContainer, err = client.Create(garden.ContainerSpec{Network: otherContainerNetwork}) 288 Expect(err).ToNot(HaveOccurred()) 289 290 runInContainer(otherContainer, fmt.Sprintf("echo hello | nc -l -p %d", tcpPort)) //tcp 291 }) 292 293 Context("when deny networks is empty", func() { 294 It("can route to each other", func() { 295 ByAllowingTCP() 296 }) 297 }) 298 }) 299 }) 300 })