Artem Chernyshev 8aad711f18 feat: implement network interfaces list API
To be used in the interactive installer to configure networking.

Signed-off-by: Artem Chernyshev <artem.0xD2@gmail.com>
2020-11-27 10:48:45 -08:00

187 lines
5.0 KiB
Go

// 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 "<nil>"
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
}