refactor(networkd): Switch from rtnetlink to rtnl

Gives a better abstraction on rtnetlink interaction

Signed-off-by: Brad Beam <brad.beam@talos-systems.com>
This commit is contained in:
Brad Beam 2019-08-19 12:11:50 -05:00 committed by Brad Beam
parent 313c118ad0
commit cdc989ddda
15 changed files with 295 additions and 497 deletions

View File

@ -223,22 +223,22 @@ steps:
depends_on:
- rootfs
#- name: lint
# image: autonomy/build-container:latest
# commands:
# - make lint
# environment:
# BINDIR: /usr/local/bin
# BUILDKIT_HOST: ${BUILDKIT_HOST=tcp://buildkitd.ci.svc:1234}
# volumes:
# - name: dockersock
# path: /var/run
# - name: dev
# path: /dev
# - name: tmp
# path: /tmp
# depends_on:
# - clone
- name: lint
image: autonomy/build-container:latest
commands:
- make lint
environment:
BINDIR: /usr/local/bin
BUILDKIT_HOST: ${BUILDKIT_HOST=tcp://buildkitd.ci.svc:1234}
volumes:
- name: dockersock
path: /var/run
- name: dev
path: /dev
- name: tmp
path: /tmp
depends_on:
- clone
- name: markdownlint
image: autonomy/build-container:latest
@ -614,22 +614,22 @@ steps:
depends_on:
- rootfs
#- name: lint
# image: autonomy/build-container:latest
# commands:
# - make lint
# environment:
# BINDIR: /usr/local/bin
# BUILDKIT_HOST: ${BUILDKIT_HOST=tcp://buildkitd.ci.svc:1234}
# volumes:
# - name: dockersock
# path: /var/run
# - name: dev
# path: /dev
# - name: tmp
# path: /tmp
# depends_on:
# - clone
- name: lint
image: autonomy/build-container:latest
commands:
- make lint
environment:
BINDIR: /usr/local/bin
BUILDKIT_HOST: ${BUILDKIT_HOST=tcp://buildkitd.ci.svc:1234}
volumes:
- name: dockersock
path: /var/run
- name: dev
path: /dev
- name: tmp
path: /tmp
depends_on:
- clone
- name: markdownlint
image: autonomy/build-container:latest
@ -1141,22 +1141,22 @@ steps:
depends_on:
- rootfs
#- name: lint
# image: autonomy/build-container:latest
# commands:
# - make lint
# environment:
# BINDIR: /usr/local/bin
# BUILDKIT_HOST: ${BUILDKIT_HOST=tcp://buildkitd.ci.svc:1234}
# volumes:
# - name: dockersock
# path: /var/run
# - name: dev
# path: /dev
# - name: tmp
# path: /tmp
# depends_on:
# - clone
- name: lint
image: autonomy/build-container:latest
commands:
- make lint
environment:
BINDIR: /usr/local/bin
BUILDKIT_HOST: ${BUILDKIT_HOST=tcp://buildkitd.ci.svc:1234}
volumes:
- name: dockersock
path: /var/run
- name: dev
path: /dev
- name: tmp
path: /tmp
depends_on:
- clone
- name: markdownlint
image: autonomy/build-container:latest
@ -1670,22 +1670,22 @@ steps:
depends_on:
- rootfs
#- name: lint
# image: autonomy/build-container:latest
# commands:
# - make lint
# environment:
# BINDIR: /usr/local/bin
# BUILDKIT_HOST: ${BUILDKIT_HOST=tcp://buildkitd.ci.svc:1234}
# volumes:
# - name: dockersock
# path: /var/run
# - name: dev
# path: /dev
# - name: tmp
# path: /tmp
# depends_on:
# - clone
- name: lint
image: autonomy/build-container:latest
commands:
- make lint
environment:
BINDIR: /usr/local/bin
BUILDKIT_HOST: ${BUILDKIT_HOST=tcp://buildkitd.ci.svc:1234}
volumes:
- name: dockersock
path: /var/run
- name: dev
path: /dev
- name: tmp
path: /tmp
depends_on:
- clone
- name: markdownlint
image: autonomy/build-container:latest
@ -2199,22 +2199,22 @@ steps:
depends_on:
- rootfs
#- name: lint
# image: autonomy/build-container:latest
# commands:
# - make lint
# environment:
# BINDIR: /usr/local/bin
# BUILDKIT_HOST: ${BUILDKIT_HOST=tcp://buildkitd.ci.svc:1234}
# volumes:
# - name: dockersock
# path: /var/run
# - name: dev
# path: /dev
# - name: tmp
# path: /tmp
# depends_on:
# - clone
- name: lint
image: autonomy/build-container:latest
commands:
- make lint
environment:
BINDIR: /usr/local/bin
BUILDKIT_HOST: ${BUILDKIT_HOST=tcp://buildkitd.ci.svc:1234}
volumes:
- name: dockersock
path: /var/run
- name: dev
path: /dev
- name: tmp
path: /tmp
depends_on:
- clone
- name: markdownlint
image: autonomy/build-container:latest

2
go.mod
View File

@ -2,6 +2,8 @@ module github.com/talos-systems/talos
go 1.12
replace github.com/jsimonetti/rtnetlink => github.com/bradbeam/rtnetlink v0.0.0-20190820045831-7b9ca088b93d
require (
code.cloudfoundry.org/bytefmt v0.0.0-20180906201452-2aa6f33b730c
github.com/beevik/ntp v0.2.0

4
go.sum
View File

@ -42,6 +42,8 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce
github.com/bifurcation/mint v0.0.0-20180715133206-93c51c6ce115/go.mod h1:zVt7zX3K/aDCk9Tj+VM7YymsX66ERvzCJzw8rFCX2JU=
github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/bradbeam/rtnetlink v0.0.0-20190820045831-7b9ca088b93d h1:w82c4zYkkMi98stDhWV5EB/+Pt6q8eyMzmtnzwy1HSs=
github.com/bradbeam/rtnetlink v0.0.0-20190820045831-7b9ca088b93d/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
github.com/caddyserver/caddy v1.0.1/go.mod h1:G+ouvOY32gENkJC+jhgl62TyhvqEsFaDiZ4uw0RzP1E=
github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/cespare/prettybench v0.0.0-20150116022406-03b8cfe5406c/go.mod h1:Xe6ZsFhtM8HrDku0pxJ3/Lr51rwykrzgFwpmTzleatY=
@ -271,8 +273,6 @@ github.com/jimstudt/http-authentication v0.0.0-20140401203705-3eca13d6893a/go.mo
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a h1:84IpUNXj4mCR9CuCEvSiCArMbzr/TMbuPIadKDwypkI=
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=

View File

@ -44,7 +44,7 @@ func (task *UserDefinedNetwork) runtime(platform platform.Platform, data *userda
}
// Convert links to nic
log.Println("Discovering local network interfaces")
log.Println("discovering local network interfaces")
netconf, err := nwd.Discover()
if err != nil {
return err
@ -53,9 +53,8 @@ func (task *UserDefinedNetwork) runtime(platform platform.Platform, data *userda
// Configure specified interface
netIfaces := make([]*nic.NetworkInterface, 0, len(netconf))
var iface *nic.NetworkInterface
for name, opts := range netconf {
log.Printf("Creating interface %s", name)
iface, err = nic.Create(opts...)
for link, opts := range netconf {
iface, err = nic.Create(link, opts...)
if err != nil {
return err
}
@ -65,7 +64,6 @@ func (task *UserDefinedNetwork) runtime(platform platform.Platform, data *userda
// kick off the addressing mechanism
// Add any necessary routes
log.Println("Configuring interface addressing")
if err = nwd.Configure(netIfaces...); err != nil {
return err
}
@ -76,6 +74,7 @@ func (task *UserDefinedNetwork) runtime(platform platform.Platform, data *userda
// 2. Kernel Arg
// 3. DHCP response
// 4. failsafe - talos-<ip addr>
// default specified in etc.Hosts()
var hostname string
kernelHostname := kernel.ProcCmdline().Get(constants.KernelParamHostname).First()
switch {
@ -83,10 +82,8 @@ func (task *UserDefinedNetwork) runtime(platform platform.Platform, data *userda
hostname = data.Networking.OS.Hostname
case kernelHostname != nil:
hostname = *kernelHostname
case nwd.Hostname() != "":
case nwd.Hostname(netIfaces...) != "":
hostname = nwd.Hostname(netIfaces...)
default:
// will default in Hosts()
}
return etc.Hosts(hostname)

View File

@ -40,9 +40,9 @@ func (task *Services) startSystemServices(data *userdata.UserData, mode runtime.
// Start the services common to all nodes.
svcs.Load(
&services.MachinedAPI{},
&services.Networkd{},
&services.SystemContainerd{},
&services.Containerd{},
&services.Networkd{},
&services.Udevd{},
&services.OSD{},
&services.NTPd{},

View File

@ -31,7 +31,8 @@ func (n *Networkd) ID(data *userdata.UserData) string {
// PreFunc implements the Service interface.
func (n *Networkd) PreFunc(ctx context.Context, data *userdata.UserData) error {
return containerd.Import(constants.SystemContainerdNamespace, &containerd.ImportRequest{
importer := containerd.NewImporter(constants.SystemContainerdNamespace, containerd.WithContainerdAddress(constants.SystemContainerdAddress))
return importer.Import(&containerd.ImportRequest{
Path: "/usr/images/networkd.tar",
Options: []containerdapi.ImportOpt{
containerdapi.WithIndexName("talos/networkd"),
@ -51,7 +52,7 @@ func (n *Networkd) Condition(data *userdata.UserData) conditions.Condition {
// DependsOn implements the Service interface.
func (n *Networkd) DependsOn(data *userdata.UserData) []string {
return []string{"containerd"}
return []string{"system-containerd"}
}
func (n *Networkd) Runner(data *userdata.UserData) (runner.Runner, error) {
@ -65,6 +66,7 @@ func (n *Networkd) Runner(data *userdata.UserData) (runner.Runner, error) {
mounts := []specs.Mount{
{Type: "bind", Destination: constants.UserDataPath, Source: constants.UserDataPath, Options: []string{"rbind", "ro"}},
{Type: "bind", Destination: "/etc/resolv.conf", Source: "/etc/resolv.conf", Options: []string{"rbind", "rw"}},
{Type: "bind", Destination: "/etc/hosts", Source: "/etc/hosts", Options: []string{"rbind", "rw"}},
}
env := []string{}
@ -75,9 +77,11 @@ func (n *Networkd) Runner(data *userdata.UserData) (runner.Runner, error) {
return restart.New(containerd.NewRunner(
data,
&args,
runner.WithContainerdAddress(constants.SystemContainerdAddress),
runner.WithContainerImage(image),
runner.WithEnv(env),
runner.WithOCISpecOpts(
containerd.WithMemoryLimit(int64(1000000*32)),
oci.WithMounts(mounts),
),
),

View File

@ -27,12 +27,8 @@ func main() {
log.Fatal(err)
}
// Is there any value in returning the low level link or
// should the translation to nic be handled here?
// links := nwd.Discover()
// Convert links to nic
log.Println("Discovering local network interfaces")
log.Println("discovering local network interfaces")
netconf, err = nwd.Discover()
if err != nil {
log.Fatal(err)
@ -44,7 +40,7 @@ func main() {
log.Printf("failed to read userdata %s, using defaults: %+v", "/var/userdata.yaml", err)
}
log.Println("Overlaying userdata network configuration")
log.Println("overlaying userdata network configuration")
// Update nic with userdata specified options
if err = netconf.OverlayUserData(ud); err != nil {
log.Fatal(err)
@ -52,10 +48,10 @@ func main() {
// Configure specified interface
netIfaces := make([]*nic.NetworkInterface, 0, len(netconf))
for name, opts := range netconf {
for link, opts := range netconf {
var iface *nic.NetworkInterface
log.Printf("Creating interface %s", name)
iface, err = nic.Create(opts...)
log.Printf("creating interface %s", link.Name)
iface, err = nic.Create(link, opts...)
if err != nil {
log.Fatal(err)
}
@ -65,11 +61,15 @@ func main() {
// kick off the addressing mechanism
// Add any necessary routes
log.Println("Configuring interface addressing")
log.Println("configuring interface addressing")
if err = nwd.Configure(netIfaces...); err != nil {
log.Fatal(err)
}
log.Println("interface configuration")
nwd.PrintState()
log.Println("starting renewal watcher")
// handle dhcp renewal
nwd.Renew(netIfaces...)
}

View File

@ -6,14 +6,10 @@ package address
import (
"context"
"encoding/binary"
"net"
"sort"
"time"
"github.com/insomniacslk/dhcp/dhcpv4"
"github.com/jsimonetti/rtnetlink"
"golang.org/x/sys/unix"
)
// Addressing provides an interface for abstracting the underlying network
@ -21,128 +17,18 @@ import (
// supported.
type Addressing interface {
Name() string
Discover(context.Context, string) error
Address() net.IP
Discover(context.Context) error
Address() *net.IPNet
Mask() net.IPMask
MTU() uint32
TTL() time.Duration
Family() uint8
Family() int
Scope() uint8
Routes() []*Route
Resolvers() []net.IP
Hostname() string
Link() *net.Interface
}
// Route is a representation of a network route
type Route = dhcpv4.Route
// AddressMessage generates a rtnetlink.AddressMessage from the underlying
// Addressing implementation. This message will be used to set the network
// interface address.
// nolint: golint
func AddressMessage(method Addressing, idx uint32) *rtnetlink.AddressMessage {
attrs := rtnetlink.AddressAttributes{
Address: method.Address(),
}
// AF_INET / ipv4 requires some additional configuration ( broadcast addr )
if method.Family() == unix.AF_INET {
brd := make(net.IP, len(method.Address()))
binary.BigEndian.PutUint32(brd, binary.BigEndian.Uint32(method.Address())|^binary.BigEndian.Uint32(method.Mask()))
attrs.Broadcast = brd
attrs.Local = method.Address()
}
ones, _ := method.Mask().Size()
// TODO: look at setting dynamic/permanent flag on address
// TODO: look at setting valid lifetime and preferred lifetime
// Ref for scope configuration
// https://elixir.bootlin.com/linux/latest/source/net/ipv4/fib_semantics.c#L919
// https://unix.stackexchange.com/questions/123084/what-is-the-interface-scope-global-vs-link-used-for
msg := &rtnetlink.AddressMessage{
Family: method.Family(),
PrefixLength: uint8(ones),
Scope: method.Scope(),
Index: idx,
Attributes: attrs,
}
return msg
}
// RouteMessage generates a slice of rtnetlink.RouteMessages from the
// underlying Addressing implementation. These messages will be used to set
// up the routing table.
func RouteMessage(method Addressing, idx uint32) []*rtnetlink.RouteMessage {
routes := make([]*rtnetlink.RouteMessage, 0, len(method.Routes()))
var protocol uint8
switch method.(type) {
case *DHCP:
protocol = unix.RTPROT_DHCP
case *Static:
protocol = unix.RTPROT_STATIC
}
for _, route := range method.Routes() {
attr := rtnetlink.RouteAttributes{
OutIface: idx,
Table: unix.RT_TABLE_MAIN,
}
// Default to scope_link
var scope uint8
switch {
case route.Dest == nil:
// If no dest set, assume gateway
scope = unix.RT_SCOPE_UNIVERSE
case route.Dest.IP.Equal(net.IPv4zero):
scope = unix.RT_SCOPE_UNIVERSE
default:
scope = unix.RT_SCOPE_LINK
}
routeMsg := &rtnetlink.RouteMessage{
Family: method.Family(),
Table: unix.RT_TABLE_MAIN,
Protocol: protocol,
Scope: scope,
Type: unix.RTN_UNICAST,
}
if route.Dest != nil {
attr.Dst = route.Dest.IP
dstLength, _ := route.Dest.Mask.Size()
routeMsg.DstLength = uint8(dstLength)
}
if route.Router != nil {
attr.Gateway = route.Router
}
// TODO figure out if Src is actually needed
/*
if r.Src != nil {
attr.Src = r.Src.IP
ones, _ := r.Src.Net.Mask.Size()
routeMsg.SrcLength = uint8(ones)
}
*/
routeMsg.Attributes = attr
routes = append(routes, routeMsg)
}
// Return a sorted list of routes by scope
// This should allow us to set up link level routes
// before universal routes. This should help prevent
// any network unreachable errors
sort.Slice(routes, func(i, j int) bool {
return routes[i].Scope > routes[j].Scope
})
return routes
}

View File

@ -20,7 +20,8 @@ import (
// DHCP implements the Addressing interface
type DHCP struct {
Ack *dhcpv4.DHCPv4
Ack *dhcpv4.DHCPv4
NetIf *net.Interface
}
// Name returns back the name of the address method.
@ -28,17 +29,26 @@ func (d *DHCP) Name() string {
return "dhcp"
}
// Link returns the underlying net.Interface that this address
// method is configured for
func (d *DHCP) Link() *net.Interface {
return d.NetIf
}
// Discover handles the DHCP client exchange stores the DHCP Ack.
func (d *DHCP) Discover(ctx context.Context, name string) error {
func (d *DHCP) Discover(ctx context.Context) error {
// TODO do something with context
ack, err := discover(name)
ack, err := d.discover()
d.Ack = ack
return err
}
// Address returns back the IP address from the received DHCP offer.
func (d *DHCP) Address() net.IP {
return d.Ack.YourIPAddr
func (d *DHCP) Address() *net.IPNet {
return &net.IPNet{
IP: d.Ack.YourIPAddr,
Mask: d.Mask(),
}
}
// Mask returns the netmask from the DHCP offer.
@ -51,7 +61,7 @@ func (d *DHCP) MTU() uint32 {
// TODO do we need to implement dhcpv4.GetUint32 upstream?
mtu, err := dhcpv4.GetUint16(dhcpv4.OptionInterfaceMTU, d.Ack.Options)
if err != nil {
return 1500
return uint32(d.NetIf.MTU)
}
return uint32(mtu)
}
@ -65,7 +75,7 @@ func (d *DHCP) TTL() time.Duration {
}
// Family qualifies the address as ipv4 or ipv6
func (d *DHCP) Family() uint8 {
func (d *DHCP) Family() int {
if d.Ack.YourIPAddr.To4() != nil {
return unix.AF_INET
}
@ -79,13 +89,23 @@ func (d *DHCP) Scope() uint8 {
// Routes aggregates all Routers and ClasslessStaticRoutes retrieved from
// the DHCP offer.
// rfc3442:
// If the DHCP server returns both a Classless Static Routes option and
// a Router option, the DHCP client MUST ignore the Router option.
func (d *DHCP) Routes() (routes []*Route) {
for _, router := range d.Ack.Router() {
// Note, we don't set a Dest on routes generated from Router()
// since these all should be gateways ( listed in order of preference )
routes = append(routes, &Route{Router: router})
if len(d.Ack.ClasslessStaticRoute()) > 0 {
return d.Ack.ClasslessStaticRoute()
}
routes = append(routes, d.Ack.ClasslessStaticRoute()...)
defRoute := &net.IPNet{
IP: net.IPv4zero,
Mask: net.IPv4Mask(0, 0, 0, 0),
}
for _, router := range d.Ack.Router() {
routes = append(routes, &Route{Router: router, Dest: defRoute})
}
return routes
}
@ -102,7 +122,7 @@ func (d *DHCP) Hostname() string {
}
// discover handles the actual DHCP conversation.
func discover(name string) (*dhcpv4.DHCPv4, error) {
func (d *DHCP) discover() (*dhcpv4.DHCPv4, error) {
opts := []dhcpv4.OptionCode{
dhcpv4.OptionClasslessStaticRoute,
dhcpv4.OptionDomainNameServer,
@ -124,11 +144,10 @@ func discover(name string) (*dhcpv4.DHCPv4, error) {
mods := []dhcpv4.Modifier{dhcpv4.WithRequestedOptions(opts...)}
// TODO expose this with some debug logging option
cli, err := nclient4.New(name, nclient4.WithTimeout(10*time.Second), nclient4.WithDebugLogger())
//cli, err := nclient4.New(name, nclient4.WithTimeout(2*time.Second))
// TODO expose this ( nclient4.WithDebugLogger() ) with some
// debug logging option
cli, err := nclient4.New(d.NetIf.Name, nclient4.WithTimeout(2*time.Second))
if err != nil {
log.Println("failed nclient4.new")
return nil, err
}
// nolint: errcheck

View File

@ -16,11 +16,12 @@ import (
// Static implements the Addressing interface
type Static struct {
Device *userdata.Device
NetIf *net.Interface
}
// Discover doesnt do anything in the static configuration since all
// the necessary configuration data is supplied via userdata.
func (s *Static) Discover(ctx context.Context, name string) error {
func (s *Static) Discover(ctx context.Context) error {
return nil
}
@ -30,13 +31,11 @@ func (s *Static) Name() string {
}
// Address returns the IP address
func (s *Static) Address() net.IP {
func (s *Static) Address() *net.IPNet {
// nolint: errcheck
ip, _, _ := net.ParseCIDR(s.Device.CIDR)
if to4 := ip.To4(); to4 != nil {
return to4
}
return ip
ip, ipn, _ := net.ParseCIDR(s.Device.CIDR)
ipn.IP = ip
return ipn
}
// Mask returns the netmask.
@ -50,7 +49,7 @@ func (s *Static) Mask() net.IPMask {
func (s *Static) MTU() uint32 {
mtu := uint32(s.Device.MTU)
if mtu == 0 {
mtu = 1500
mtu = uint32(s.NetIf.MTU)
}
return mtu
}
@ -62,8 +61,8 @@ func (s *Static) TTL() time.Duration {
}
// Family qualifies the address as ipv4 or ipv6
func (s *Static) Family() uint8 {
if s.Address().To4() != nil {
func (s *Static) Family() int {
if s.Address().IP.To4() != nil {
return unix.AF_INET
}
return unix.AF_INET6
@ -97,3 +96,9 @@ func (s *Static) Resolvers() []net.IP {
func (s *Static) Hostname() string {
return ""
}
// Link returns the underlying net.Interface that this address
// method is configured for
func (s Static) Link() *net.Interface {
return s.NetIf
}

View File

@ -11,7 +11,6 @@ import (
"net"
"strings"
"github.com/jsimonetti/rtnetlink"
"github.com/talos-systems/talos/internal/app/networkd/pkg/address"
"github.com/talos-systems/talos/internal/app/networkd/pkg/nic"
"github.com/talos-systems/talos/pkg/userdata"
@ -19,16 +18,14 @@ import (
// filterInterfaceByName filters network links by name so we only mange links
// we need to
func filterInterfaceByName(links []rtnetlink.LinkMessage) (filteredLinks []rtnetlink.LinkMessage) {
func filterInterfaceByName(links []*net.Interface) (filteredLinks []*net.Interface) {
for _, link := range links {
switch {
case strings.HasPrefix(link.Attributes.Name, "en"):
case strings.HasPrefix(link.Name, "en"):
filteredLinks = append(filteredLinks, link)
case strings.HasPrefix(link.Attributes.Name, "eth"):
case strings.HasPrefix(link.Name, "eth"):
filteredLinks = append(filteredLinks, link)
// TODO Add bond support
// case strings.HasPrefix(netif.Name, "bond"):
case strings.HasPrefix(link.Attributes.Name, "lo"):
case strings.HasPrefix(link.Name, "lo"):
filteredLinks = append(filteredLinks, link)
}
}
@ -37,23 +34,24 @@ func filterInterfaceByName(links []rtnetlink.LinkMessage) (filteredLinks []rtnet
}
// parseLinkMessage creates the base set of attributes for nic creation
func parseLinkMessage(link rtnetlink.LinkMessage) []nic.Option {
func parseLinkMessage(link *net.Interface) []nic.Option {
opts := []nic.Option{}
opts = append(opts, nic.WithName(link.Attributes.Name))
opts = append(opts, nic.WithMTU(link.Attributes.MTU))
opts = append(opts, nic.WithIndex(link.Index))
opts = append(opts, nic.WithName(link.Name))
opts = append(opts, nic.WithMTU(uint32(link.MTU)))
opts = append(opts, nic.WithIndex(uint32(link.Index)))
// Ensure lo has proper loopback address
// Ensure MTU for loopback is 64k
// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=0cf833aefaa85bbfce3ff70485e5534e09254773
if strings.HasPrefix(link.Attributes.Name, "lo") {
if strings.HasPrefix(link.Name, "lo") {
opts = append(opts, nic.WithAddressing(
&address.Static{
Device: &userdata.Device{
CIDR: "127.0.0.1/8",
MTU: 65536,
},
NetIf: link,
},
))
}
@ -76,7 +74,7 @@ func writeResolvConf(resolvers []net.IP) error {
break
}
if _, err = resolvconf.WriteString(fmt.Sprintf("nameserver %s\n", resolver)); err != nil {
log.Println("failde to add some resolver to resolvconf")
log.Println("failed to add some resolver to resolvconf")
return err
}
}

View File

@ -5,38 +5,47 @@
package networkd
import (
"net"
"github.com/talos-systems/talos/internal/app/networkd/pkg/address"
"github.com/talos-systems/talos/internal/app/networkd/pkg/nic"
"github.com/talos-systems/talos/pkg/userdata"
)
// NetConf provides a mapping between an interface name and the functional
// NetConf provides a mapping between an interface link and the functional
// options needed to configure the interface
type NetConf map[string][]nic.Option
type NetConf map[*net.Interface][]nic.Option
// OverlayUserData translates the supplied userdata to functional options
func (n *NetConf) OverlayUserData(data *userdata.UserData) error {
if !validNetworkUserData(data) {
if !validateNetworkUserData(data) {
return nil
}
for name, opts := range *n {
for link, opts := range *n {
for _, device := range data.Networking.OS.Devices {
device := device
if name != device.Interface {
if link.Name != device.Interface {
continue
}
if device.CIDR != "" {
s := &address.Static{Device: &device}
// Configure Addressing
if device.DHCP {
d := &address.DHCP{NetIf: link}
(*n)[link] = append(opts, nic.WithAddressing(d))
}
(*n)[name] = append(opts, nic.WithAddressing(s))
if device.CIDR != "" {
s := &address.Static{Device: &device, NetIf: link}
(*n)[link] = append(opts, nic.WithAddressing(s))
}
// Configure MTU
if device.MTU != 0 {
(*n)[name] = append(opts, nic.WithMTU(uint32(device.MTU)))
(*n)[link] = append(opts, nic.WithMTU(uint32(device.MTU)))
}
}
}
@ -44,7 +53,7 @@ func (n *NetConf) OverlayUserData(data *userdata.UserData) error {
}
// validateNetworkUserData ensures that we have actual data to do our lookups
func validNetworkUserData(data *userdata.UserData) bool {
func validateNetworkUserData(data *userdata.UserData) bool {
if data == nil {
return false
}

View File

@ -6,57 +6,22 @@ package networkd
import (
"log"
"net"
"os"
"syscall"
"github.com/jsimonetti/rtnetlink"
"github.com/mdlayher/netlink"
"golang.org/x/sys/unix"
)
// ifup sets the link state to up
func (n *Networkd) ifup(idx uint32) error {
msg, err := n.Conn.Link.Get(idx)
if err != nil {
log.Printf("failed to get link %d\n", idx)
return err
}
// Only bring the link up if needed
switch msg.Attributes.OperationalState {
case rtnetlink.OperStateUp:
case rtnetlink.OperStateUnknown:
default:
err = n.Conn.Link.Set(&rtnetlink.LinkMessage{
Family: msg.Family,
Type: msg.Type,
Index: idx,
Flags: unix.IFF_UP,
Change: unix.IFF_UP,
})
if err != nil {
log.Println("failed ifup")
return err
}
}
return err
}
// setMTU sets the link MTU
func (n *Networkd) setMTU(idx, mtu uint32) error {
msg, err := n.Conn.Link.Get(idx)
func (n *Networkd) setMTU(idx int, mtu uint32) error {
msg, err := n.nlConn.Link.Get(uint32(idx))
if err != nil {
log.Printf("failed to get link %d\n", idx)
return err
}
err = n.Conn.Link.Set(&rtnetlink.LinkMessage{
err = n.nlConn.Link.Set(&rtnetlink.LinkMessage{
Family: msg.Family,
Type: msg.Type,
Index: idx,
Index: uint32(idx),
Flags: msg.Flags,
Change: 0,
Attributes: &rtnetlink.LinkAttributes{
@ -66,131 +31,3 @@ func (n *Networkd) setMTU(idx, mtu uint32) error {
return err
}
// AddressAdd attempts to configure an address on an interface specified by
// the rtnetlink.AddressMessage. If the address is already configured on the
// interface, no action is taken.
func (n *Networkd) AddressAdd(msg *rtnetlink.AddressMessage) error {
exists, err := n.addressExists(msg)
if err != nil {
return err
}
if exists {
return err
}
return n.Conn.Address.New(msg)
}
func (n *Networkd) addressExists(msg *rtnetlink.AddressMessage) (bool, error) {
al, err := n.Conn.Address.List()
if err != nil {
return false, err
}
// See if the address is already configured
for _, addr := range al {
if addr.Index == msg.Index {
if !msg.Attributes.Address.Equal(addr.Attributes.Address) {
continue
}
if msg.PrefixLength != addr.PrefixLength {
continue
}
return true, err
}
}
return false, err
}
// RouteAdd attempts to configure a route specified by the
// rtnetlink.RouteMessage. If the route is already configured in the
// routing table, no action is taken.
func (n *Networkd) RouteAdd(msg *rtnetlink.RouteMessage) error {
exists, err := n.routeExists(msg)
if err != nil {
return err
}
if exists {
return err
}
if err = n.Conn.Route.Add(msg); err != nil {
// nolint: gocritic
switch err := err.(type) {
case *netlink.OpError:
// ignore the error if it's -EEXIST or -ESRCH
if !os.IsExist(err.Err) && err.Err != syscall.ESRCH {
return err
}
}
}
return nil
}
func (n *Networkd) routeExists(msg *rtnetlink.RouteMessage) (bool, error) {
rl, err := n.Conn.Route.List()
if err != nil {
return false, err
}
for _, route := range rl {
if msg.Attributes.OutIface != route.Attributes.OutIface {
continue
}
// This feels super ugly
// Only compare against what was given
if msg.Attributes.Dst != nil {
if !compareNets(msg.Attributes.Dst, route.Attributes.Dst) {
continue
}
if msg.DstLength != route.DstLength {
continue
}
}
if msg.Attributes.Gateway != nil {
if !compareNets(msg.Attributes.Gateway, route.Attributes.Gateway) {
continue
}
}
// TODO
// Unsure what to do if scope doesnt match
// feels like it should require a delete + add
// TODO figure out if Src is actually needed
/*
if msg.Attributes.Src != nil {
if !compareNets(msg.Attributes.Src, route.Attributes.Src) {
continue
}
if msg.SrcLength != route.SrcLength {
continue
}
}
*/
return true, err
}
return false, err
}
// compareNets is a simple utility function to see if address `a` is
// equal to `b`
func compareNets(a, b net.IP) bool {
if a == nil && b == nil {
return true
}
if a != nil && a.Equal(b) {
return true
}
return false
}

View File

@ -6,14 +6,19 @@ package networkd
import (
"context"
"io/ioutil"
"log"
"net"
"sync"
"os"
"syscall"
"time"
"github.com/jsimonetti/rtnetlink"
"github.com/jsimonetti/rtnetlink/rtnl"
"github.com/mdlayher/netlink"
"github.com/talos-systems/talos/internal/app/networkd/pkg/address"
"github.com/talos-systems/talos/internal/app/networkd/pkg/nic"
"golang.org/x/sys/unix"
)
// Set up default nameservers
@ -23,28 +28,36 @@ const (
)
// Networkd provides the high level interaction to configure network interfaces
// on a host system. This currently support addressing configuration via dhcp
// on a host system. This currently supports addressing configuration via dhcp
// and/or a specified configuration file.
type Networkd struct {
Conn *rtnetlink.Conn
Conn *rtnl.Conn
nlConn *rtnetlink.Conn
}
// New instantiates a new rtnetlink connection that is used for all subsequent
// actions
func New() (*Networkd, error) {
// Handle netlink connection
conn, err := rtnetlink.Dial(nil)
conn, err := rtnl.Dial(nil)
if err != nil {
return nil, err
}
return &Networkd{Conn: conn}, err
// Need rtnetlink for MTU setting
// TODO: possible rtnl enhancement
nlConn, err := rtnetlink.Dial(nil)
if err != nil {
return nil, err
}
return &Networkd{Conn: conn, nlConn: nlConn}, err
}
// Discover enumerates a list of network links on the host and creates a
// base set of interface configuration options
func (n *Networkd) Discover() (NetConf, error) {
links, err := n.Conn.Link.List()
links, err := n.Conn.Links()
if err != nil {
return NetConf{}, err
}
@ -52,7 +65,7 @@ func (n *Networkd) Discover() (NetConf, error) {
linkmap := NetConf{}
for _, link := range filterInterfaceByName(links) {
linkmap[link.Attributes.Name] = parseLinkMessage(link)
linkmap[link] = parseLinkMessage(link)
}
return linkmap, nil
@ -68,15 +81,9 @@ func (n *Networkd) Configure(ifaces ...*nic.NetworkInterface) error {
)
for _, iface := range ifaces {
log.Printf("configuring %+v\n", iface)
// Attempt dhcp against all unconfigured interfaces
if len(iface.AddressMethod) == 0 {
iface.AddressMethod = append(iface.AddressMethod, &address.DHCP{})
}
// Bring up the interface
if err = n.ifup(iface.Index); err != nil {
log.Printf("Failed to bring up %s: %v", iface.Name, err)
if err = n.Conn.LinkUp(&net.Interface{Index: int(iface.Index)}); err != nil {
log.Printf("failed to bring up %s: %v", iface.Name, err)
continue
}
@ -84,7 +91,8 @@ func (n *Networkd) Configure(ifaces ...*nic.NetworkInterface) error {
// the interface
for _, method := range iface.AddressMethod {
log.Printf("configuring %s addressing for %s\n", method.Name(), iface.Name)
if err = n.configureInterface(method, iface.Name, iface.Index); err != nil {
if err = n.configureInterface(method); err != nil {
// Treat as non fatal error when failing to configure an interface
log.Println(err)
continue
@ -107,35 +115,31 @@ func (n *Networkd) Configure(ifaces ...*nic.NetworkInterface) error {
// addressing configuration. Currently this only applies to interfaces
// configured by DHCP.
func (n *Networkd) Renew(ifaces ...*nic.NetworkInterface) {
var wg sync.WaitGroup
for _, iface := range ifaces {
for _, method := range iface.AddressMethod {
if method.TTL() == 0 {
continue
}
wg.Add(1)
go n.renew(method, iface.Name, iface.Index)
go n.renew(method)
}
}
// We dont ever wg.Done
// because this should run forever
// Probably a better way to do this
wg.Wait()
// TODO switch this out with context when that gets added
select {}
}
// renew sets up the looping to ensure we keep the addressing information
// up to date. We attempt to do our first reconfiguration halfway through
// address TTL. If that fails, we'll continue to attempt to retry every
// halflife.
func (n *Networkd) renew(method address.Addressing, name string, index uint32) {
func (n *Networkd) renew(method address.Addressing) {
renewDuration := method.TTL() / 2
for {
<-time.After(renewDuration)
if err := n.configureInterface(method, name, index); err != nil {
log.Printf("failed to renew interface address for %s: %v\n", name, err)
if err := n.configureInterface(method); err != nil {
log.Printf("failed to renew interface address for %s: %v\n", method.Link().Name, err)
renewDuration = (renewDuration / 2)
} else {
renewDuration = method.TTL() / 2
@ -145,43 +149,70 @@ func (n *Networkd) renew(method address.Addressing, name string, index uint32) {
// configureInterface handles the actual address discovery mechanism and
// netlink interaction to configure the interface
func (n *Networkd) configureInterface(method address.Addressing, name string, index uint32) error {
// nolint: gocyclo
func (n *Networkd) configureInterface(method address.Addressing) error {
// TODO s/Discover/Something else/
// TODO make context more relevant
var err error
if err = method.Discover(context.Background(), name); err != nil {
if err = method.Discover(context.Background()); err != nil {
// Right now this would only happen during dhcp discovery failure
log.Printf("Failed to prep %s: %v", name, err)
return err
}
// Netlink message generation
msg := address.AddressMessage(method, index)
// Add address if not exist
if err = n.AddressAdd(msg); err != nil {
// TODO how do we want to handle failures in addressing?
log.Printf("Failed to add address %+v to %s: %v", msg, name, err)
log.Printf("failed to prep %s: %v", method.Link().Name, err)
return err
}
// Set link MTU if we got a response
if err = n.setMTU(index, method.MTU()); err != nil {
log.Printf("Failed to set mtu %d for %s: %v", method.MTU(), name, err)
if err = n.setMTU(method.Link().Index, method.MTU()); err != nil {
log.Printf("failed to set mtu %d for %s: %v", method.MTU(), method.Link().Name, err)
return err
}
// Add any routes
rMsgs := address.RouteMessage(method, index)
for _, r := range rMsgs {
if err = n.RouteAdd(r); err != nil {
// TODO how do we want to handle failures in routing?
log.Printf("Failed to add route %+v for %s: %v", r, name, err)
continue
// Check to see if we need to configure the address
addrs, err := n.Conn.Addrs(method.Link(), method.Family())
if err != nil {
return err
}
addressExists := false
for _, addr := range addrs {
if method.Address().String() == addr.String() {
addressExists = true
break
}
}
return err
if !addressExists {
if err = n.Conn.AddrAdd(method.Link(), method.Address()); err != nil {
switch err := err.(type) {
case *netlink.OpError:
// ignore the error if it's -EEXIST or -ESRCH
if !os.IsExist(err.Err) && err.Err != syscall.ESRCH {
log.Printf("failed to add address %+v to %s: %v", method.Address(), method.Link().Name, err)
return err
}
default:
log.Printf("failed to add address (already exists) %+v to %s: %v", method.Address(), method.Link().Name, err)
}
}
}
// Add any routes
for _, r := range method.Routes() {
if err = n.Conn.RouteAddSrc(method.Link(), *r.Dest, method.Address(), r.Router); err != nil {
switch err := err.(type) {
case *netlink.OpError:
// ignore the error if it's -EEXIST or -ESRCH
if !os.IsExist(err.Err) && err.Err != syscall.ESRCH {
log.Printf("failed to add route %+v for %s: %v", r, method.Link().Name, err)
continue
}
default:
log.Printf("failed to add route (already exists) %+v for %s: %v", r, method.Link().Name, err)
}
}
}
return nil
}
// Hostname returns the first hostname found from the addressing methods.
@ -197,10 +228,10 @@ func (n *Networkd) Hostname(ifaces ...*nic.NetworkInterface) string {
return ""
}
/*
// TODO add this in with some debug level of loggin
func (n *Networkd) printState() {
rl, err := n.Conn.Route.List()
// PrintState displays the current links, addresses, and routing table.
// nolint: gocyclo
func (n *Networkd) PrintState() {
rl, err := n.nlConn.Route.List()
if err != nil {
log.Println(err)
return
@ -209,21 +240,30 @@ func (n *Networkd) printState() {
log.Printf("%+v", r)
}
links, err := n.Conn.Link.List()
links, err := n.Conn.Links()
if err != nil {
log.Println(err)
return
}
for _, link := range links {
log.Printf("%+v", link)
log.Printf("%+v", link.Attributes)
for _, fam := range []int{unix.AF_INET, unix.AF_INET6} {
var addrs []*net.IPNet
addrs, err = n.Conn.Addrs(link, fam)
if err != nil {
log.Println(err)
}
for _, addr := range addrs {
log.Printf("%+v", addr)
}
}
}
b, err := ioutil.ReadFile("/etc/resolv.conf")
var b []byte
b, err = ioutil.ReadFile("/etc/resolv.conf")
if err != nil {
log.Println(err)
return
}
log.Printf("resolv.conf: %s", string(b))
}
*/

View File

@ -6,6 +6,7 @@ package nic
import (
"errors"
"net"
"github.com/hashicorp/go-multierror"
"github.com/talos-systems/talos/internal/app/networkd/pkg/address"
@ -35,7 +36,7 @@ type NetworkInterface struct {
// Create returns a NetworkInterface with all of the given setter options
// applied
func Create(setters ...Option) (*NetworkInterface, error) {
func Create(link *net.Interface, setters ...Option) (*NetworkInterface, error) {
// Default interface setup
iface := defaultOptions()
@ -56,7 +57,7 @@ func Create(setters ...Option) (*NetworkInterface, error) {
// TODO: do we want this behavior or to be explicit with userdata
// so we dont configure every interface be default?
if len(iface.AddressMethod) == 0 {
iface.AddressMethod = append(iface.AddressMethod, &address.DHCP{})
iface.AddressMethod = append(iface.AddressMethod, &address.DHCP{NetIf: link})
}
return iface, result.ErrorOrNil()