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:
Andrey Smirnov 2021-08-13 22:54:34 +03:00
parent 0ef8f83acf
commit 37ea2c9ca2
No known key found for this signature in database
GPG Key ID: 7B26396447AB6DFD
16 changed files with 128 additions and 24 deletions

View File

@ -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,

View File

@ -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()

View File

@ -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)

View File

@ -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,

View File

@ -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,

View File

@ -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]

View File

@ -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)

View File

@ -203,6 +203,7 @@ type Vlan interface {
type Route interface {
Network() string
Gateway() string
Source() string
Metric() uint32
}

View File

@ -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

View File

@ -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"`
}

View File

@ -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."

View File

@ -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))
}
}
}

View File

@ -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

View File

@ -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 {

View File

@ -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"`

View File

@ -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>