blob: 00d7fb2d4c2f43ab07688af839d68b5517bc0db4 [file] [log] [blame]
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +02001// Copyright 2020 The Monogon Project Authors.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17package network
18
19import (
20 "context"
21 "fmt"
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020022 "net"
23 "os"
24
Serge Bazanskib1b742f2020-03-24 13:58:19 +010025 "git.monogon.dev/source/nexantic.git/core/internal/common/supervisor"
Lorenz Brun45333b62019-11-11 15:26:27 +010026
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020027 "github.com/vishvananda/netlink"
28 "go.uber.org/zap"
29 "golang.org/x/sys/unix"
30)
31
32const (
33 resolvConfPath = "/etc/resolv.conf"
34 resolvConfSwapPath = "/etc/resolv.conf.new"
35)
36
37type Service struct {
Serge Bazanskicdb8c782020-02-17 12:34:02 +010038 config Config
Serge Bazanskib1b742f2020-03-24 13:58:19 +010039 dhcp *dhcpClient
Serge Bazanskicdb8c782020-02-17 12:34:02 +010040
Serge Bazanskib1b742f2020-03-24 13:58:19 +010041 logger *zap.Logger
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020042}
43
44type Config struct {
45}
46
Serge Bazanskib1b742f2020-03-24 13:58:19 +010047func New(config Config) *Service {
48 return &Service{
Serge Bazanskicdb8c782020-02-17 12:34:02 +010049 config: config,
Serge Bazanskib1b742f2020-03-24 13:58:19 +010050 dhcp: newDHCPClient(),
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020051 }
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020052}
53
54func setResolvconf(nameservers []net.IP, searchDomains []string) error {
Leopold Schabel68c58752019-11-14 21:00:59 +010055 _ = os.Mkdir("/etc", 0755)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020056 newResolvConf, err := os.Create(resolvConfSwapPath)
57 if err != nil {
58 return err
59 }
60 defer newResolvConf.Close()
61 defer os.Remove(resolvConfSwapPath)
62 for _, ns := range nameservers {
63 if _, err := newResolvConf.WriteString(fmt.Sprintf("nameserver %v\n", ns)); err != nil {
64 return err
65 }
66 }
67 for _, searchDomain := range searchDomains {
68 if _, err := newResolvConf.WriteString(fmt.Sprintf("search %v", searchDomain)); err != nil {
69 return err
70 }
71 }
72 newResolvConf.Close()
73 // Atomically swap in new config
74 return unix.Rename(resolvConfSwapPath, resolvConfPath)
75}
76
Serge Bazanski1a5a6672020-02-18 10:09:43 +010077func (s *Service) addNetworkRoutes(link netlink.Link, addr net.IPNet, gw net.IP) error {
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020078 if err := netlink.AddrReplace(link, &netlink.Addr{IPNet: &addr}); err != nil {
79 return err
80 }
Serge Bazanski1a5a6672020-02-18 10:09:43 +010081
82 if gw.IsUnspecified() {
Serge Bazanskib1b742f2020-03-24 13:58:19 +010083 s.logger.Info("No default route set, only local network will be reachable", zap.String("local", addr.String()))
Serge Bazanski1a5a6672020-02-18 10:09:43 +010084 return nil
85 }
86
87 route := &netlink.Route{
Lorenz Brun45333b62019-11-11 15:26:27 +010088 Dst: &net.IPNet{IP: net.IPv4(0, 0, 0, 0), Mask: net.IPv4Mask(0, 0, 0, 0)},
89 Gw: gw,
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020090 Scope: netlink.SCOPE_UNIVERSE,
Serge Bazanski1a5a6672020-02-18 10:09:43 +010091 }
92 if err := netlink.RouteAdd(route); err != nil {
93 return fmt.Errorf("could not add default route: netlink.RouteAdd(%+v): %v", route, err)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020094 }
95 return nil
96}
97
Serge Bazanskib1b742f2020-03-24 13:58:19 +010098func (s *Service) useInterface(ctx context.Context, iface netlink.Link) error {
99 err := supervisor.Run(ctx, "dhcp", s.dhcp.run(iface))
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200100 if err != nil {
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100101 return err
102 }
103 status, err := s.dhcp.status(ctx, true)
104 if err != nil {
105 return fmt.Errorf("could not get DHCP status: %w", err)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200106 }
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100107
Serge Bazanski1a5a6672020-02-18 10:09:43 +0100108 if err := setResolvconf(status.dns, []string{}); err != nil {
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100109 s.logger.Warn("failed to set resolvconf", zap.Error(err))
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100110 }
111
Serge Bazanski1a5a6672020-02-18 10:09:43 +0100112 if err := s.addNetworkRoutes(iface, net.IPNet{IP: status.address.IP, Mask: status.address.Mask}, status.gateway); err != nil {
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100113 s.logger.Warn("failed to add routes", zap.Error(err))
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200114 }
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100115
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200116 return nil
117}
118
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100119// GetIP returns the current IP (and optionally waits for one to be assigned)
Serge Bazanskicdb8c782020-02-17 12:34:02 +0100120func (s *Service) GetIP(ctx context.Context, wait bool) (*net.IP, error) {
121 status, err := s.dhcp.status(ctx, wait)
122 if err != nil {
123 return nil, err
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100124 }
Serge Bazanski1a5a6672020-02-18 10:09:43 +0100125 return &status.address.IP, nil
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100126}
127
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100128func (s *Service) Run(ctx context.Context) error {
129 s.logger = supervisor.Logger(ctx)
130 s.logger.Info("Starting network service")
131
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200132 links, err := netlink.LinkList()
133 if err != nil {
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100134 s.logger.Fatal("Failed to list network links", zap.Error(err))
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200135 }
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100136
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200137 var ethernetLinks []netlink.Link
138 for _, link := range links {
139 attrs := link.Attrs()
140 if link.Type() == "device" && len(attrs.HardwareAddr) > 0 {
141 if len(attrs.HardwareAddr) == 6 { // Ethernet
142 if attrs.Flags&net.FlagUp != net.FlagUp {
143 netlink.LinkSetUp(link) // Attempt to take up all ethernet links
144 }
145 ethernetLinks = append(ethernetLinks, link)
146 } else {
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100147 s.logger.Info("Ignoring non-Ethernet interface", zap.String("interface", attrs.Name))
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200148 }
Lorenz Brun45333b62019-11-11 15:26:27 +0100149 } else if link.Attrs().Name == "lo" {
150 if err := netlink.LinkSetUp(link); err != nil {
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100151 s.logger.Error("Failed to take up loopback interface", zap.Error(err))
Lorenz Brun45333b62019-11-11 15:26:27 +0100152 }
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200153 }
154 }
Serge Bazanskicdb8c782020-02-17 12:34:02 +0100155 if len(ethernetLinks) != 1 {
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100156 s.logger.Warn("Network service needs exactly one link, bailing")
157 } else {
158 link := ethernetLinks[0]
159 if err := s.useInterface(ctx, link); err != nil {
160 return fmt.Errorf("failed to bring up link %s: %w", link.Attrs().Name, err)
161 }
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200162 }
Serge Bazanskicdb8c782020-02-17 12:34:02 +0100163
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100164 supervisor.Signal(ctx, supervisor.SignalHealthy)
165 supervisor.Signal(ctx, supervisor.SignalDone)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200166 return nil
167}