From f321162c889351395dbbb43ea989cf8c38e58e63 Mon Sep 17 00:00:00 2001 From: Anish Ramasekar Date: Mon, 1 Jul 2019 12:59:47 -0700 Subject: [PATCH 1/2] Support for IPv6 update comment --- cmd/ip-masq-agent/ip-masq-agent.go | 58 ++++++++++-- cmd/ip-masq-agent/ip-masq-agent_test.go | 119 ++++++++++++++++++++---- 2 files changed, 152 insertions(+), 25 deletions(-) diff --git a/cmd/ip-masq-agent/ip-masq-agent.go b/cmd/ip-masq-agent/ip-masq-agent.go index ed5dbfc..29190e0 100644 --- a/cmd/ip-masq-agent/ip-masq-agent.go +++ b/cmd/ip-masq-agent/ip-masq-agent.go @@ -39,6 +39,8 @@ import ( const ( linkLocalCIDR = "169.254.0.0/16" + // RFC 4291 + linkLocalCIDRIPv6 = "FE80::/10" // path to a yaml or json file configPath = "/etc/config/ip-masq-agent" ) @@ -100,19 +102,23 @@ func NewMasqConfig(masqAllReservedRanges bool) *MasqConfig { // daemon object type MasqDaemon struct { - config *MasqConfig - iptables utiliptables.Interface + config *MasqConfig + iptables utiliptables.Interface + ip6tables utiliptables.Interface } // returns a MasqDaemon with default values, including an initialized utiliptables.Interface func NewMasqDaemon(c *MasqConfig) *MasqDaemon { execer := utilexec.New() dbus := utildbus.New() - protocol := utiliptables.ProtocolIpv4 - iptables := utiliptables.New(execer, dbus, protocol) + protocolv4 := utiliptables.ProtocolIpv4 + protocolv6 := utiliptables.ProtocolIpv6 + iptables := utiliptables.New(execer, dbus, protocolv4) + ip6tables := utiliptables.New(execer, dbus, protocolv6) return &MasqDaemon{ - config: c, - iptables: iptables, + config: c, + iptables: iptables, + ip6tables: ip6tables, } } @@ -240,6 +246,8 @@ func validateCIDR(cidr string) error { func (m *MasqDaemon) syncMasqRules() error { // make sure our custom chain for non-masquerade exists m.iptables.EnsureChain(utiliptables.TableNAT, masqChain) + // make sure our custom chain for ipv6 non-masquerade exists + m.ip6tables.EnsureChain(utiliptables.TableNAT, masqChain) // ensure that any non-local in POSTROUTING jumps to masqChain if err := m.ensurePostroutingJump(); err != nil { @@ -251,23 +259,39 @@ func (m *MasqDaemon) syncMasqRules() error { writeLine(lines, "*nat") writeLine(lines, utiliptables.MakeChainLine(masqChain)) // effectively flushes masqChain atomically with rule restore + // build up lines to pass to ip6tables-restore + lines6 := bytes.NewBuffer(nil) + writeLine(lines6, "*nat") + writeLine(lines6, utiliptables.MakeChainLine(masqChain)) // effectively flushes masqChain atomically with rule restore + // link-local CIDR is always non-masquerade if !m.config.MasqLinkLocal { writeNonMasqRule(lines, linkLocalCIDR) + writeNonMasqRule(lines6, linkLocalCIDRIPv6) } // non-masquerade for user-provided CIDRs for _, cidr := range m.config.NonMasqueradeCIDRs { - writeNonMasqRule(lines, cidr) + if !isIPv6CIDR(cidr) { + writeNonMasqRule(lines, cidr) + continue + } + writeNonMasqRule(lines6, cidr) } // masquerade all other traffic that is not bound for a --dst-type LOCAL destination writeMasqRule(lines) + writeMasqRule(lines6) writeLine(lines, "COMMIT") + writeLine(lines6, "COMMIT") + if err := m.iptables.RestoreAll(lines.Bytes(), utiliptables.NoFlushTables, utiliptables.NoRestoreCounters); err != nil { return err } + if err := m.ip6tables.RestoreAll(lines6.Bytes(), utiliptables.NoFlushTables, utiliptables.NoRestoreCounters); err != nil { + return err + } return nil } @@ -285,6 +309,11 @@ func (m *MasqDaemon) ensurePostroutingJump() error { "-m", "addrtype", "!", "--dst-type", "LOCAL", "-j", string(masqChain)); err != nil { return fmt.Errorf("failed to ensure that %s chain %s jumps to MASQUERADE: %v", utiliptables.TableNAT, masqChain, err) } + if _, err := m.ip6tables.EnsureRule(utiliptables.Append, utiliptables.TableNAT, utiliptables.ChainPostrouting, + "-m", "comment", "--comment", postroutingJumpComment(), + "-m", "addrtype", "!", "--dst-type", "LOCAL", "-j", string(masqChain)); err != nil { + return fmt.Errorf("failed to ensure that %s chain %s jumps to MASQUERADE: %v for ipv6", utiliptables.TableNAT, masqChain, err) + } return nil } @@ -311,3 +340,18 @@ func writeRule(lines *bytes.Buffer, position utiliptables.RulePosition, chain ut func writeLine(lines *bytes.Buffer, words ...string) { lines.WriteString(strings.Join(words, " ") + "\n") } + +// isIPv6CIDR checks if the provided cidr block belongs to ipv6 family. +// If cidr belongs to ipv6 family, return true else it returns false +// which means the cidr belongs to ipv4 family +func isIPv6CIDR(cidr string) bool { + ip, _, _ := net.ParseCIDR(cidr) + return isIPv6(ip.String()) +} + +// isIPv6 checks if the provided ip belongs to ipv6 family. +// If ip belongs to ipv6 family, return true else it returns false +// which means the ip belongs to ipv6 family +func isIPv6(ip string) bool { + return net.ParseIP(ip).To4() == nil +} diff --git a/cmd/ip-masq-agent/ip-masq-agent_test.go b/cmd/ip-masq-agent/ip-masq-agent_test.go index 6c1a83b..5e30834 100644 --- a/cmd/ip-masq-agent/ip-masq-agent_test.go +++ b/cmd/ip-masq-agent/ip-masq-agent_test.go @@ -40,8 +40,9 @@ func TestMain(m *testing.M) { // returns a MasqDaemon with empty config values and a fake iptables interface func NewFakeMasqDaemon() *MasqDaemon { return &MasqDaemon{ - config: &MasqConfig{}, - iptables: iptest.NewFake(), + config: &MasqConfig{}, + iptables: iptest.NewFake(), + ip6tables: iptest.NewFake(), } } @@ -197,6 +198,19 @@ resyncInterval: 5m // file does not exist {"no config file", fakefs.NotExistFS{}, nil, NewMasqConfigNoReservedRanges()}, // If the file does not exist, defaults should be used + + // valid json with ipv6 non masquerade cidr + {"valid json file, all keys with ipv6 cidr", fakefs.StringFS{File: ` + { + "nonMasqueradeCIDRs": ["172.16.0.0/12", "10.0.0.0/8", "fc00::/7"], + "masqLinkLocal": true, + "resyncInterval": "5s" + } + `}, + nil, &MasqConfig{ + NonMasqueradeCIDRs: []string{"172.16.0.0/12", "10.0.0.0/8", "fc00::/7"}, + MasqLinkLocal: true, + ResyncInterval: Duration(5 * time.Second)}}, } // tests MasqDaemon.syncConfig @@ -216,10 +230,11 @@ func TestSyncConfig(t *testing.T) { // tests MasqDaemon.syncMasqRules func TestSyncMasqRules(t *testing.T) { var syncMasqRulesTests = []struct { - desc string // human readable description of the test - cfg *MasqConfig // Masq configuration to use - err error // expected error, if any. If nil, no error expected - want string // String expected to be sent to iptables-restore + desc string // human readable description of the test + cfg *MasqConfig // Masq configuration to use + err error // expected error, if any. If nil, no error expected + want string // String expected to be sent to iptables-restore + want6 string // String expected to be sent to ip6tables-restore }{ { desc: "empty config", @@ -229,6 +244,12 @@ func TestSyncMasqRules(t *testing.T) { -A ` + string(masqChain) + ` ` + nonMasqRuleComment + ` -d 169.254.0.0/16 -j RETURN -A ` + string(masqChain) + ` ` + masqRuleComment + ` -j MASQUERADE COMMIT +`, + want6: `*nat +:` + string(masqChain) + ` - [0:0] +-A ` + string(masqChain) + ` ` + nonMasqRuleComment + ` -d FE80::/10 -j RETURN +-A ` + string(masqChain) + ` ` + masqRuleComment + ` -j MASQUERADE +COMMIT `, }, { @@ -242,6 +263,12 @@ COMMIT -A ` + string(masqChain) + ` ` + nonMasqRuleComment + ` -d 192.168.0.0/16 -j RETURN -A ` + string(masqChain) + ` ` + masqRuleComment + ` -j MASQUERADE COMMIT +`, + want6: `*nat +:` + string(masqChain) + ` - [0:0] +-A ` + string(masqChain) + ` ` + nonMasqRuleComment + ` -d FE80::/10 -j RETURN +-A ` + string(masqChain) + ` ` + masqRuleComment + ` -j MASQUERADE +COMMIT `, }, { @@ -263,6 +290,35 @@ COMMIT -A ` + string(masqChain) + ` ` + nonMasqRuleComment + ` -d 240.0.0.0/4 -j RETURN -A ` + string(masqChain) + ` ` + masqRuleComment + ` -j MASQUERADE COMMIT +`, + want6: `*nat +:` + string(masqChain) + ` - [0:0] +-A ` + string(masqChain) + ` ` + nonMasqRuleComment + ` -d FE80::/10 -j RETURN +-A ` + string(masqChain) + ` ` + masqRuleComment + ` -j MASQUERADE +COMMIT +`, + }, + { + desc: "config has ipv4 and ipv6 non masquerade cidr", + cfg: &MasqConfig{ + NonMasqueradeCIDRs: []string{ + "10.244.0.0/16", + "fc00::/7", + }, + }, + want: `*nat +:` + string(masqChain) + ` - [0:0] +-A ` + string(masqChain) + ` ` + nonMasqRuleComment + ` -d 169.254.0.0/16 -j RETURN +-A ` + string(masqChain) + ` ` + nonMasqRuleComment + ` -d 10.244.0.0/16 -j RETURN +-A ` + string(masqChain) + ` ` + masqRuleComment + ` -j MASQUERADE +COMMIT +`, + want6: `*nat +:` + string(masqChain) + ` - [0:0] +-A ` + string(masqChain) + ` ` + nonMasqRuleComment + ` -d FE80::/10 -j RETURN +-A ` + string(masqChain) + ` ` + nonMasqRuleComment + ` -d fc00::/7 -j RETURN +-A ` + string(masqChain) + ` ` + masqRuleComment + ` -j MASQUERADE +COMMIT `, }, } @@ -279,6 +335,14 @@ COMMIT if string(fipt.Lines) != tt.want { t.Errorf("syncMasqRules wrote %q, want %q", string(fipt.Lines), tt.want) } + + fipt6, ok := m.ip6tables.(*iptest.FakeIPTables) + if !ok { + t.Errorf("MasqDaemon wasn't using the expected ip6tables mock") + } + if string(fipt6.Lines) != tt.want6 { + t.Errorf("syncMasqRules wrote %q, want %q", string(fipt.Lines), tt.want) + } }) } } @@ -294,19 +358,38 @@ func TestEnsurePostroutingJump(t *testing.T) { // tests writeNonMasqRule func TestWriteNonMasqRule(t *testing.T) { - lines := bytes.NewBuffer(nil) - cidr := "10.0.0.0/8" - want := string(utiliptables.Append) + " " + string(masqChain) + - ` -m comment --comment "ip-masq-agent: local traffic is not subject to MASQUERADE"` + - " -d " + cidr + " -j RETURN\n" - writeNonMasqRule(lines, cidr) - - s, err := lines.ReadString('\n') - if err != nil { - t.Error("writeRule did not append a newline") + var writeNonMasqRuleTests = []struct { + desc string + cidr string + want string + }{ + { + desc: "with ipv4 non masquerade cidr", + cidr: "10.0.0.0/8", + want: string(utiliptables.Append) + " " + string(masqChain) + + ` -m comment --comment "ip-masq-agent: local traffic is not subject to MASQUERADE"` + + " -d 10.0.0.0/8 -j RETURN\n", + }, + { + desc: "with ipv6 non masquerade cidr", + cidr: "fc00::/7", + want: string(utiliptables.Append) + " " + string(masqChain) + + ` -m comment --comment "ip-masq-agent: local traffic is not subject to MASQUERADE"` + + " -d fc00::/7 -j RETURN\n", + }, } - if s != want { - t.Errorf("writeNonMasqRule(lines, "+cidr+"):\n got: %q\n want: %q", s, want) + + for _, tt := range writeNonMasqRuleTests { + lines := bytes.NewBuffer(nil) + writeNonMasqRule(lines, tt.cidr) + + s, err := lines.ReadString('\n') + if err != nil { + t.Error("writeRule did not append a newline") + } + if s != tt.want { + t.Errorf("writeNonMasqRule(lines, "+tt.cidr+"):\n got: %q\n want: %q", s, tt.want) + } } } From 6a890d40bbe0c46baf457d0120a86c639abf31a0 Mon Sep 17 00:00:00 2001 From: Anish Ramasekar Date: Mon, 15 Jul 2019 14:11:31 -0700 Subject: [PATCH 2/2] add agent flag for enable-ipv6 --- README.md | 3 + agent-config/config | 1 + cmd/ip-masq-agent/ip-masq-agent.go | 89 +++++++++++++----- cmd/ip-masq-agent/ip-masq-agent_test.go | 115 +++++++++++++++--------- 4 files changed, 144 insertions(+), 64 deletions(-) diff --git a/README.md b/README.md index 13c0f5c..c93eefb 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ By default, the agent is configured to reload its configuration from the `/etc/c The agent configuration file should be written in yaml or json syntax, and may contain three optional keys: - `nonMasqueradeCIDRs []string`: A list strings in CIDR notation that specify the non-masquerade ranges. - `masqLinkLocal bool`: Whether to masquerade traffic to `169.254.0.0/16`. False by default. +- `masqLinkLocalIPv6 bool`: Whether to masquerade traffic to `fe80::/10`. False by default. - `resyncInterval string`: The interval at which the agent attempts to reload config from disk. The syntax is any format accepted by Go's [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) function. The agent will look for a config file in its container at `/etc/config/ip-masq-agent`. This file can be provided via a `ConfigMap`, plumbed into the container via a `ConfigMapVolumeSource`. As a result, the agent can be reconfigured in a live cluster by creating or editing this `ConfigMap`. @@ -50,6 +51,8 @@ The agent accepts two flags, which may be specified in the yaml file. `nomasq-all-reserved-ranges` : Whether or not to masquerade all RFC reserved ranges when the configmap is empty. The default is `false`. When `false`, the agent will masquerade to every destination except the ranges reserved by RFC 1918 (namely `10.0.0.0/8`, `172.16.0.0/12`, and `192.168.0.0/16`). When `true`, the agent will masquerade to every destination that is not marked reserved by an RFC. The full list of ranges is (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `100.64.0.0/10`, `192.0.0.0/24`, `192.0.2.0/24`, `192.88.99.0/24`, `198.18.0.0/15`, `198.51.100.0/24`, `203.0.113.0/24`, and `240.0.0.0/4`). Note however, that this list of ranges is overridden by specifying the nonMasqueradeCIDRs key in the agent configmap. +`enable-ipv6` +: Whether to configurate ip6tables rules. By default `enable-ipv6` is false. ## Rationale (from the [incubator proposal](https://gist.github.com/mtaufen/253309166e7d5aa9e9b560600a438447)) diff --git a/agent-config/config b/agent-config/config index 2b0e828..c34a990 100644 --- a/agent-config/config +++ b/agent-config/config @@ -3,4 +3,5 @@ nonMasqueradeCIDRs: - 172.16.0.0/12 - 192.168.0.0/16 masqLinkLocal: false +masqLinkLocalIPv6: false resyncInterval: 60s \ No newline at end of file diff --git a/cmd/ip-masq-agent/ip-masq-agent.go b/cmd/ip-masq-agent/ip-masq-agent.go index 29190e0..e4cd97d 100644 --- a/cmd/ip-masq-agent/ip-masq-agent.go +++ b/cmd/ip-masq-agent/ip-masq-agent.go @@ -40,7 +40,7 @@ import ( const ( linkLocalCIDR = "169.254.0.0/16" // RFC 4291 - linkLocalCIDRIPv6 = "FE80::/10" + linkLocalCIDRIPv6 = "fe80::/10" // path to a yaml or json file configPath = "/etc/config/ip-masq-agent" ) @@ -50,18 +50,21 @@ var ( masqChain utiliptables.Chain masqChainFlag = flag.String("masq-chain", "IP-MASQ-AGENT", `Name of nat chain for iptables masquerade rules.`) noMasqueradeAllReservedRangesFlag = flag.Bool("nomasq-all-reserved-ranges", false, "Whether to disable masquerade for all IPv4 ranges reserved by RFCs.") + enableIPv6 = flag.Bool("enable-ipv6", false, "Whether to enable IPv6.") ) -// config object +// MasqConfig object type MasqConfig struct { NonMasqueradeCIDRs []string `json:"nonMasqueradeCIDRs"` MasqLinkLocal bool `json:"masqLinkLocal"` + MasqLinkLocalIPv6 bool `json:"masqLinkLocalIPv6"` ResyncInterval Duration `json:"resyncInterval"` } -// Go's JSON unmarshaler can't handle time.ParseDuration syntax when unmarshaling into time.Duration, so we do it here +// Duration - Go's JSON unmarshaler can't handle time.ParseDuration syntax when unmarshaling into time.Duration, so we do it here type Duration time.Duration +// UnmarshalJSON ... func (d *Duration) UnmarshalJSON(json []byte) error { if json[0] == '"' { s := string(json[1 : len(json)-1]) @@ -76,7 +79,7 @@ func (d *Duration) UnmarshalJSON(json []byte) error { return fmt.Errorf("expected string value for unmarshal to field of type Duration, got %q", s) } -// returns a MasqConfig with default values +// NewMasqConfig returns a MasqConfig with default values func NewMasqConfig(masqAllReservedRanges bool) *MasqConfig { // RFC 1918 defines the private ip address space as 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 nonMasq := []string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"} @@ -96,18 +99,19 @@ func NewMasqConfig(masqAllReservedRanges bool) *MasqConfig { return &MasqConfig{ NonMasqueradeCIDRs: nonMasq, MasqLinkLocal: false, + MasqLinkLocalIPv6: false, ResyncInterval: Duration(60 * time.Second), } } -// daemon object +// MasqDaemon object type MasqDaemon struct { config *MasqConfig iptables utiliptables.Interface ip6tables utiliptables.Interface } -// returns a MasqDaemon with default values, including an initialized utiliptables.Interface +// NewMasqDaemon returns a MasqDaemon with default values, including an initialized utiliptables.Interface func NewMasqDaemon(c *MasqConfig) *MasqDaemon { execer := utilexec.New() dbus := utildbus.New() @@ -137,6 +141,7 @@ func main() { m.Run() } +// Run ... func (m *MasqDaemon) Run() { // Periodically resync to reconfigure or heal from any rule decay for { @@ -152,6 +157,11 @@ func (m *MasqDaemon) Run() { glog.Errorf("error syncing masquerade rules: %v", err) return } + // resync ipv6 rules + if err := m.syncMasqRulesIPv6(); err != nil { + glog.Errorf("error syncing masquerade rules for ipv6: %v", err) + return + } }() } } @@ -180,6 +190,7 @@ func (m *MasqDaemon) syncConfig(fs fakefs.FileSystem) error { // file does not exist, use defaults m.config.NonMasqueradeCIDRs = c.NonMasqueradeCIDRs m.config.MasqLinkLocal = c.MasqLinkLocal + m.config.MasqLinkLocalIPv6 = c.MasqLinkLocalIPv6 m.config.ResyncInterval = c.ResyncInterval glog.V(2).Infof("no config file found at %q, using default values", configPath) return nil @@ -216,13 +227,17 @@ func (c *MasqConfig) validate() error { // limit to 64 CIDRs (excluding link-local) to protect against really bad mistakes n := len(c.NonMasqueradeCIDRs) if n > 64 { - return fmt.Errorf("The daemon can only accept up to 64 CIDRs (excluding link-local), but got %d CIDRs (excluding link local).", n) + return fmt.Errorf("the daemon can only accept up to 64 CIDRs (excluding link-local), but got %d CIDRs (excluding link local)", n) } // check CIDRs are valid for _, cidr := range c.NonMasqueradeCIDRs { if err := validateCIDR(cidr); err != nil { return err } + // can't configure ipv6 cidr if ipv6 is not enabled + if !*enableIPv6 && isIPv6CIDR(cidr) { + return fmt.Errorf("ipv6 is not enabled, but ipv6 cidr %s provided. Enable ipv6 using --enable-ipv6 agent flag", cidr) + } } return nil } @@ -246,8 +261,6 @@ func validateCIDR(cidr string) error { func (m *MasqDaemon) syncMasqRules() error { // make sure our custom chain for non-masquerade exists m.iptables.EnsureChain(utiliptables.TableNAT, masqChain) - // make sure our custom chain for ipv6 non-masquerade exists - m.ip6tables.EnsureChain(utiliptables.TableNAT, masqChain) // ensure that any non-local in POSTROUTING jumps to masqChain if err := m.ensurePostroutingJump(); err != nil { @@ -259,38 +272,66 @@ func (m *MasqDaemon) syncMasqRules() error { writeLine(lines, "*nat") writeLine(lines, utiliptables.MakeChainLine(masqChain)) // effectively flushes masqChain atomically with rule restore - // build up lines to pass to ip6tables-restore - lines6 := bytes.NewBuffer(nil) - writeLine(lines6, "*nat") - writeLine(lines6, utiliptables.MakeChainLine(masqChain)) // effectively flushes masqChain atomically with rule restore - // link-local CIDR is always non-masquerade if !m.config.MasqLinkLocal { writeNonMasqRule(lines, linkLocalCIDR) - writeNonMasqRule(lines6, linkLocalCIDRIPv6) } // non-masquerade for user-provided CIDRs for _, cidr := range m.config.NonMasqueradeCIDRs { if !isIPv6CIDR(cidr) { writeNonMasqRule(lines, cidr) - continue } - writeNonMasqRule(lines6, cidr) } // masquerade all other traffic that is not bound for a --dst-type LOCAL destination writeMasqRule(lines) - writeMasqRule(lines6) writeLine(lines, "COMMIT") - writeLine(lines6, "COMMIT") if err := m.iptables.RestoreAll(lines.Bytes(), utiliptables.NoFlushTables, utiliptables.NoRestoreCounters); err != nil { return err } - if err := m.ip6tables.RestoreAll(lines6.Bytes(), utiliptables.NoFlushTables, utiliptables.NoRestoreCounters); err != nil { - return err + return nil +} + +func (m *MasqDaemon) syncMasqRulesIPv6() error { + isIPv6Enabled := *enableIPv6 + + if isIPv6Enabled { + // make sure our custom chain for ipv6 non-masquerade exists + _, err := m.ip6tables.EnsureChain(utiliptables.TableNAT, masqChain) + if err != nil { + return err + } + // ensure that any non-local in POSTROUTING jumps to masqChain + if err := m.ensurePostroutingJumpIPv6(); err != nil { + return err + } + // build up lines to pass to ip6tables-restore + lines6 := bytes.NewBuffer(nil) + writeLine(lines6, "*nat") + writeLine(lines6, utiliptables.MakeChainLine(masqChain)) // effectively flushes masqChain atomically with rule restore + + // link-local IPv6 CIDR is non-masquerade by default + if !m.config.MasqLinkLocalIPv6 { + writeNonMasqRule(lines6, linkLocalCIDRIPv6) + } + + for _, cidr := range m.config.NonMasqueradeCIDRs { + if isIPv6CIDR(cidr) { + writeNonMasqRule(lines6, cidr) + } + } + + // masquerade all other traffic that is not bound for a --dst-type LOCAL destination + writeMasqRule(lines6) + + writeLine(lines6, "COMMIT") + + if err := m.ip6tables.RestoreAll(lines6.Bytes(), utiliptables.NoFlushTables, utiliptables.NoRestoreCounters); err != nil { + return err + } } return nil } @@ -309,6 +350,10 @@ func (m *MasqDaemon) ensurePostroutingJump() error { "-m", "addrtype", "!", "--dst-type", "LOCAL", "-j", string(masqChain)); err != nil { return fmt.Errorf("failed to ensure that %s chain %s jumps to MASQUERADE: %v", utiliptables.TableNAT, masqChain, err) } + return nil +} + +func (m *MasqDaemon) ensurePostroutingJumpIPv6() error { if _, err := m.ip6tables.EnsureRule(utiliptables.Append, utiliptables.TableNAT, utiliptables.ChainPostrouting, "-m", "comment", "--comment", postroutingJumpComment(), "-m", "addrtype", "!", "--dst-type", "LOCAL", "-j", string(masqChain)); err != nil { @@ -351,7 +396,7 @@ func isIPv6CIDR(cidr string) bool { // isIPv6 checks if the provided ip belongs to ipv6 family. // If ip belongs to ipv6 family, return true else it returns false -// which means the ip belongs to ipv6 family +// which means the ip belongs to ipv4 family func isIPv6(ip string) bool { return net.ParseIP(ip).To4() == nil } diff --git a/cmd/ip-masq-agent/ip-masq-agent_test.go b/cmd/ip-masq-agent/ip-masq-agent_test.go index 5e30834..4440a2e 100644 --- a/cmd/ip-masq-agent/ip-masq-agent_test.go +++ b/cmd/ip-masq-agent/ip-masq-agent_test.go @@ -216,6 +216,7 @@ resyncInterval: 5m // tests MasqDaemon.syncConfig func TestSyncConfig(t *testing.T) { for _, tt := range syncConfigTests { + flag.Set("enable-ipv6", "true") m := NewFakeMasqDaemon() m.config = NewMasqConfigNoReservedRanges() err := m.syncConfig(tt.fs) @@ -230,11 +231,10 @@ func TestSyncConfig(t *testing.T) { // tests MasqDaemon.syncMasqRules func TestSyncMasqRules(t *testing.T) { var syncMasqRulesTests = []struct { - desc string // human readable description of the test - cfg *MasqConfig // Masq configuration to use - err error // expected error, if any. If nil, no error expected - want string // String expected to be sent to iptables-restore - want6 string // String expected to be sent to ip6tables-restore + desc string // human readable description of the test + cfg *MasqConfig // Masq configuration to use + err error // expected error, if any. If nil, no error expected + want string // String expected to be sent to iptables-restore }{ { desc: "empty config", @@ -244,12 +244,6 @@ func TestSyncMasqRules(t *testing.T) { -A ` + string(masqChain) + ` ` + nonMasqRuleComment + ` -d 169.254.0.0/16 -j RETURN -A ` + string(masqChain) + ` ` + masqRuleComment + ` -j MASQUERADE COMMIT -`, - want6: `*nat -:` + string(masqChain) + ` - [0:0] --A ` + string(masqChain) + ` ` + nonMasqRuleComment + ` -d FE80::/10 -j RETURN --A ` + string(masqChain) + ` ` + masqRuleComment + ` -j MASQUERADE -COMMIT `, }, { @@ -263,12 +257,6 @@ COMMIT -A ` + string(masqChain) + ` ` + nonMasqRuleComment + ` -d 192.168.0.0/16 -j RETURN -A ` + string(masqChain) + ` ` + masqRuleComment + ` -j MASQUERADE COMMIT -`, - want6: `*nat -:` + string(masqChain) + ` - [0:0] --A ` + string(masqChain) + ` ` + nonMasqRuleComment + ` -d FE80::/10 -j RETURN --A ` + string(masqChain) + ` ` + masqRuleComment + ` -j MASQUERADE -COMMIT `, }, { @@ -290,12 +278,6 @@ COMMIT -A ` + string(masqChain) + ` ` + nonMasqRuleComment + ` -d 240.0.0.0/4 -j RETURN -A ` + string(masqChain) + ` ` + masqRuleComment + ` -j MASQUERADE COMMIT -`, - want6: `*nat -:` + string(masqChain) + ` - [0:0] --A ` + string(masqChain) + ` ` + nonMasqRuleComment + ` -d FE80::/10 -j RETURN --A ` + string(masqChain) + ` ` + masqRuleComment + ` -j MASQUERADE -COMMIT `, }, { @@ -312,13 +294,6 @@ COMMIT -A ` + string(masqChain) + ` ` + nonMasqRuleComment + ` -d 10.244.0.0/16 -j RETURN -A ` + string(masqChain) + ` ` + masqRuleComment + ` -j MASQUERADE COMMIT -`, - want6: `*nat -:` + string(masqChain) + ` - [0:0] --A ` + string(masqChain) + ` ` + nonMasqRuleComment + ` -d FE80::/10 -j RETURN --A ` + string(masqChain) + ` ` + nonMasqRuleComment + ` -d fc00::/7 -j RETURN --A ` + string(masqChain) + ` ` + masqRuleComment + ` -j MASQUERADE -COMMIT `, }, } @@ -335,13 +310,67 @@ COMMIT if string(fipt.Lines) != tt.want { t.Errorf("syncMasqRules wrote %q, want %q", string(fipt.Lines), tt.want) } + }) + } +} +// tests MasqDaemon.syncMasqRulesIPv6 +func TestSyncMasqRulesIPv6(t *testing.T) { + var syncMasqRulesIPv6Tests = []struct { + desc string // human readable description of the test + cfg *MasqConfig // Masq configuration to use + err error // expected error, if any. If nil, no error expected + want string // String expected to be sent to iptables-restore + }{ + { + desc: "empty config", + cfg: &MasqConfig{}, + want: `*nat +:` + string(masqChain) + ` - [0:0] +-A ` + string(masqChain) + ` ` + nonMasqRuleComment + ` -d fe80::/10 -j RETURN +-A ` + string(masqChain) + ` ` + masqRuleComment + ` -j MASQUERADE +COMMIT +`, + }, + { + desc: "config has ipv4 and ipv6 non masquerade cidr", + cfg: &MasqConfig{ + NonMasqueradeCIDRs: []string{ + "10.244.0.0/16", + "fc00::/7", + }, + }, + want: `*nat +:` + string(masqChain) + ` - [0:0] +-A ` + string(masqChain) + ` ` + nonMasqRuleComment + ` -d fe80::/10 -j RETURN +-A ` + string(masqChain) + ` ` + nonMasqRuleComment + ` -d fc00::/7 -j RETURN +-A ` + string(masqChain) + ` ` + masqRuleComment + ` -j MASQUERADE +COMMIT +`, + }, + { + desc: "config has masqLinkLocalIPv6: true", + cfg: &MasqConfig{MasqLinkLocalIPv6: true}, + want: `*nat +:` + string(masqChain) + ` - [0:0] +-A ` + string(masqChain) + ` ` + masqRuleComment + ` -j MASQUERADE +COMMIT +`, + }, + } + + for _, tt := range syncMasqRulesIPv6Tests { + t.Run(tt.desc, func(t *testing.T) { + flag.Set("enable-ipv6", "true") + m := NewFakeMasqDaemon() + m.config = tt.cfg + m.syncMasqRulesIPv6() fipt6, ok := m.ip6tables.(*iptest.FakeIPTables) if !ok { - t.Errorf("MasqDaemon wasn't using the expected ip6tables mock") + t.Errorf("MasqDaemon wasn't using the expected iptables mock") } - if string(fipt6.Lines) != tt.want6 { - t.Errorf("syncMasqRules wrote %q, want %q", string(fipt.Lines), tt.want) + if string(fipt6.Lines) != tt.want { + t.Errorf("syncMasqRulesIPv6 wrote %q, want %q", string(fipt6.Lines), tt.want) } }) } @@ -380,16 +409,18 @@ func TestWriteNonMasqRule(t *testing.T) { } for _, tt := range writeNonMasqRuleTests { - lines := bytes.NewBuffer(nil) - writeNonMasqRule(lines, tt.cidr) + t.Run(tt.desc, func(t *testing.T) { + lines := bytes.NewBuffer(nil) + writeNonMasqRule(lines, tt.cidr) - s, err := lines.ReadString('\n') - if err != nil { - t.Error("writeRule did not append a newline") - } - if s != tt.want { - t.Errorf("writeNonMasqRule(lines, "+tt.cidr+"):\n got: %q\n want: %q", s, tt.want) - } + s, err := lines.ReadString('\n') + if err != nil { + t.Error("writeRule did not append a newline") + } + if s != tt.want { + t.Errorf("writeNonMasqRule(lines, "+tt.cidr+"):\n got: %q\n want: %q", s, tt.want) + } + }) } }