From 307397c0b31f09ec445ee16fbc3893b2fb9dc36e Mon Sep 17 00:00:00 2001 From: Paul Holzinger Date: Wed, 29 Apr 2026 13:11:51 +0200 Subject: [PATCH 1/2] libnetwork: add route type field Added in netavark in https://github.com/containers/netavark/pull/1417 Needed for https://github.com/containers/podman/pull/28230 The invalid gw test was somewhat broken, as the go type uses net.IP the parsing of the ip failed in the test and the ip was just left nil. This means the json marshal just sends an empty string to netavark and well that failed parsing in NV then. However now with the route type the gateway is optional an omitempty and therefore no longer is send to NV. Instead NV will no fail with a different error about the missing gateway for unicast routes. But because the other test checks that once already I rather remove the checks here as they do not really add value to do this for each network driver. Signed-off-by: Paul Holzinger --- common/libnetwork/netavark/config_test.go | 83 ++++++++--------------- common/libnetwork/types/network.go | 20 +++++- 2 files changed, 48 insertions(+), 55 deletions(-) diff --git a/common/libnetwork/netavark/config_test.go b/common/libnetwork/netavark/config_test.go index 45e301ccda..e26396319e 100644 --- a/common/libnetwork/netavark/config_test.go +++ b/common/libnetwork/netavark/config_test.go @@ -1817,59 +1817,6 @@ var _ = Describe("Config", func() { Expect(err.Error()).To(ContainSubstring("failed to load network create: IO error: invalid IP address syntax")) }) - 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(ContainSubstring("invalid IP address syntax")) - }) - - 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(ContainSubstring("invalid IP address syntax")) - }) - - 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(ContainSubstring("invalid IP address syntax")) - }) - It("create macvlan config with metric option", func() { network := types.Network{ Driver: "macvlan", @@ -1968,6 +1915,36 @@ var _ = Describe("Config", func() { Expect(err.Error()).To(ContainSubstring("unable to parse \"no_default_route\"")) }) + It("create bridge custom route type route", func() { + dest := "10.1.0.0/24" + d, _ := types.ParseCIDR(dest) + for _, routeType := range []types.RouteType{types.RouteTypeBlackhole, types.RouteTypeProhibit, types.RouteTypeUnreachable} { + network := types.Network{ + Driver: "bridge", + Routes: []types.Route{ + {Destination: d, RouteType: routeType}, + }, + } + network1, err := libpodNet.NetworkCreate(network, nil) + Expect(err).ToNot(HaveOccurred()) + Expect(network1.Name).ToNot(BeEmpty()) + Expect(network1.Routes).To(HaveLen(1)) + Expect(network1.Routes[0].Destination.String()).To(Equal(dest)) + Expect(network1.Routes[0].RouteType).To(Equal(routeType)) + } + + // without gateway a unicast route should fail + network := types.Network{ + Driver: "bridge", + Routes: []types.Route{ + {Destination: d, RouteType: types.RouteTypeUnicast}, + }, + } + _, err := libpodNet.NetworkCreate(network, nil) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("route 10.1.0.0/24 requires a gateway for type unicast")) + }) + Context("network load valid existing ones", func() { BeforeEach(func() { dir := "testfiles/valid" diff --git a/common/libnetwork/types/network.go b/common/libnetwork/types/network.go index 1878bea6b4..11ba341355 100644 --- a/common/libnetwork/types/network.go +++ b/common/libnetwork/types/network.go @@ -201,15 +201,31 @@ type Subnet struct { LeaseRange *LeaseRange `json:"lease_range,omitempty"` } +// RouteType represents the type of a route. +type RouteType string + +const ( + // RouteTypeUnicast is a regular route with a gateway (default). + RouteTypeUnicast RouteType = "unicast" + // RouteTypeBlackhole silently discards packets. + RouteTypeBlackhole RouteType = "blackhole" + // RouteTypeUnreachable rejects with "destination unreachable". + RouteTypeUnreachable RouteType = "unreachable" + // RouteTypeProhibit rejects with "administratively prohibited". + RouteTypeProhibit RouteType = "prohibit" +) + type Route struct { // Destination for this route in CIDR form. // swagger:strfmt string Destination IPNet `json:"destination"` - // Gateway IP for this route. + // Gateway IP for this route. Required for unicast routes, must be empty for blackhole/unreachable/prohibit. // swagger:strfmt string - Gateway net.IP `json:"gateway"` + Gateway net.IP `json:"gateway,omitempty"` // Metric for this route. Optional. Metric *uint32 `json:"metric,omitempty"` + // RouteType is the type of route: unicast (default), blackhole, unreachable, prohibit. + RouteType RouteType `json:"route_type,omitempty"` } // LeaseRange contains the range where IP are leased. From 90ef8062d390fcee2fd6b35aca73cba681d19b95 Mon Sep 17 00:00:00 2001 From: Paul Holzinger Date: Wed, 29 Apr 2026 16:04:05 +0200 Subject: [PATCH 2/2] libnetwork: remove some unused code Signed-off-by: Paul Holzinger --- common/libnetwork/internal/util/validate.go | 59 --------------------- 1 file changed, 59 deletions(-) diff --git a/common/libnetwork/internal/util/validate.go b/common/libnetwork/internal/util/validate.go index 011a101ac6..adf04dd726 100644 --- a/common/libnetwork/internal/util/validate.go +++ b/common/libnetwork/internal/util/validate.go @@ -4,8 +4,6 @@ import ( "errors" "fmt" "net" - "strings" - "unicode" "go.podman.io/common/libnetwork/types" "go.podman.io/common/libnetwork/util" @@ -98,43 +96,6 @@ 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 errors.New("route destination ip nil") - } - - if route.Destination.Mask == nil { - return errors.New("route destination mask nil") - } - - if route.Gateway == nil { - return errors.New("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 errors.New("route destination invalid") - } - - return nil -} - func ValidateSetupOptions(n NetUtil, namespacePath string, options types.SetupOptions) error { if namespacePath == "" { return errors.New("namespacePath is empty") @@ -176,23 +137,3 @@ func validatePerNetworkOpts(network *types.Network, netOpts *types.PerNetworkOpt } return nil } - -// ValidateInterfaceName validates the interface name based on the following rules: -// 1. The name must be less than MaxInterfaceNameLength characters -// 2. The name must not be "." or ".." -// 3. The name must not contain / or : or any whitespace characters -// ref to https://github.com/torvalds/linux/blob/81e4f8d68c66da301bb881862735bd74c6241a19/include/uapi/linux/if.h#L33C18-L33C20 -func ValidateInterfaceName(ifName string) error { - if len(ifName) > types.MaxInterfaceNameLength { - return fmt.Errorf("interface name is too long: interface names must be %d characters or less: %w", types.MaxInterfaceNameLength, types.ErrInvalidArg) - } - if ifName == "." || ifName == ".." { - return fmt.Errorf("interface name is . or ..: %w", types.ErrInvalidArg) - } - if strings.ContainsFunc(ifName, func(r rune) bool { - return r == '/' || r == ':' || unicode.IsSpace(r) - }) { - return fmt.Errorf("interface name contains / or : or whitespace characters: %w", types.ErrInvalidArg) - } - return nil -}