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  })