blob: 04ab15935168db5a8b1b516c8482b6b33a05076a [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"
Lorenz Brunaa6b7342019-12-12 02:55:02 +010024 "sync"
25 "time"
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020026
Lorenz Brun45333b62019-11-11 15:26:27 +010027 "git.monogon.dev/source/nexantic.git/core/internal/common/service"
28
Lorenz Brunaa6b7342019-12-12 02:55:02 +010029 "github.com/insomniacslk/dhcp/dhcpv4"
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020030 "github.com/insomniacslk/dhcp/dhcpv4/nclient4"
31 "github.com/vishvananda/netlink"
32 "go.uber.org/zap"
33 "golang.org/x/sys/unix"
34)
35
36const (
37 resolvConfPath = "/etc/resolv.conf"
38 resolvConfSwapPath = "/etc/resolv.conf.new"
39)
40
41type Service struct {
Leopold Schabel68c58752019-11-14 21:00:59 +010042 *service.BaseService
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020043 config Config
44 dhcp4Client *nclient4.Client
Lorenz Brunaa6b7342019-12-12 02:55:02 +010045 ip *net.IP
46 ipNotify chan struct{}
47 lock sync.Mutex
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020048}
49
50type Config struct {
51}
52
53func NewNetworkService(config Config, logger *zap.Logger) (*Service, error) {
54 s := &Service{
Lorenz Brunaa6b7342019-12-12 02:55:02 +010055 config: config,
56 ipNotify: make(chan struct{}),
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020057 }
Leopold Schabel68c58752019-11-14 21:00:59 +010058 s.BaseService = service.NewBaseService("network", logger, s)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020059 return s, nil
60}
61
62func setResolvconf(nameservers []net.IP, searchDomains []string) error {
Leopold Schabel68c58752019-11-14 21:00:59 +010063 _ = os.Mkdir("/etc", 0755)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020064 newResolvConf, err := os.Create(resolvConfSwapPath)
65 if err != nil {
66 return err
67 }
68 defer newResolvConf.Close()
69 defer os.Remove(resolvConfSwapPath)
70 for _, ns := range nameservers {
71 if _, err := newResolvConf.WriteString(fmt.Sprintf("nameserver %v\n", ns)); err != nil {
72 return err
73 }
74 }
75 for _, searchDomain := range searchDomains {
76 if _, err := newResolvConf.WriteString(fmt.Sprintf("search %v", searchDomain)); err != nil {
77 return err
78 }
79 }
80 newResolvConf.Close()
81 // Atomically swap in new config
82 return unix.Rename(resolvConfSwapPath, resolvConfPath)
83}
84
85func addNetworkRoutes(link netlink.Link, addr net.IPNet, gw net.IP) error {
86 if err := netlink.AddrReplace(link, &netlink.Addr{IPNet: &addr}); err != nil {
87 return err
88 }
89 if err := netlink.RouteAdd(&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,
93 }); err != nil {
94 return fmt.Errorf("Failed to add default route: %w", err)
95 }
96 return nil
97}
98
99const (
100 stateInitialize = 1
101 stateSelect = 2
102 stateBound = 3
103 stateRenew = 4
104 stateRebind = 5
105)
106
107var dhcpBroadcastAddr = &net.UDPAddr{IP: net.IP{255, 255, 255, 255}, Port: 67}
108
109// TODO(lorenz): This is a super terrible DHCP client, but it works for QEMU slirp
110func (s *Service) dhcpClient(iface netlink.Link) error {
111 client, err := nclient4.New(iface.Attrs().Name)
112 if err != nil {
113 panic(err)
114 }
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100115 var ack *dhcpv4.DHCPv4
116 for {
117 dhcpCtx, dhcpCtxCancel := context.WithTimeout(context.Background(), 10*time.Second)
118 defer dhcpCtxCancel()
119 _, ack, err = client.Request(dhcpCtx)
120 if err == nil {
121 break
122 }
123 s.Logger.Info("DHCP request failed", zap.Error(err))
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200124 }
125 s.Logger.Info("Network service got IP", zap.String("ip", ack.YourIPAddr.String()))
126 if err := setResolvconf(ack.DNS(), []string{}); err != nil {
127 s.Logger.Warn("Failed to set resolvconf", zap.Error(err))
128 }
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100129
130 s.lock.Lock()
131 s.ip = &ack.YourIPAddr
132 s.lock.Unlock()
133loop:
134 for {
135 select {
136 case s.ipNotify <- struct{}{}:
137 default:
138 break loop
139 }
140 }
141
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200142 if err := addNetworkRoutes(iface, net.IPNet{IP: ack.YourIPAddr, Mask: ack.SubnetMask()}, ack.GatewayIPAddr); err != nil {
143 s.Logger.Warn("Failed to add routes", zap.Error(err))
144 }
145 return nil
146}
147
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100148// GetIP returns the current IP (and optionally waits for one to be assigned)
149func (s *Service) GetIP(wait bool) *net.IP {
150 s.lock.Lock()
151 if !wait {
152 ip := s.ip
153 s.lock.Unlock()
154 return ip
155 }
156
157 for {
158 if s.ip != nil {
159 ip := s.ip
160 s.lock.Unlock()
161 return ip
162 }
163 s.lock.Unlock()
164 <-s.ipNotify
165 s.lock.Lock()
166 }
167}
168
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200169func (s *Service) OnStart() error {
170 s.Logger.Info("Starting network service")
171 links, err := netlink.LinkList()
172 if err != nil {
173 s.Logger.Fatal("Failed to list network links", zap.Error(err))
174 }
175 var ethernetLinks []netlink.Link
176 for _, link := range links {
177 attrs := link.Attrs()
178 if link.Type() == "device" && len(attrs.HardwareAddr) > 0 {
179 if len(attrs.HardwareAddr) == 6 { // Ethernet
180 if attrs.Flags&net.FlagUp != net.FlagUp {
181 netlink.LinkSetUp(link) // Attempt to take up all ethernet links
182 }
183 ethernetLinks = append(ethernetLinks, link)
184 } else {
185 s.Logger.Info("Ignoring non-Ethernet interface", zap.String("interface", attrs.Name))
186 }
Lorenz Brun45333b62019-11-11 15:26:27 +0100187 } else if link.Attrs().Name == "lo" {
188 if err := netlink.LinkSetUp(link); err != nil {
189 s.Logger.Error("Failed to take up loopback interface", zap.Error(err))
190 }
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200191 }
192 }
193 if len(ethernetLinks) == 1 {
194 link := ethernetLinks[0]
195 go s.dhcpClient(link)
196
197 } else {
198 s.Logger.Warn("Network service cannot yet handle more than one interface :(")
199 }
200 return nil
201}
202
203func (s *Service) OnStop() error {
204 return nil
205}