diff --git a/libnetwork/internal/util/validate.go b/libnetwork/internal/util/validate.go index 14f4052d8..adf615552 100644 --- a/libnetwork/internal/util/validate.go +++ b/libnetwork/internal/util/validate.go @@ -81,6 +81,43 @@ func ValidateSubnets(network *types.Network, addGateway bool, usedNetworks []*ne return nil } +func ValidateRoutes(routes []types.Route) error { + for _, route := range routes { + err := ValidateRoute(route) + if err != nil { + return err + } + } + return nil +} + +func ValidateRoute(route types.Route) error { + if route.Destination.IP == nil { + return fmt.Errorf("route destination ip nil") + } + + if route.Destination.Mask == nil { + return fmt.Errorf("route destination mask nil") + } + + if route.Gateway == nil { + return fmt.Errorf("route gateway nil") + } + + // Reparse to ensure destination is valid. + ip, ipNet, err := net.ParseCIDR(route.Destination.String()) + if err != nil { + return fmt.Errorf("route destination invalid: %w", err) + } + + // check that destination is a network and not an address + if !ip.Equal(ipNet.IP) { + return fmt.Errorf("route destination invalid") + } + + return nil +} + func ValidateSetupOptions(n NetUtil, namespacePath string, options types.SetupOptions) error { if namespacePath == "" { return errors.New("namespacePath is empty") diff --git a/libnetwork/netavark/config.go b/libnetwork/netavark/config.go index 297338ffb..b1c47703b 100644 --- a/libnetwork/netavark/config.go +++ b/libnetwork/netavark/config.go @@ -198,6 +198,13 @@ func (n *netavarkNetwork) networkCreate(newNetwork *types.Network, defaultNet bo if err != nil { return nil, err } + case types.NoDefaultRoute: + val, err := strconv.ParseBool(value) + if err != nil { + return nil, err + } + // rust only support "true" or "false" while go can parse 1 and 0 as well so we need to change it + newNetwork.Options[types.NoDefaultRoute] = strconv.FormatBool(val) default: return nil, fmt.Errorf("unsupported bridge network option %s", key) @@ -237,6 +244,12 @@ func (n *netavarkNetwork) networkCreate(newNetwork *types.Network, defaultNet bo return nil, err } + //validate routes + err = internalutil.ValidateRoutes(newNetwork.Routes) + if err != nil { + return nil, err + } + newNetwork.Created = time.Now() if !defaultNet { @@ -317,6 +330,13 @@ func createIpvlanOrMacvlan(network *types.Network) error { if err != nil { return err } + case types.NoDefaultRoute: + val, err := strconv.ParseBool(value) + if err != nil { + return err + } + // rust only support "true" or "false" while go can parse 1 and 0 as well so we need to change it + network.Options[types.NoDefaultRoute] = strconv.FormatBool(val) default: return fmt.Errorf("unsupported %s network option %s", driver, key) } diff --git a/libnetwork/netavark/config_test.go b/libnetwork/netavark/config_test.go index fff934605..7e6450289 100644 --- a/libnetwork/netavark/config_test.go +++ b/libnetwork/netavark/config_test.go @@ -1356,6 +1356,375 @@ var _ = Describe("Config", func() { }) }) + It("create bridge config with static route", func() { + dest := "10.1.0.0/24" + gw := "10.1.0.1" + d, _ := types.ParseCIDR(dest) + g := net.ParseIP(gw) + network := types.Network{ + Driver: "bridge", + Routes: []types.Route{ + {Destination: d, Gateway: g}, + }, + } + network1, err := libpodNet.NetworkCreate(network, nil) + Expect(err).To(BeNil()) + Expect(network1.Name).ToNot(BeEmpty()) + Expect(network1.Routes).To(HaveLen(1)) + Expect(network1.Routes[0].Destination.String()).To(Equal(dest)) + Expect(network1.Routes[0].Gateway.String()).To(Equal(gw)) + }) + + It("create macvlan config with static route", func() { + dest := "10.1.0.0/24" + gw := "10.1.0.1" + d, _ := types.ParseCIDR(dest) + g := net.ParseIP(gw) + network := types.Network{ + Driver: "macvlan", + Routes: []types.Route{ + {Destination: d, Gateway: g}, + }, + } + network1, err := libpodNet.NetworkCreate(network, nil) + Expect(err).To(BeNil()) + Expect(network1.Name).ToNot(BeEmpty()) + Expect(network1.Routes).To(HaveLen(1)) + Expect(network1.Routes[0].Destination.String()).To(Equal(dest)) + Expect(network1.Routes[0].Gateway.String()).To(Equal(gw)) + }) + + It("create ipvlan config with static route", func() { + subnet := "10.1.0.0/24" + n, _ := types.ParseCIDR(subnet) + dest := "10.1.0.0/24" + gw := "10.1.0.1" + d, _ := types.ParseCIDR(dest) + g := net.ParseIP(gw) + network := types.Network{ + Driver: "ipvlan", + Subnets: []types.Subnet{ + {Subnet: n}, + }, + Routes: []types.Route{ + {Destination: d, Gateway: g}, + }, + } + + network1, err := libpodNet.NetworkCreate(network, nil) + Expect(err).To(BeNil()) + Expect(network1.Name).ToNot(BeEmpty()) + Expect(network1.Routes).To(HaveLen(1)) + Expect(network1.Routes[0].Destination.String()).To(Equal(dest)) + Expect(network1.Routes[0].Gateway.String()).To(Equal(gw)) + }) + + It("create bridge config with static route (ipv6)", func() { + dest := "fd:1234::/64" + gw := "fd:4321::1" + d, _ := types.ParseCIDR(dest) + g := net.ParseIP(gw) + network := types.Network{ + Driver: "bridge", + Routes: []types.Route{ + {Destination: d, Gateway: g}, + }, + } + network1, err := libpodNet.NetworkCreate(network, nil) + Expect(err).To(BeNil()) + Expect(network1.Name).ToNot(BeEmpty()) + Expect(network1.Routes).To(HaveLen(1)) + Expect(network1.Routes[0].Destination.String()).To(Equal(dest)) + Expect(network1.Routes[0].Gateway.String()).To(Equal(gw)) + }) + + It("create macvlan config with static route (ipv6)", func() { + dest := "fd:1234::/64" + gw := "fd:4321::1" + d, _ := types.ParseCIDR(dest) + g := net.ParseIP(gw) + network := types.Network{ + Driver: "macvlan", + Routes: []types.Route{ + {Destination: d, Gateway: g}, + }, + } + network1, err := libpodNet.NetworkCreate(network, nil) + Expect(err).To(BeNil()) + Expect(network1.Name).ToNot(BeEmpty()) + Expect(network1.Routes).To(HaveLen(1)) + Expect(network1.Routes[0].Destination.String()).To(Equal(dest)) + Expect(network1.Routes[0].Gateway.String()).To(Equal(gw)) + }) + + It("create ipvlan config with static route (ipv6)", func() { + subnet := "fd:4321::/64" + n, _ := types.ParseCIDR(subnet) + dest := "fd:1234::/64" + gw := "fd:4321::1" + d, _ := types.ParseCIDR(dest) + g := net.ParseIP(gw) + network := types.Network{ + Driver: "ipvlan", + Subnets: []types.Subnet{ + {Subnet: n}, + }, + Routes: []types.Route{ + {Destination: d, Gateway: g}, + }, + } + network1, err := libpodNet.NetworkCreate(network, nil) + Expect(err).To(BeNil()) + Expect(network1.Name).ToNot(BeEmpty()) + Expect(network1.Routes).To(HaveLen(1)) + Expect(network1.Routes[0].Destination.String()).To(Equal(dest)) + Expect(network1.Routes[0].Gateway.String()).To(Equal(gw)) + }) + + It("create bridge config with invalid static route (destination is address)", func() { + dest := "10.0.11.10/24" + gw := "10.1.0.1" + d, _ := types.ParseCIDR(dest) + g := net.ParseIP(gw) + network := types.Network{ + Driver: "bridge", + Routes: []types.Route{ + {Destination: d, Gateway: g}, + }, + } + _, err := libpodNet.NetworkCreate(network, nil) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(Equal("route destination invalid")) + }) + + It("create macvlan config with invalid static route (destination is address)", func() { + dest := "10.0.11.10/24" + gw := "10.1.0.1" + d, _ := types.ParseCIDR(dest) + g := net.ParseIP(gw) + network := types.Network{ + Driver: "macvlan", + Routes: []types.Route{ + {Destination: d, Gateway: g}, + }, + } + _, err := libpodNet.NetworkCreate(network, nil) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(Equal("route destination invalid")) + }) + + It("create ipvlan config with invalid static route (destination is address)", func() { + subnet := "10.1.0.0/24" + n, _ := types.ParseCIDR(subnet) + dest := "10.0.11.10/24" + gw := "10.1.0.1" + d, _ := types.ParseCIDR(dest) + g := net.ParseIP(gw) + network := types.Network{ + Driver: "ipvlan", + Subnets: []types.Subnet{ + {Subnet: n}, + }, + Routes: []types.Route{ + {Destination: d, Gateway: g}, + }, + } + _, err := libpodNet.NetworkCreate(network, nil) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(Equal("route destination invalid")) + }) + + It("create bridge config with invalid static route (dest = \"foo\")", func() { + dest := "foo" + gw := "10.1.0.1" + d, _ := types.ParseCIDR(dest) + g := net.ParseIP(gw) + network := types.Network{ + Driver: "bridge", + Routes: []types.Route{ + {Destination: d, Gateway: g}, + }, + } + _, err := libpodNet.NetworkCreate(network, nil) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(Equal("route destination ip nil")) + }) + + It("create macvlan config with invalid static route (dest = \"foo\")", func() { + dest := "foo" + gw := "10.1.0.1" + d, _ := types.ParseCIDR(dest) + g := net.ParseIP(gw) + network := types.Network{ + Driver: "macvlan", + Routes: []types.Route{ + {Destination: d, Gateway: g}, + }, + } + _, err := libpodNet.NetworkCreate(network, nil) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(Equal("route destination ip nil")) + }) + + It("create ipvlan config with invalid static route (dest = \"foo\")", func() { + subnet := "10.1.0.0/24" + n, _ := types.ParseCIDR(subnet) + dest := "foo" + gw := "10.1.0.1" + d, _ := types.ParseCIDR(dest) + g := net.ParseIP(gw) + network := types.Network{ + Driver: "ipvlan", + Subnets: []types.Subnet{ + {Subnet: n}, + }, + Routes: []types.Route{ + {Destination: d, Gateway: g}, + }, + } + _, err := libpodNet.NetworkCreate(network, nil) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(Equal("route destination ip nil")) + }) + + It("create bridge config with invalid static route (gw = \"foo\")", func() { + dest := "10.1.0.0/24" + gw := "foo" + d, _ := types.ParseCIDR(dest) + g := net.ParseIP(gw) + network := types.Network{ + Driver: "bridge", + Routes: []types.Route{ + {Destination: d, Gateway: g}, + }, + } + _, err := libpodNet.NetworkCreate(network, nil) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(Equal("route gateway nil")) + }) + + It("create macvlan config with invalid static route (gw = \"foo\")", func() { + dest := "10.1.0.0/24" + gw := "foo" + d, _ := types.ParseCIDR(dest) + g := net.ParseIP(gw) + network := types.Network{ + Driver: "macvlan", + Routes: []types.Route{ + {Destination: d, Gateway: g}, + }, + } + _, err := libpodNet.NetworkCreate(network, nil) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(Equal("route gateway nil")) + }) + + It("create ipvlan config with invalid static route (gw = \"foo\")", func() { + subnet := "10.1.0.0/24" + n, _ := types.ParseCIDR(subnet) + dest := "10.1.0.0/24" + gw := "foo" + d, _ := types.ParseCIDR(dest) + g := net.ParseIP(gw) + network := types.Network{ + Driver: "ipvlan", + Subnets: []types.Subnet{ + {Subnet: n}, + }, + Routes: []types.Route{ + {Destination: d, Gateway: g}, + }, + } + _, err := libpodNet.NetworkCreate(network, nil) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(Equal("route gateway nil")) + }) + + It("create bridge config with no_default_route", func() { + network := types.Network{ + Driver: "bridge", + Options: map[string]string{ + "no_default_route": "1", + }, + } + network1, err := libpodNet.NetworkCreate(network, nil) + Expect(err).To(BeNil()) + Expect(network1.Options).To(HaveLen(1)) + Expect(network1.Options["no_default_route"]).To(Equal("true")) + }) + + It("create macvlan config with no_default_route", func() { + network := types.Network{ + Driver: "macvlan", + Options: map[string]string{ + "no_default_route": "1", + }, + } + network1, err := libpodNet.NetworkCreate(network, nil) + Expect(err).To(BeNil()) + Expect(network1.Options).To(HaveLen(1)) + Expect(network1.Options["no_default_route"]).To(Equal("true")) + }) + + It("create ipvlan config with no_default_route", func() { + subnet := "10.1.0.0/24" + n, _ := types.ParseCIDR(subnet) + network := types.Network{ + Driver: "ipvlan", + Subnets: []types.Subnet{ + {Subnet: n}, + }, + Options: map[string]string{ + "no_default_route": "1", + }, + } + network1, err := libpodNet.NetworkCreate(network, nil) + Expect(err).To(BeNil()) + Expect(network1.Options).To(HaveLen(1)) + Expect(network1.Options["no_default_route"]).To(Equal("true")) + }) + + It("create bridge config with invalid no_default_route", func() { + network := types.Network{ + Driver: "bridge", + Options: map[string]string{ + "no_default_route": "foo", + }, + } + _, err := libpodNet.NetworkCreate(network, nil) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("parsing \"foo\": invalid syntax")) + }) + + It("create macvlan config with invalid no_default_route", func() { + network := types.Network{ + Driver: "macvlan", + Options: map[string]string{ + "no_default_route": "foo", + }, + } + _, err := libpodNet.NetworkCreate(network, nil) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("parsing \"foo\": invalid syntax")) + }) + + It("create ipvlan config with invalid no_default_route", func() { + subnet := "10.1.0.0/24" + n, _ := types.ParseCIDR(subnet) + network := types.Network{ + Driver: "ipvlan", + Subnets: []types.Subnet{ + {Subnet: n}, + }, + Options: map[string]string{ + "no_default_route": "foo", + }, + } + _, err := libpodNet.NetworkCreate(network, nil) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("parsing \"foo\": invalid syntax")) + }) + Context("network load valid existing ones", func() { BeforeEach(func() { dir := "testfiles/valid" diff --git a/libnetwork/types/const.go b/libnetwork/types/const.go index e367f9ad3..aa07328d6 100644 --- a/libnetwork/types/const.go +++ b/libnetwork/types/const.go @@ -41,6 +41,7 @@ const ( ModeOption = "mode" IsolateOption = "isolate" MetricOption = "metric" + NoDefaultRoute = "no_default_route" ) type NetworkBackend string diff --git a/libnetwork/types/network.go b/libnetwork/types/network.go index b8804bf6b..573dda0a1 100644 --- a/libnetwork/types/network.go +++ b/libnetwork/types/network.go @@ -50,6 +50,8 @@ type Network struct { Created time.Time `json:"created,omitempty"` // Subnets to use for this network. Subnets []Subnet `json:"subnets,omitempty"` + // Routes to use for this network. + Routes []Route `json:"routes,omitempty"` // IPv6Enabled if set to true an ipv6 subnet should be created for this net. IPv6Enabled bool `json:"ipv6_enabled"` // Internal is whether the Network should not have external routes @@ -169,6 +171,17 @@ type Subnet struct { LeaseRange *LeaseRange `json:"lease_range,omitempty"` } +type Route struct { + // Destination for this route in CIDR form. + // swagger:strfmt string + Destination IPNet `json:"destination"` + // Gateway IP for this route. + // swagger:strfmt string + Gateway net.IP `json:"gateway"` + // Metric for this route. Optional. + Metric *uint32 `json:"metric,omitempty"` +} + // LeaseRange contains the range where IP are leased. type LeaseRange struct { // StartIP first IP in the subnet which should be used to assign ips.