github.com/cloudfoundry-attic/garden-linux@v0.333.2-candidate/network/iptables/iptables_test.go (about)

     1  package iptables_test
     2  
     3  import (
     4  	"errors"
     5  	"net"
     6  	"os/exec"
     7  
     8  	"github.com/cloudfoundry-incubator/garden"
     9  	. "github.com/cloudfoundry-incubator/garden-linux/network/iptables"
    10  	"github.com/cloudfoundry/gunk/command_runner/fake_command_runner"
    11  	. "github.com/cloudfoundry/gunk/command_runner/fake_command_runner/matchers"
    12  	"github.com/pivotal-golang/lager/lagertest"
    13  
    14  	. "github.com/onsi/ginkgo"
    15  	. "github.com/onsi/gomega"
    16  )
    17  
    18  var _ = Describe("Iptables", func() {
    19  	Describe("Chain", func() {
    20  		var fakeRunner *fake_command_runner.FakeCommandRunner
    21  		var subject Chain
    22  		var useKernelLogging bool
    23  
    24  		JustBeforeEach(func() {
    25  			fakeRunner = fake_command_runner.New()
    26  			subject = NewLoggingChain("foo-bar-baz", useKernelLogging, fakeRunner, lagertest.NewTestLogger("test"))
    27  		})
    28  
    29  		Describe("Setup", func() {
    30  			Context("when kernel logging is not enabled", func() {
    31  				It("creates the log chain using iptables", func() {
    32  					Expect(subject.Setup("logPrefix")).To(Succeed())
    33  					Expect(fakeRunner).To(HaveExecutedSerially(
    34  						fake_command_runner.CommandSpec{
    35  							Path: "/sbin/iptables",
    36  							Args: []string{"-w", "-F", "foo-bar-baz-log"},
    37  						},
    38  						fake_command_runner.CommandSpec{
    39  							Path: "/sbin/iptables",
    40  							Args: []string{"-w", "-X", "foo-bar-baz-log"},
    41  						},
    42  						fake_command_runner.CommandSpec{
    43  							Path: "/sbin/iptables",
    44  							Args: []string{"-w", "-N", "foo-bar-baz-log"},
    45  						},
    46  						fake_command_runner.CommandSpec{
    47  							Path: "/sbin/iptables",
    48  							Args: []string{"-w", "-A", "foo-bar-baz-log", "-m", "conntrack", "--ctstate", "NEW,UNTRACKED,INVALID", "--protocol", "tcp", "--jump", "NFLOG", "--nflog-prefix", "logPrefix", "--nflog-group", "1"},
    49  						},
    50  						fake_command_runner.CommandSpec{
    51  							Path: "/sbin/iptables",
    52  							Args: []string{"-w", "-A", "foo-bar-baz-log", "--jump", "RETURN"},
    53  						}))
    54  				})
    55  			})
    56  
    57  			Context("when kernel logging is enabled", func() {
    58  				BeforeEach(func() {
    59  					useKernelLogging = true
    60  				})
    61  
    62  				It("creates the log chain using iptables", func() {
    63  					Expect(subject.Setup("logPrefix")).To(Succeed())
    64  					Expect(fakeRunner).To(HaveExecutedSerially(
    65  						fake_command_runner.CommandSpec{
    66  							Path: "/sbin/iptables",
    67  							Args: []string{"-w", "-F", "foo-bar-baz-log"},
    68  						},
    69  						fake_command_runner.CommandSpec{
    70  							Path: "/sbin/iptables",
    71  							Args: []string{"-w", "-X", "foo-bar-baz-log"},
    72  						},
    73  						fake_command_runner.CommandSpec{
    74  							Path: "/sbin/iptables",
    75  							Args: []string{"-w", "-N", "foo-bar-baz-log"},
    76  						},
    77  						fake_command_runner.CommandSpec{
    78  							Path: "/sbin/iptables",
    79  							Args: []string{"-w", "-A", "foo-bar-baz-log", "-m", "conntrack", "--ctstate", "NEW,UNTRACKED,INVALID", "--protocol", "tcp",
    80  								"--jump", "LOG", "--log-prefix", "logPrefix"},
    81  						},
    82  						fake_command_runner.CommandSpec{
    83  							Path: "/sbin/iptables",
    84  							Args: []string{"-w", "-A", "foo-bar-baz-log", "--jump", "RETURN"},
    85  						}))
    86  				})
    87  			})
    88  
    89  			It("ignores failures to flush", func() {
    90  				someError := errors.New("y")
    91  				fakeRunner.WhenRunning(
    92  					fake_command_runner.CommandSpec{
    93  						Path: "/sbin/iptables",
    94  						Args: []string{"-w", "-F", "foo-bar-baz-log"},
    95  					},
    96  					func(cmd *exec.Cmd) error {
    97  						return someError
    98  					})
    99  
   100  				Expect(subject.Setup("logPrefix")).To(Succeed())
   101  			})
   102  
   103  			It("ignores failures to delete", func() {
   104  				someError := errors.New("y")
   105  				fakeRunner.WhenRunning(
   106  					fake_command_runner.CommandSpec{
   107  						Path: "/sbin/iptables",
   108  						Args: []string{"-w", "-X", "foo-bar-baz-log"},
   109  					},
   110  					func(cmd *exec.Cmd) error {
   111  						return someError
   112  					})
   113  
   114  				Expect(subject.Setup("logPrefix")).To(Succeed())
   115  			})
   116  
   117  			It("returns any error returned when the table is created", func() {
   118  				someError := errors.New("y")
   119  				fakeRunner.WhenRunning(
   120  					fake_command_runner.CommandSpec{
   121  						Path: "/sbin/iptables",
   122  						Args: []string{"-w", "-N", "foo-bar-baz-log"},
   123  					},
   124  					func(cmd *exec.Cmd) error {
   125  						return someError
   126  					})
   127  
   128  				Expect(subject.Setup("logPrefix")).To(MatchError("iptables: log chain setup: y"))
   129  			})
   130  
   131  			It("returns any error returned when the logging rule is added", func() {
   132  				someError := errors.New("y")
   133  				fakeRunner.WhenRunning(
   134  					fake_command_runner.CommandSpec{
   135  						Path: "/sbin/iptables",
   136  						Args: []string{"-w", "-A", "foo-bar-baz-log", "-m", "conntrack", "--ctstate", "NEW,UNTRACKED,INVALID", "--protocol", "tcp", "--jump", "LOG", "--log-prefix", "logPrefix"},
   137  					},
   138  					func(cmd *exec.Cmd) error {
   139  						return someError
   140  					})
   141  
   142  				Expect(subject.Setup("logPrefix")).To(MatchError("iptables: log chain setup: y"))
   143  			})
   144  
   145  			It("returns any error returned when the RETURN rule is added", func() {
   146  				someError := errors.New("y")
   147  				fakeRunner.WhenRunning(
   148  					fake_command_runner.CommandSpec{
   149  						Path: "/sbin/iptables",
   150  						Args: []string{"-w", "-A", "foo-bar-baz-log", "--jump", "RETURN"},
   151  					},
   152  					func(cmd *exec.Cmd) error {
   153  						return someError
   154  					})
   155  
   156  				Expect(subject.Setup("logPrefix")).To(MatchError("iptables: log chain setup: y"))
   157  			})
   158  		})
   159  
   160  		Describe("TearDown", func() {
   161  			It("should flush and delete the underlying iptables log chain", func() {
   162  				Expect(subject.TearDown()).To(Succeed())
   163  				Expect(fakeRunner).To(HaveExecutedSerially(
   164  					fake_command_runner.CommandSpec{
   165  						Path: "/sbin/iptables",
   166  						Args: []string{"-w", "-F", "foo-bar-baz-log"},
   167  					},
   168  					fake_command_runner.CommandSpec{
   169  						Path: "/sbin/iptables",
   170  						Args: []string{"-w", "-X", "foo-bar-baz-log"},
   171  					}))
   172  			})
   173  
   174  			It("ignores failures to flush", func() {
   175  				someError := errors.New("y")
   176  				fakeRunner.WhenRunning(
   177  					fake_command_runner.CommandSpec{
   178  						Path: "/sbin/iptables",
   179  						Args: []string{"-w", "-F", "foo-bar-baz-log"},
   180  					},
   181  					func(cmd *exec.Cmd) error {
   182  						return someError
   183  					})
   184  
   185  				Expect(subject.TearDown()).To(Succeed())
   186  			})
   187  
   188  			It("ignores failures to delete", func() {
   189  				someError := errors.New("y")
   190  				fakeRunner.WhenRunning(
   191  					fake_command_runner.CommandSpec{
   192  						Path: "/sbin/iptables",
   193  						Args: []string{"-w", "-X", "foo-bar-baz-log"},
   194  					},
   195  					func(cmd *exec.Cmd) error {
   196  						return someError
   197  					})
   198  
   199  				Expect(subject.TearDown()).To(Succeed())
   200  			})
   201  
   202  		})
   203  
   204  		Describe("AppendRule", func() {
   205  			It("runs iptables to create the rule with the correct parameters", func() {
   206  				subject.AppendRule("", "2.0.0.0/11", Return)
   207  
   208  				Expect(fakeRunner).To(HaveExecutedSerially(fake_command_runner.CommandSpec{
   209  					Path: "/sbin/iptables",
   210  					Args: []string{"-w", "-A", "foo-bar-baz", "--destination", "2.0.0.0/11", "--jump", "RETURN"},
   211  				}))
   212  			})
   213  		})
   214  
   215  		Describe("AppendNatRule", func() {
   216  			Context("creating a rule", func() {
   217  				Context("when all parameters are specified", func() {
   218  					It("runs iptables to create the rule with the correct parameters", func() {
   219  						subject.AppendNatRule("1.3.5.0/28", "2.0.0.0/11", Return, net.ParseIP("1.2.3.4"))
   220  
   221  						Expect(fakeRunner).To(HaveExecutedSerially(fake_command_runner.CommandSpec{
   222  							Path: "/sbin/iptables",
   223  							Args: []string{"-w", "-t", "nat", "-A", "foo-bar-baz", "--source", "1.3.5.0/28", "--destination", "2.0.0.0/11", "--jump", "RETURN", "--to", "1.2.3.4"},
   224  						}))
   225  					})
   226  				})
   227  
   228  				Context("when Source is not specified", func() {
   229  					It("does not include the --source parameter in the command", func() {
   230  						subject.AppendNatRule("", "2.0.0.0/11", Return, net.ParseIP("1.2.3.4"))
   231  
   232  						Expect(fakeRunner).To(HaveExecutedSerially(fake_command_runner.CommandSpec{
   233  							Path: "/sbin/iptables",
   234  							Args: []string{"-w", "-t", "nat", "-A", "foo-bar-baz", "--destination", "2.0.0.0/11", "--jump", "RETURN", "--to", "1.2.3.4"},
   235  						}))
   236  					})
   237  				})
   238  
   239  				Context("when Destination is not specified", func() {
   240  					It("does not include the --destination parameter in the command", func() {
   241  						subject.AppendNatRule("1.3.5.0/28", "", Return, net.ParseIP("1.2.3.4"))
   242  
   243  						Expect(fakeRunner).To(HaveExecutedSerially(fake_command_runner.CommandSpec{
   244  							Path: "/sbin/iptables",
   245  							Args: []string{"-w", "-t", "nat", "-A", "foo-bar-baz", "--source", "1.3.5.0/28", "--jump", "RETURN", "--to", "1.2.3.4"},
   246  						}))
   247  					})
   248  				})
   249  
   250  				Context("when To is not specified", func() {
   251  					It("does not include the --to parameter in the command", func() {
   252  						subject.AppendNatRule("1.3.5.0/28", "2.0.0.0/11", Return, nil)
   253  
   254  						Expect(fakeRunner).To(HaveExecutedSerially(fake_command_runner.CommandSpec{
   255  							Path: "/sbin/iptables",
   256  							Args: []string{"-w", "-t", "nat", "-A", "foo-bar-baz", "--source", "1.3.5.0/28", "--destination", "2.0.0.0/11", "--jump", "RETURN"},
   257  						}))
   258  					})
   259  				})
   260  
   261  				Context("when the command returns an error", func() {
   262  					It("returns an error", func() {
   263  						someError := errors.New("badly laid iptable")
   264  						fakeRunner.WhenRunning(
   265  							fake_command_runner.CommandSpec{Path: "/sbin/iptables"},
   266  							func(cmd *exec.Cmd) error {
   267  								return someError
   268  							},
   269  						)
   270  
   271  						Expect(subject.AppendRule("1.2.3.4/5", "", "")).ToNot(Succeed())
   272  					})
   273  				})
   274  			})
   275  
   276  			Describe("DeleteRule", func() {
   277  				It("runs iptables to delete the rule with the correct parameters", func() {
   278  					subject.DeleteRule("", "2.0.0.0/11", Return)
   279  
   280  					Expect(fakeRunner).To(HaveExecutedSerially(fake_command_runner.CommandSpec{
   281  						Path: "/sbin/iptables",
   282  						Args: []string{"-w", "-D", "foo-bar-baz", "--destination", "2.0.0.0/11", "--jump", "RETURN"},
   283  					}))
   284  				})
   285  			})
   286  
   287  			Context("DeleteNatRule", func() {
   288  				Context("when all parameters are specified", func() {
   289  					It("runs iptables to delete the rule with the correct parameters", func() {
   290  						subject.DeleteNatRule("1.3.5.0/28", "2.0.0.0/11", Return, net.ParseIP("1.2.3.4"))
   291  
   292  						Expect(fakeRunner).To(HaveExecutedSerially(fake_command_runner.CommandSpec{
   293  							Path: "/sbin/iptables",
   294  							Args: []string{"-w", "-t", "nat", "-D", "foo-bar-baz", "--source", "1.3.5.0/28", "--destination", "2.0.0.0/11", "--jump", "RETURN", "--to", "1.2.3.4"},
   295  						}))
   296  					})
   297  				})
   298  
   299  				Context("when Source is not specified", func() {
   300  					It("does not include the --source parameter in the command", func() {
   301  						subject.DeleteNatRule("", "2.0.0.0/11", Return, net.ParseIP("1.2.3.4"))
   302  
   303  						Expect(fakeRunner).To(HaveExecutedSerially(fake_command_runner.CommandSpec{
   304  							Path: "/sbin/iptables",
   305  							Args: []string{"-w", "-t", "nat", "-D", "foo-bar-baz", "--destination", "2.0.0.0/11", "--jump", "RETURN", "--to", "1.2.3.4"},
   306  						}))
   307  					})
   308  				})
   309  
   310  				Context("when Destination is not specified", func() {
   311  					It("does not include the --destination parameter in the command", func() {
   312  						subject.DeleteNatRule("1.3.5.0/28", "", Return, net.ParseIP("1.2.3.4"))
   313  
   314  						Expect(fakeRunner).To(HaveExecutedSerially(fake_command_runner.CommandSpec{
   315  							Path: "/sbin/iptables",
   316  							Args: []string{"-w", "-t", "nat", "-D", "foo-bar-baz", "--source", "1.3.5.0/28", "--jump", "RETURN", "--to", "1.2.3.4"},
   317  						}))
   318  					})
   319  				})
   320  
   321  				Context("when To is not specified", func() {
   322  					It("does not include the --to parameter in the command", func() {
   323  						subject.DeleteNatRule("1.3.5.0/28", "2.0.0.0/11", Return, nil)
   324  
   325  						Expect(fakeRunner).To(HaveExecutedSerially(fake_command_runner.CommandSpec{
   326  							Path: "/sbin/iptables",
   327  							Args: []string{"-w", "-t", "nat", "-D", "foo-bar-baz", "--source", "1.3.5.0/28", "--destination", "2.0.0.0/11", "--jump", "RETURN"},
   328  						}))
   329  					})
   330  				})
   331  
   332  				Context("when the command returns an error", func() {
   333  					It("returns an error", func() {
   334  						someError := errors.New("badly laid iptable")
   335  						fakeRunner.WhenRunning(
   336  							fake_command_runner.CommandSpec{Path: "/sbin/iptables"},
   337  							func(cmd *exec.Cmd) error {
   338  								return someError
   339  							},
   340  						)
   341  
   342  						Expect(subject.DeleteNatRule("1.3.4.5/6", "", "", nil)).ToNot(Succeed())
   343  					})
   344  				})
   345  			})
   346  
   347  			Describe("PrependFilterRule", func() {
   348  				Context("when all parameters are defaulted", func() {
   349  					It("runs iptables with appropriate parameters", func() {
   350  						Expect(subject.PrependFilterRule(garden.NetOutRule{})).To(Succeed())
   351  						Expect(fakeRunner).To(HaveExecutedSerially(fake_command_runner.CommandSpec{
   352  							Path: "/sbin/iptables",
   353  							Args: []string{"-w", "-I", "foo-bar-baz", "1", "--protocol", "all", "--jump", "RETURN"},
   354  						}))
   355  					})
   356  				})
   357  
   358  				Describe("Network", func() {
   359  					Context("when an empty IPRange is specified", func() {
   360  						It("does not limit the range", func() {
   361  							Expect(subject.PrependFilterRule(garden.NetOutRule{
   362  								Networks: []garden.IPRange{
   363  									garden.IPRange{},
   364  								},
   365  							})).To(Succeed())
   366  
   367  							Expect(fakeRunner).To(HaveExecutedSerially(fake_command_runner.CommandSpec{
   368  								Path: "/sbin/iptables",
   369  								Args: []string{"-w", "-I", "foo-bar-baz", "1", "--protocol", "all", "--jump", "RETURN"},
   370  							}))
   371  						})
   372  					})
   373  
   374  					Context("when a single destination IP is specified", func() {
   375  						It("opens only that IP", func() {
   376  							Expect(subject.PrependFilterRule(garden.NetOutRule{
   377  								Networks: []garden.IPRange{
   378  									{
   379  										Start: net.ParseIP("1.2.3.4"),
   380  									},
   381  								},
   382  							})).To(Succeed())
   383  
   384  							Expect(fakeRunner).To(HaveExecutedSerially(fake_command_runner.CommandSpec{
   385  								Path: "/sbin/iptables",
   386  								Args: []string{"-w", "-I", "foo-bar-baz", "1", "--protocol", "all", "--destination", "1.2.3.4", "--jump", "RETURN"},
   387  							}))
   388  						})
   389  					})
   390  
   391  					Context("when a multiple destination networks are specified", func() {
   392  						It("opens only that IP", func() {
   393  							Expect(subject.PrependFilterRule(garden.NetOutRule{
   394  								Networks: []garden.IPRange{
   395  									{
   396  										Start: net.ParseIP("1.2.3.4"),
   397  									},
   398  									{
   399  										Start: net.ParseIP("2.2.3.4"),
   400  										End:   net.ParseIP("2.2.3.9"),
   401  									},
   402  								},
   403  							})).To(Succeed())
   404  
   405  							Expect(fakeRunner.ExecutedCommands()).To(HaveLen(2))
   406  							Expect(fakeRunner).To(HaveExecutedSerially(
   407  								fake_command_runner.CommandSpec{
   408  									Path: "/sbin/iptables",
   409  									Args: []string{"-w", "-I", "foo-bar-baz", "1", "--protocol", "all", "--destination", "1.2.3.4", "--jump", "RETURN"},
   410  								},
   411  								fake_command_runner.CommandSpec{
   412  									Path: "/sbin/iptables",
   413  									Args: []string{"-w", "-I", "foo-bar-baz", "1", "--protocol", "all", "-m", "iprange", "--dst-range", "2.2.3.4-2.2.3.9", "--jump", "RETURN"},
   414  								},
   415  							))
   416  						})
   417  					})
   418  
   419  					Context("when a EndIP is specified without a StartIP", func() {
   420  						It("opens only that IP", func() {
   421  							Expect(subject.PrependFilterRule(garden.NetOutRule{
   422  								Networks: []garden.IPRange{
   423  									{
   424  										End: net.ParseIP("1.2.3.4"),
   425  									},
   426  								},
   427  							})).To(Succeed())
   428  
   429  							Expect(fakeRunner).To(HaveExecutedSerially(fake_command_runner.CommandSpec{
   430  								Path: "/sbin/iptables",
   431  								Args: []string{"-w", "-I", "foo-bar-baz", "1", "--protocol", "all", "--destination", "1.2.3.4", "--jump", "RETURN"},
   432  							}))
   433  						})
   434  					})
   435  
   436  					Context("when a range of IPs is specified", func() {
   437  						It("opens only the range", func() {
   438  							Expect(subject.PrependFilterRule(garden.NetOutRule{
   439  								Networks: []garden.IPRange{
   440  									{
   441  										net.ParseIP("1.2.3.4"), net.ParseIP("2.3.4.5"),
   442  									},
   443  								},
   444  							})).To(Succeed())
   445  
   446  							Expect(fakeRunner).To(HaveExecutedSerially(fake_command_runner.CommandSpec{
   447  								Path: "/sbin/iptables",
   448  								Args: []string{"-w", "-I", "foo-bar-baz", "1", "--protocol", "all", "-m", "iprange", "--dst-range", "1.2.3.4-2.3.4.5", "--jump", "RETURN"},
   449  							}))
   450  						})
   451  					})
   452  				})
   453  
   454  				Describe("Ports", func() {
   455  					Context("when a single port is specified", func() {
   456  						It("opens only that port", func() {
   457  							Expect(subject.PrependFilterRule(garden.NetOutRule{
   458  								Protocol: garden.ProtocolTCP,
   459  								Ports: []garden.PortRange{
   460  									garden.PortRangeFromPort(22),
   461  								},
   462  							})).To(Succeed())
   463  
   464  							Expect(fakeRunner).To(HaveExecutedSerially(fake_command_runner.CommandSpec{
   465  								Path: "/sbin/iptables",
   466  								Args: []string{"-w", "-I", "foo-bar-baz", "1", "--protocol", "tcp", "--destination-port", "22", "--jump", "RETURN"},
   467  							}))
   468  						})
   469  					})
   470  
   471  					Context("when a port range is specified", func() {
   472  						It("opens that port range", func() {
   473  							Expect(subject.PrependFilterRule(garden.NetOutRule{
   474  								Protocol: garden.ProtocolTCP,
   475  								Ports: []garden.PortRange{
   476  									{12, 24},
   477  								},
   478  							})).To(Succeed())
   479  
   480  							Expect(fakeRunner).To(HaveExecutedSerially(fake_command_runner.CommandSpec{
   481  								Path: "/sbin/iptables",
   482  								Args: []string{"-w", "-I", "foo-bar-baz", "1", "--protocol", "tcp", "--destination-port", "12:24", "--jump", "RETURN"},
   483  							}))
   484  						})
   485  					})
   486  
   487  					Context("when multiple port ranges are specified", func() {
   488  						It("opens those port ranges", func() {
   489  							Expect(subject.PrependFilterRule(garden.NetOutRule{
   490  								Protocol: garden.ProtocolTCP,
   491  								Ports: []garden.PortRange{
   492  									{12, 24},
   493  									{64, 942},
   494  								},
   495  							})).To(Succeed())
   496  
   497  							Expect(fakeRunner).To(HaveExecutedSerially(
   498  								fake_command_runner.CommandSpec{
   499  									Path: "/sbin/iptables",
   500  									Args: []string{"-w", "-I", "foo-bar-baz", "1", "--protocol", "tcp", "--destination-port", "12:24", "--jump", "RETURN"},
   501  								},
   502  								fake_command_runner.CommandSpec{
   503  									Path: "/sbin/iptables",
   504  									Args: []string{"-w", "-I", "foo-bar-baz", "1", "--protocol", "tcp", "--destination-port", "64:942", "--jump", "RETURN"},
   505  								},
   506  							))
   507  						})
   508  					})
   509  				})
   510  
   511  				Describe("Protocol", func() {
   512  					Context("when tcp protocol is specified", func() {
   513  						It("passes tcp protocol to iptables", func() {
   514  							Expect(subject.PrependFilterRule(garden.NetOutRule{
   515  								Protocol: garden.ProtocolTCP,
   516  							})).To(Succeed())
   517  
   518  							Expect(fakeRunner).To(HaveExecutedSerially(fake_command_runner.CommandSpec{
   519  								Path: "/sbin/iptables",
   520  								Args: []string{"-w", "-I", "foo-bar-baz", "1", "--protocol", "tcp", "--jump", "RETURN"},
   521  							}))
   522  						})
   523  					})
   524  
   525  					Context("when udp protocol is specified", func() {
   526  						It("passes udp protocol to iptables", func() {
   527  							Expect(subject.PrependFilterRule(garden.NetOutRule{
   528  								Protocol: garden.ProtocolUDP,
   529  							})).To(Succeed())
   530  
   531  							Expect(fakeRunner).To(HaveExecutedSerially(fake_command_runner.CommandSpec{
   532  								Path: "/sbin/iptables",
   533  								Args: []string{"-w", "-I", "foo-bar-baz", "1", "--protocol", "udp", "--jump", "RETURN"},
   534  							}))
   535  						})
   536  					})
   537  
   538  					Context("when icmp protocol is specified", func() {
   539  						It("passes icmp protocol to iptables", func() {
   540  							Expect(subject.PrependFilterRule(garden.NetOutRule{
   541  								Protocol: garden.ProtocolICMP,
   542  							})).To(Succeed())
   543  
   544  							Expect(fakeRunner).To(HaveExecutedSerially(fake_command_runner.CommandSpec{
   545  								Path: "/sbin/iptables",
   546  								Args: []string{"-w", "-I", "foo-bar-baz", "1", "--protocol", "icmp", "--jump", "RETURN"},
   547  							}))
   548  						})
   549  
   550  						Context("when icmp type is specified", func() {
   551  							It("passes icmp protcol type to iptables", func() {
   552  								Expect(subject.PrependFilterRule(garden.NetOutRule{
   553  									Protocol: garden.ProtocolICMP,
   554  									ICMPs: &garden.ICMPControl{
   555  										Type: 99,
   556  									},
   557  								})).To(Succeed())
   558  
   559  								Expect(fakeRunner).To(HaveExecutedSerially(fake_command_runner.CommandSpec{
   560  									Path: "/sbin/iptables",
   561  									Args: []string{"-w", "-I", "foo-bar-baz", "1", "--protocol", "icmp", "--icmp-type", "99", "--jump", "RETURN"},
   562  								}))
   563  							})
   564  						})
   565  
   566  						Context("when icmp type and code are specified", func() {
   567  							It("passes icmp protcol type and code to iptables", func() {
   568  								Expect(subject.PrependFilterRule(garden.NetOutRule{
   569  									Protocol: garden.ProtocolICMP,
   570  									ICMPs: &garden.ICMPControl{
   571  										Type: 99,
   572  										Code: garden.ICMPControlCode(11),
   573  									},
   574  								})).To(Succeed())
   575  
   576  								Expect(fakeRunner).To(HaveExecutedSerially(fake_command_runner.CommandSpec{
   577  									Path: "/sbin/iptables",
   578  									Args: []string{"-w", "-I", "foo-bar-baz", "1", "--protocol", "icmp", "--icmp-type", "99/11", "--jump", "RETURN"},
   579  								}))
   580  							})
   581  						})
   582  					})
   583  				})
   584  
   585  				Describe("Log", func() {
   586  					It("redirects via the log chain if log is specified", func() {
   587  						Expect(subject.PrependFilterRule(garden.NetOutRule{
   588  							Log: true,
   589  						})).To(Succeed())
   590  
   591  						Expect(fakeRunner).To(HaveExecutedSerially(fake_command_runner.CommandSpec{
   592  							Path: "/sbin/iptables",
   593  							Args: []string{"-w", "-I", "foo-bar-baz", "1", "--protocol", "all", "--goto", "foo-bar-baz-log"},
   594  						}))
   595  					})
   596  				})
   597  
   598  				Context("when multiple port ranges and multiple networks are specified", func() {
   599  					It("opens the permutations of those port ranges and networks", func() {
   600  						Expect(subject.PrependFilterRule(garden.NetOutRule{
   601  							Protocol: garden.ProtocolTCP,
   602  							Networks: []garden.IPRange{
   603  								{
   604  									Start: net.ParseIP("1.2.3.4"),
   605  								},
   606  								{
   607  									Start: net.ParseIP("2.2.3.4"),
   608  									End:   net.ParseIP("2.2.3.9"),
   609  								},
   610  							},
   611  							Ports: []garden.PortRange{
   612  								{12, 24},
   613  								{64, 942},
   614  							},
   615  						})).To(Succeed())
   616  
   617  						Expect(fakeRunner.ExecutedCommands()).To(HaveLen(4))
   618  						Expect(fakeRunner).To(HaveExecutedSerially(
   619  							fake_command_runner.CommandSpec{
   620  								Path: "/sbin/iptables",
   621  								Args: []string{"-w", "-I", "foo-bar-baz", "1", "--protocol", "tcp", "--destination", "1.2.3.4", "--destination-port", "12:24", "--jump", "RETURN"},
   622  							},
   623  							fake_command_runner.CommandSpec{
   624  								Path: "/sbin/iptables",
   625  								Args: []string{"-w", "-I", "foo-bar-baz", "1", "--protocol", "tcp", "--destination", "1.2.3.4", "--destination-port", "64:942", "--jump", "RETURN"},
   626  							},
   627  							fake_command_runner.CommandSpec{
   628  								Path: "/sbin/iptables",
   629  								Args: []string{"-w", "-I", "foo-bar-baz", "1", "--protocol", "tcp", "-m", "iprange", "--dst-range", "2.2.3.4-2.2.3.9", "--destination-port", "12:24", "--jump", "RETURN"},
   630  							},
   631  							fake_command_runner.CommandSpec{
   632  								Path: "/sbin/iptables",
   633  								Args: []string{"-w", "-I", "foo-bar-baz", "1", "--protocol", "tcp", "-m", "iprange", "--dst-range", "2.2.3.4-2.2.3.9", "--destination-port", "64:942", "--jump", "RETURN"},
   634  							},
   635  						))
   636  					})
   637  				})
   638  
   639  				Context("when a portrange is specified for ProtocolALL", func() {
   640  					It("returns a nice error message", func() {
   641  						Expect(subject.PrependFilterRule(garden.NetOutRule{
   642  							Protocol: garden.ProtocolAll,
   643  							Ports:    []garden.PortRange{{Start: 1, End: 5}},
   644  						})).To(MatchError("Ports cannot be specified for Protocol ALL"))
   645  					})
   646  
   647  					It("does not run iptables", func() {
   648  						subject.PrependFilterRule(garden.NetOutRule{
   649  							Protocol: garden.ProtocolAll,
   650  							Ports:    []garden.PortRange{{Start: 1, End: 5}},
   651  						})
   652  
   653  						Expect(fakeRunner.ExecutedCommands()).To(HaveLen(0))
   654  					})
   655  				})
   656  
   657  				Context("when a portrange is specified for ProtocolICMP", func() {
   658  					It("returns a nice error message", func() {
   659  						Expect(subject.PrependFilterRule(garden.NetOutRule{
   660  							Protocol: garden.ProtocolICMP,
   661  							Ports:    []garden.PortRange{{Start: 1, End: 5}},
   662  						})).To(MatchError("Ports cannot be specified for Protocol ICMP"))
   663  					})
   664  
   665  					It("does not run iptables", func() {
   666  						subject.PrependFilterRule(garden.NetOutRule{
   667  							Protocol: garden.ProtocolICMP,
   668  							Ports:    []garden.PortRange{{Start: 1, End: 5}},
   669  						})
   670  
   671  						Expect(fakeRunner.ExecutedCommands()).To(HaveLen(0))
   672  					})
   673  				})
   674  
   675  				Context("when an invaild protocol is specified", func() {
   676  					It("returns an error", func() {
   677  						err := subject.PrependFilterRule(garden.NetOutRule{
   678  							Protocol: garden.Protocol(52),
   679  						})
   680  						Expect(err).To(HaveOccurred())
   681  						Expect(err).To(MatchError("invalid protocol: 52"))
   682  					})
   683  				})
   684  
   685  				Context("when the command returns an error", func() {
   686  					It("returns a wrapped error, including stderr", func() {
   687  						someError := errors.New("badly laid iptable")
   688  						fakeRunner.WhenRunning(
   689  							fake_command_runner.CommandSpec{Path: "/sbin/iptables"},
   690  							func(cmd *exec.Cmd) error {
   691  								cmd.Stderr.Write([]byte("stderr contents"))
   692  								return someError
   693  							},
   694  						)
   695  
   696  						Expect(subject.PrependFilterRule(garden.NetOutRule{})).To(MatchError("iptables: badly laid iptable, stderr contents"))
   697  					})
   698  				})
   699  			})
   700  		})
   701  	})
   702  })