feat: support for route source addresses in the configuration
Fixes #3941 Also fixes route source address to be address, not a CIDR, as the Linux kernel keeps it this way actually. Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
This commit is contained in:
parent
0ef8f83acf
commit
37ea2c9ca2
@ -195,7 +195,7 @@ func (d *DHCP4) parseAck(ack *dhcpv4.DHCPv4) {
|
||||
d.routes = append(d.routes, network.RouteSpecSpec{
|
||||
Family: nethelpers.FamilyInet4,
|
||||
Destination: dst,
|
||||
Source: addr,
|
||||
Source: addr.IP(),
|
||||
Gateway: gw,
|
||||
OutLinkName: d.linkName,
|
||||
Table: nethelpers.TableMain,
|
||||
@ -213,7 +213,7 @@ func (d *DHCP4) parseAck(ack *dhcpv4.DHCPv4) {
|
||||
d.routes = append(d.routes, network.RouteSpecSpec{
|
||||
Family: nethelpers.FamilyInet4,
|
||||
Gateway: gw,
|
||||
Source: addr,
|
||||
Source: addr.IP(),
|
||||
OutLinkName: d.linkName,
|
||||
Table: nethelpers.TableMain,
|
||||
Priority: d.routeMetric,
|
||||
|
@ -219,6 +219,13 @@ func (ctrl *RouteConfigController) parseMachineConfiguration(logger *zap.Logger,
|
||||
return route, fmt.Errorf("error parsing route gateway: %w", err)
|
||||
}
|
||||
|
||||
if in.Source() != "" {
|
||||
route.Source, err = netaddr.ParseIP(in.Source())
|
||||
if err != nil {
|
||||
return route, fmt.Errorf("error parsing route source: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
route.Normalize()
|
||||
|
||||
route.Priority = in.Metric()
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/talos-systems/go-procfs/procfs"
|
||||
"github.com/talos-systems/go-retry/retry"
|
||||
"inet.af/netaddr"
|
||||
|
||||
netctrl "github.com/talos-systems/talos/internal/app/machined/pkg/controllers/network"
|
||||
"github.com/talos-systems/talos/pkg/logging"
|
||||
@ -180,6 +181,16 @@ func (suite *RouteConfigSuite) TestMachineConfiguration() {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
DeviceInterface: "eth1",
|
||||
DeviceRoutes: []*v1alpha1.Route{
|
||||
{
|
||||
RouteNetwork: "192.244.0.0/24",
|
||||
RouteGateway: "192.244.0.1",
|
||||
RouteSource: "192.244.0.10",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -200,6 +211,7 @@ func (suite *RouteConfigSuite) TestMachineConfiguration() {
|
||||
"configuration/inet6/2001:470:6d:30e:8ed2:b60c:9d2f:803b//1024",
|
||||
"configuration/inet4/10.0.3.1/10.0.3.0/24/1024",
|
||||
"configuration/inet4/192.168.0.25/192.168.0.0/18/25",
|
||||
"configuration/inet4/192.244.0.1/192.244.0.0/24/1024",
|
||||
}, func(r *network.RouteSpec) error {
|
||||
switch r.Metadata().ID() {
|
||||
case "configuration/inet6/2001:470:6d:30e:8ed2:b60c:9d2f:803b//1024":
|
||||
@ -214,6 +226,11 @@ func (suite *RouteConfigSuite) TestMachineConfiguration() {
|
||||
suite.Assert().Equal("eth3", r.TypedSpec().OutLinkName)
|
||||
suite.Assert().Equal(nethelpers.FamilyInet4, r.TypedSpec().Family)
|
||||
suite.Assert().EqualValues(25, r.TypedSpec().Priority)
|
||||
case "configuration/inet4/192.244.0.1/192.244.0.0/24/1024":
|
||||
suite.Assert().Equal("eth1", r.TypedSpec().OutLinkName)
|
||||
suite.Assert().Equal(nethelpers.FamilyInet4, r.TypedSpec().Family)
|
||||
suite.Assert().EqualValues(netctrl.DefaultRouteMetric, r.TypedSpec().Priority)
|
||||
suite.Assert().EqualValues(netaddr.MustParseIP("192.244.0.10"), r.TypedSpec().Source)
|
||||
}
|
||||
|
||||
suite.Assert().Equal(network.ConfigMachineConfiguration, r.TypedSpec().ConfigLayer)
|
||||
|
@ -201,7 +201,7 @@ func (ctrl *RouteSpecController) syncRoute(ctx context.Context, r controller.Run
|
||||
existing.Protocol == uint8(route.TypedSpec().Protocol) &&
|
||||
existing.Attributes.OutIface == linkIndex && existing.Attributes.Priority == route.TypedSpec().Priority &&
|
||||
(route.TypedSpec().Source.IsZero() ||
|
||||
existing.Attributes.Src.Equal(route.TypedSpec().Source.IP().IPAddr().IP)) {
|
||||
existing.Attributes.Src.Equal(route.TypedSpec().Source.IPAddr().IP)) {
|
||||
matchFound = true
|
||||
|
||||
continue
|
||||
@ -240,14 +240,14 @@ func (ctrl *RouteSpecController) syncRoute(ctx context.Context, r controller.Run
|
||||
msg := &rtnetlink.RouteMessage{
|
||||
Family: uint8(route.TypedSpec().Family),
|
||||
DstLength: route.TypedSpec().Destination.Bits(),
|
||||
SrcLength: route.TypedSpec().Source.Bits(),
|
||||
SrcLength: 0,
|
||||
Protocol: uint8(route.TypedSpec().Protocol),
|
||||
Scope: uint8(route.TypedSpec().Scope),
|
||||
Type: uint8(route.TypedSpec().Type),
|
||||
Flags: uint32(route.TypedSpec().Flags),
|
||||
Attributes: rtnetlink.RouteAttributes{
|
||||
Dst: route.TypedSpec().Destination.IP().IPAddr().IP,
|
||||
Src: route.TypedSpec().Source.IP().IPAddr().IP,
|
||||
Src: route.TypedSpec().Source.IPAddr().IP,
|
||||
Gateway: route.TypedSpec().Gateway.IPAddr().IP,
|
||||
OutIface: linkIndex,
|
||||
Priority: route.TypedSpec().Priority,
|
||||
|
@ -292,7 +292,7 @@ func (suite *RouteSpecSuite) TestDefaultAndInterfaceRoutes() {
|
||||
Family: nethelpers.FamilyInet4,
|
||||
Destination: netaddr.IPPrefix{},
|
||||
Gateway: netaddr.MustParseIP("10.28.0.1"),
|
||||
Source: netaddr.MustParseIPPrefix("10.28.0.27/32"),
|
||||
Source: netaddr.MustParseIP("10.28.0.27"),
|
||||
Table: nethelpers.TableMain,
|
||||
OutLinkName: dummyInterface,
|
||||
Protocol: nethelpers.ProtocolStatic,
|
||||
@ -307,7 +307,7 @@ func (suite *RouteSpecSuite) TestDefaultAndInterfaceRoutes() {
|
||||
Family: nethelpers.FamilyInet4,
|
||||
Destination: netaddr.MustParseIPPrefix("10.28.0.1/32"),
|
||||
Gateway: netaddr.MustParseIP("0.0.0.0"),
|
||||
Source: netaddr.MustParseIPPrefix("10.28.0.27/32"),
|
||||
Source: netaddr.MustParseIP("10.28.0.27"),
|
||||
Table: nethelpers.TableMain,
|
||||
OutLinkName: dummyInterface,
|
||||
Protocol: nethelpers.ProtocolStatic,
|
||||
@ -414,7 +414,7 @@ func (suite *RouteSpecSuite) TestLinkLocalRoute() {
|
||||
Family: nethelpers.FamilyInet4,
|
||||
Destination: netaddr.MustParseIPPrefix("169.254.169.254/32"),
|
||||
Gateway: netaddr.MustParseIP("10.28.0.1"),
|
||||
Source: netaddr.MustParseIPPrefix("10.28.0.27/24"),
|
||||
Source: netaddr.MustParseIP("10.28.0.27"),
|
||||
Table: nethelpers.TableMain,
|
||||
OutLinkName: dummyInterface,
|
||||
Protocol: nethelpers.ProtocolStatic,
|
||||
|
@ -103,7 +103,6 @@ func (ctrl *RouteStatusController) Run(ctx context.Context, r controller.Runtime
|
||||
dstAddr, _ := netaddr.FromStdIPRaw(route.Attributes.Dst)
|
||||
dstPrefix := netaddr.IPPrefixFrom(dstAddr, route.DstLength)
|
||||
srcAddr, _ := netaddr.FromStdIPRaw(route.Attributes.Src)
|
||||
srcPrefix := netaddr.IPPrefixFrom(srcAddr, route.SrcLength)
|
||||
gatewayAddr, _ := netaddr.FromStdIPRaw(route.Attributes.Gateway)
|
||||
id := network.RouteID(nethelpers.RoutingTable(route.Table), nethelpers.Family(route.Family), dstPrefix, gatewayAddr, route.Attributes.Priority)
|
||||
|
||||
@ -112,7 +111,7 @@ func (ctrl *RouteStatusController) Run(ctx context.Context, r controller.Runtime
|
||||
|
||||
status.Family = nethelpers.Family(route.Family)
|
||||
status.Destination = dstPrefix
|
||||
status.Source = srcPrefix
|
||||
status.Source = srcAddr
|
||||
status.Gateway = gatewayAddr
|
||||
status.OutLinkIndex = route.Attributes.OutIface
|
||||
status.OutLinkName = linkLookup[route.Attributes.OutIface]
|
||||
|
@ -100,7 +100,7 @@ func (suite *RouteStatusSuite) TestRoutes() {
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertRoutes([]string{"local/inet4//127.0.0.0/8/0"}, func(r *network.RouteStatus) error {
|
||||
suite.Assert().True(r.TypedSpec().Source.IP().IsLoopback())
|
||||
suite.Assert().True(r.TypedSpec().Source.IsLoopback())
|
||||
suite.Assert().Equal("lo", r.TypedSpec().OutLinkName)
|
||||
suite.Assert().Equal(nethelpers.TableLocal, r.TypedSpec().Table)
|
||||
suite.Assert().Equal(nethelpers.ScopeHost, r.TypedSpec().Scope)
|
||||
|
@ -203,6 +203,7 @@ type Vlan interface {
|
||||
type Route interface {
|
||||
Network() string
|
||||
Gateway() string
|
||||
Source() string
|
||||
Metric() uint32
|
||||
}
|
||||
|
||||
|
@ -682,6 +682,11 @@ func (r *Route) Gateway() string {
|
||||
return r.RouteGateway
|
||||
}
|
||||
|
||||
// Source implements the MachineNetwork interface.
|
||||
func (r *Route) Source() string {
|
||||
return r.RouteSource
|
||||
}
|
||||
|
||||
// Metric implements the MachineNetwork interface.
|
||||
func (r *Route) Metric() uint32 {
|
||||
return r.RouteMetric
|
||||
|
@ -1758,6 +1758,8 @@ type Route struct {
|
||||
RouteNetwork string `yaml:"network"`
|
||||
// description: The route's gateway.
|
||||
RouteGateway string `yaml:"gateway"`
|
||||
// description: The route's source address (optional).
|
||||
RouteSource string `yaml:"source,omitempty"`
|
||||
// description: The optional metric for the route.
|
||||
RouteMetric uint32 `yaml:"metric,omitempty"`
|
||||
}
|
||||
|
@ -1711,7 +1711,7 @@ func init() {
|
||||
FieldName: "routes",
|
||||
},
|
||||
}
|
||||
RouteDoc.Fields = make([]encoder.Doc, 3)
|
||||
RouteDoc.Fields = make([]encoder.Doc, 4)
|
||||
RouteDoc.Fields[0].Name = "network"
|
||||
RouteDoc.Fields[0].Type = "string"
|
||||
RouteDoc.Fields[0].Note = ""
|
||||
@ -1722,11 +1722,16 @@ func init() {
|
||||
RouteDoc.Fields[1].Note = ""
|
||||
RouteDoc.Fields[1].Description = "The route's gateway."
|
||||
RouteDoc.Fields[1].Comments[encoder.LineComment] = "The route's gateway."
|
||||
RouteDoc.Fields[2].Name = "metric"
|
||||
RouteDoc.Fields[2].Type = "uint32"
|
||||
RouteDoc.Fields[2].Name = "source"
|
||||
RouteDoc.Fields[2].Type = "string"
|
||||
RouteDoc.Fields[2].Note = ""
|
||||
RouteDoc.Fields[2].Description = "The optional metric for the route."
|
||||
RouteDoc.Fields[2].Comments[encoder.LineComment] = "The optional metric for the route."
|
||||
RouteDoc.Fields[2].Description = "The route's source address (optional)."
|
||||
RouteDoc.Fields[2].Comments[encoder.LineComment] = "The route's source address (optional)."
|
||||
RouteDoc.Fields[3].Name = "metric"
|
||||
RouteDoc.Fields[3].Type = "uint32"
|
||||
RouteDoc.Fields[3].Note = ""
|
||||
RouteDoc.Fields[3].Description = "The optional metric for the route."
|
||||
RouteDoc.Fields[3].Comments[encoder.LineComment] = "The optional metric for the route."
|
||||
|
||||
RegistryMirrorConfigDoc.Type = "RegistryMirrorConfig"
|
||||
RegistryMirrorConfigDoc.Comments[encoder.LineComment] = "RegistryMirrorConfig represents mirror configuration for a registry."
|
||||
|
@ -592,12 +592,18 @@ func CheckDeviceRoutes(d *Device, bondedInterfaces map[string]string) ([]string,
|
||||
for idx, route := range d.DeviceRoutes {
|
||||
if route.Network() != "" {
|
||||
if _, _, err := net.ParseCIDR(route.Network()); err != nil {
|
||||
result = multierror.Append(result, fmt.Errorf("[%s] %q: %w", "networking.os.device.route["+strconv.Itoa(idx)+"].Network", route.Network(), ErrInvalidAddress))
|
||||
result = multierror.Append(result, fmt.Errorf("[%s] %q: %w", "networking.os.device.route["+strconv.Itoa(idx)+"].network", route.Network(), ErrInvalidAddress))
|
||||
}
|
||||
}
|
||||
|
||||
if ip := net.ParseIP(route.Gateway()); ip == nil {
|
||||
result = multierror.Append(result, fmt.Errorf("[%s] %q: %w", "networking.os.device.route["+strconv.Itoa(idx)+"].Gateway", route.Gateway(), ErrInvalidAddress))
|
||||
result = multierror.Append(result, fmt.Errorf("[%s] %q: %w", "networking.os.device.route["+strconv.Itoa(idx)+"].gateway", route.Gateway(), ErrInvalidAddress))
|
||||
}
|
||||
|
||||
if route.Source() != "" {
|
||||
if ip := net.ParseIP(route.Source()); ip == nil {
|
||||
result = multierror.Append(result, fmt.Errorf("[%s] %q: %w", "networking.os.device.route["+strconv.Itoa(idx)+"].source", route.Source(), ErrInvalidAddress))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -672,6 +672,58 @@ func TestValidate(t *testing.T) {
|
||||
expectedError: "3 errors occurred:\n\t* public key invalid: wrong key \"\" length: 0\n\t* public key invalid: wrong key \"4A3rogGVHuVjeZz5cbqryWXGkGBdIGC0E6+5mX2Iz1==\" length: 31\n" +
|
||||
"\t* peer allowed IP \"10.2.0\" is invalid: invalid CIDR address: 10.2.0\n\n",
|
||||
},
|
||||
{
|
||||
name: "StaticRoutes",
|
||||
config: &v1alpha1.Config{
|
||||
ConfigVersion: "v1alpha1",
|
||||
MachineConfig: &v1alpha1.MachineConfig{
|
||||
MachineType: "controlplane",
|
||||
MachineNetwork: &v1alpha1.NetworkConfig{
|
||||
NetworkInterfaces: []*v1alpha1.Device{
|
||||
{
|
||||
DeviceInterface: "eth0",
|
||||
DeviceRoutes: []*v1alpha1.Route{
|
||||
{
|
||||
RouteGateway: "172.0.0.1",
|
||||
},
|
||||
{
|
||||
RouteNetwork: "10.0.0.0/24",
|
||||
RouteGateway: "10.0.0.1",
|
||||
},
|
||||
{
|
||||
RouteNetwork: "10.0.0.0/24",
|
||||
RouteGateway: "10.0.0.1",
|
||||
RouteSource: "10.0.0.5",
|
||||
},
|
||||
{
|
||||
RouteGateway: "172.0.0.x",
|
||||
},
|
||||
{
|
||||
RouteNetwork: "10.0.0.0",
|
||||
RouteGateway: "10.0.0.1",
|
||||
},
|
||||
{
|
||||
RouteNetwork: "10.0.0.0/24",
|
||||
RouteGateway: "10.0.0.1",
|
||||
RouteSource: "10.0.0.3/32",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ClusterConfig: &v1alpha1.ClusterConfig{
|
||||
ControlPlane: &v1alpha1.ControlPlaneConfig{
|
||||
Endpoint: &v1alpha1.Endpoint{
|
||||
endpointURL,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: "3 errors occurred:\n\t* [networking.os.device.route[3].gateway] \"172.0.0.x\": invalid network address\n" +
|
||||
"\t* [networking.os.device.route[4].network] \"10.0.0.0\": invalid network address\n" +
|
||||
"\t* [networking.os.device.route[5].source] \"10.0.0.3/32\": invalid network address\n\n",
|
||||
},
|
||||
} {
|
||||
test := test
|
||||
|
||||
|
@ -27,7 +27,7 @@ type RouteSpec struct {
|
||||
type RouteSpecSpec struct {
|
||||
Family nethelpers.Family `yaml:"family"`
|
||||
Destination netaddr.IPPrefix `yaml:"dst"`
|
||||
Source netaddr.IPPrefix `yaml:"src"`
|
||||
Source netaddr.IP `yaml:"src"`
|
||||
Gateway netaddr.IP `yaml:"gateway"`
|
||||
OutLinkName string `yaml:"outLinkName,omitempty"`
|
||||
Table nethelpers.RoutingTable `yaml:"table"`
|
||||
@ -45,8 +45,6 @@ var (
|
||||
)
|
||||
|
||||
// Normalize converts 0.0.0.0 to zero value.
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func (route *RouteSpecSpec) Normalize() {
|
||||
if route.Destination.Bits() == 0 && (route.Destination.IP().Compare(zero4) == 0 || route.Destination.IP().Compare(zero16) == 0) {
|
||||
// clear destination to be zero value to support "0.0.0.0/0" routes
|
||||
@ -57,8 +55,8 @@ func (route *RouteSpecSpec) Normalize() {
|
||||
route.Gateway = netaddr.IP{}
|
||||
}
|
||||
|
||||
if route.Source.Bits() == 0 && (route.Source.IP().Compare(zero4) == 0 || route.Source.IP().Compare(zero16) == 0) {
|
||||
route.Source = netaddr.IPPrefix{}
|
||||
if route.Source.Compare(zero4) == 0 || route.Source.Compare(zero16) == 0 {
|
||||
route.Source = netaddr.IP{}
|
||||
}
|
||||
|
||||
switch {
|
||||
|
@ -27,7 +27,7 @@ type RouteStatus struct {
|
||||
type RouteStatusSpec struct {
|
||||
Family nethelpers.Family `yaml:"family"`
|
||||
Destination netaddr.IPPrefix `yaml:"dst"`
|
||||
Source netaddr.IPPrefix `yaml:"src"`
|
||||
Source netaddr.IP `yaml:"src"`
|
||||
Gateway netaddr.IP `yaml:"gateway"`
|
||||
OutLinkIndex uint32 `yaml:"outLinkIndex,omitempty"`
|
||||
OutLinkName string `yaml:"outLinkName,omitempty"`
|
||||
|
@ -4566,6 +4566,18 @@ The route's gateway.
|
||||
<hr />
|
||||
<div class="dd">
|
||||
|
||||
<code>source</code> <i>string</i>
|
||||
|
||||
</div>
|
||||
<div class="dt">
|
||||
|
||||
The route's source address (optional).
|
||||
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
<div class="dd">
|
||||
|
||||
<code>metric</code> <i>uint32</i>
|
||||
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user