blob: 01760c76fd3ea6743d2c73504129cc7759f02488 [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
Lorenz Brun45333b62019-11-11 15:26:27 +010025 "git.monogon.dev/source/nexantic.git/core/internal/common/service"
26
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 {
Leopold Schabel68c58752019-11-14 21:00:59 +010038 *service.BaseService
Serge Bazanskicdb8c782020-02-17 12:34:02 +010039 config Config
40
41 dhcp *dhcpClient
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020042}
43
44type Config struct {
45}
46
47func NewNetworkService(config Config, logger *zap.Logger) (*Service, error) {
48 s := &Service{
Serge Bazanskicdb8c782020-02-17 12:34:02 +010049 config: config,
50 dhcp: newDHCPClient(logger),
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020051 }
Leopold Schabel68c58752019-11-14 21:00:59 +010052 s.BaseService = service.NewBaseService("network", logger, s)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020053 return s, nil
54}
55
56func setResolvconf(nameservers []net.IP, searchDomains []string) error {
Leopold Schabel68c58752019-11-14 21:00:59 +010057 _ = os.Mkdir("/etc", 0755)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020058 newResolvConf, err := os.Create(resolvConfSwapPath)
59 if err != nil {
60 return err
61 }
62 defer newResolvConf.Close()
63 defer os.Remove(resolvConfSwapPath)
64 for _, ns := range nameservers {
65 if _, err := newResolvConf.WriteString(fmt.Sprintf("nameserver %v\n", ns)); err != nil {
66 return err
67 }
68 }
69 for _, searchDomain := range searchDomains {
70 if _, err := newResolvConf.WriteString(fmt.Sprintf("search %v", searchDomain)); err != nil {
71 return err
72 }
73 }
74 newResolvConf.Close()
75 // Atomically swap in new config
76 return unix.Rename(resolvConfSwapPath, resolvConfPath)
77}
78
Serge Bazanski1a5a6672020-02-18 10:09:43 +010079func (s *Service) addNetworkRoutes(link netlink.Link, addr net.IPNet, gw net.IP) error {
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020080 if err := netlink.AddrReplace(link, &netlink.Addr{IPNet: &addr}); err != nil {
81 return err
82 }
Serge Bazanski1a5a6672020-02-18 10:09:43 +010083
84 if gw.IsUnspecified() {
85 s.Logger.Info("No default route set, only local network will be reachable", zap.String("local", addr.String()))
86 return nil
87 }
88
89 route := &netlink.Route{
Lorenz Brun45333b62019-11-11 15:26:27 +010090 Dst: &net.IPNet{IP: net.IPv4(0, 0, 0, 0), Mask: net.IPv4Mask(0, 0, 0, 0)},
91 Gw: gw,
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020092 Scope: netlink.SCOPE_UNIVERSE,
Serge Bazanski1a5a6672020-02-18 10:09:43 +010093 }
94 if err := netlink.RouteAdd(route); err != nil {
95 return fmt.Errorf("could not add default route: netlink.RouteAdd(%+v): %v", route, err)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020096 }
97 return nil
98}
99
Serge Bazanskicdb8c782020-02-17 12:34:02 +0100100func (s *Service) useInterface(iface netlink.Link) error {
101 go s.dhcp.run(s.Context(), iface)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200102
Serge Bazanskicdb8c782020-02-17 12:34:02 +0100103 status, err := s.dhcp.status(s.Context(), true)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200104 if err != nil {
Serge Bazanskicdb8c782020-02-17 12:34:02 +0100105 return fmt.Errorf("could not get DHCP status: %v", 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 Bazanskicdb8c782020-02-17 12:34:02 +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 Bazanskicdb8c782020-02-17 12:34:02 +0100113 s.Logger.Warn("failed to add routes", zap.Error(err))
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200114 }
115 return nil
116}
117
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100118// GetIP returns the current IP (and optionally waits for one to be assigned)
Serge Bazanskicdb8c782020-02-17 12:34:02 +0100119func (s *Service) GetIP(ctx context.Context, wait bool) (*net.IP, error) {
120 status, err := s.dhcp.status(ctx, wait)
121 if err != nil {
122 return nil, err
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100123 }
Serge Bazanski1a5a6672020-02-18 10:09:43 +0100124 return &status.address.IP, nil
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100125}
126
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200127func (s *Service) OnStart() error {
128 s.Logger.Info("Starting network service")
129 links, err := netlink.LinkList()
130 if err != nil {
131 s.Logger.Fatal("Failed to list network links", zap.Error(err))
132 }
133 var ethernetLinks []netlink.Link
134 for _, link := range links {
135 attrs := link.Attrs()
136 if link.Type() == "device" && len(attrs.HardwareAddr) > 0 {
137 if len(attrs.HardwareAddr) == 6 { // Ethernet
138 if attrs.Flags&net.FlagUp != net.FlagUp {
139 netlink.LinkSetUp(link) // Attempt to take up all ethernet links
140 }
141 ethernetLinks = append(ethernetLinks, link)
142 } else {
143 s.Logger.Info("Ignoring non-Ethernet interface", zap.String("interface", attrs.Name))
144 }
Lorenz Brun45333b62019-11-11 15:26:27 +0100145 } else if link.Attrs().Name == "lo" {
146 if err := netlink.LinkSetUp(link); err != nil {
147 s.Logger.Error("Failed to take up loopback interface", zap.Error(err))
148 }
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200149 }
150 }
Serge Bazanskicdb8c782020-02-17 12:34:02 +0100151 if len(ethernetLinks) != 1 {
152 s.Logger.Warn("Network service needs exactly one link, bailing")
153 return nil
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200154 }
Serge Bazanskicdb8c782020-02-17 12:34:02 +0100155
156 link := ethernetLinks[0]
157 go s.useInterface(link)
158
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200159 return nil
160}
161
162func (s *Service) OnStop() error {
163 return nil
164}