// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. // Package reg provides the gRPC network service implementation. package reg import ( "context" "errors" "fmt" "log" "net" "time" "github.com/golang/protobuf/ptypes/empty" "github.com/jsimonetti/rtnetlink" "golang.org/x/sys/unix" "google.golang.org/grpc" "github.com/talos-systems/talos/internal/app/networkd/pkg/networkd" healthapi "github.com/talos-systems/talos/pkg/machinery/api/health" networkapi "github.com/talos-systems/talos/pkg/machinery/api/network" ) // Registrator is the concrete type that implements the factory.Registrator and // networkapi.NetworkServer interfaces. type Registrator struct { Networkd *networkd.Networkd Conn *rtnetlink.Conn } // NewRegistrator builds new Registrator instance. func NewRegistrator(n *networkd.Networkd) *Registrator { nlConn, err := rtnetlink.Dial(nil) if err != nil { log.Fatal(err) } return &Registrator{ Networkd: n, Conn: nlConn, } } // Register implements the factory.Registrator interface. func (r *Registrator) Register(s *grpc.Server) { networkapi.RegisterNetworkServiceServer(s, r) healthapi.RegisterHealthServer(s, r) } // Routes returns the hosts routing table. func (r *Registrator) Routes(ctx context.Context, in *empty.Empty) (reply *networkapi.RoutesResponse, err error) { list, err := r.Conn.Route.List() if err != nil { return nil, fmt.Errorf("failed to get route list: %w", err) } routes := []*networkapi.Route{} for _, rMesg := range list { ifaceData, err := r.Conn.Link.Get((rMesg.Attributes.OutIface)) if err != nil { log.Printf("failed to get interface details for interface index %d: %v", rMesg.Attributes.OutIface, err) // TODO: Remove once we get this sorted on why there's a // failure here log.Printf("%+v", rMesg) continue } routes = append(routes, &networkapi.Route{ Interface: ifaceData.Attributes.Name, Destination: toCIDR(rMesg.Family, rMesg.Attributes.Dst, int(rMesg.DstLength)), Gateway: toCIDR(rMesg.Family, rMesg.Attributes.Gateway, 32), Metric: rMesg.Attributes.Priority, Scope: uint32(rMesg.Scope), Source: toCIDR(rMesg.Family, rMesg.Attributes.Src, int(rMesg.SrcLength)), Family: networkapi.AddressFamily(rMesg.Family), Protocol: networkapi.RouteProtocol(rMesg.Protocol), Flags: rMesg.Flags, }) } return &networkapi.RoutesResponse{ Messages: []*networkapi.Routes{ { Routes: routes, }, }, }, nil } // Interfaces returns the hosts network interfaces and addresses. func (r *Registrator) Interfaces(ctx context.Context, in *empty.Empty) (reply *networkapi.InterfacesResponse, err error) { return networkd.GetDevices() } func toCIDR(family uint8, prefix net.IP, prefixLen int) string { netLen := 32 if family == unix.AF_INET6 { netLen = 128 } // Set a friendly readable value instead of "" if prefix == nil { switch family { case unix.AF_INET6: prefix = net.ParseIP("::") case unix.AF_INET: prefix = net.ParseIP("0.0.0.0") } } ipNet := &net.IPNet{ IP: prefix, Mask: net.CIDRMask(prefixLen, netLen), } return ipNet.String() } // Check implements the Health api and provides visibilty into the state of networkd. func (r *Registrator) Check(ctx context.Context, in *empty.Empty) (reply *healthapi.HealthCheckResponse, err error) { reply = &healthapi.HealthCheckResponse{ Messages: []*healthapi.HealthCheck{ { Status: healthapi.HealthCheck_SERVING, }, }, } return reply, nil } // Watch implements the Health api and provides visibilty into the state of networkd. // Ready signifies the daemon (api) is healthy and ready to serve requests. func (r *Registrator) Watch(in *healthapi.HealthWatchRequest, srv healthapi.Health_WatchServer) (err error) { if in == nil { return errors.New("an input interval is required") } var ( resp *healthapi.HealthCheckResponse ticker = time.NewTicker(time.Duration(in.IntervalSeconds) * time.Second) ) defer ticker.Stop() for { select { case <-srv.Context().Done(): return srv.Context().Err() case <-ticker.C: resp, err = r.Check(srv.Context(), &empty.Empty{}) if err != nil { return err } if err = srv.Send(resp); err != nil { return err } } } } // Ready implements the Health api and provides visibility to the state of networkd. // Ready signifies the initial network configuration ( interfaces, routes, hostname, resolv.conf ) // settings have been applied. // Not Ready signifies that the initial network configuration still needs to happen. func (r *Registrator) Ready(ctx context.Context, in *empty.Empty) (reply *healthapi.ReadyCheckResponse, err error) { rdy := &healthapi.ReadyCheck{Status: healthapi.ReadyCheck_NOT_READY} if r.Networkd.Ready() { rdy.Status = healthapi.ReadyCheck_READY } reply = &healthapi.ReadyCheckResponse{ Messages: []*healthapi.ReadyCheck{ rdy, }, } return reply, nil }