blob: 0888de7ad4b68ed8ba7042bb443faf7dcab32892 [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/insomniacslk/dhcp/dhcpv4/nclient4"
28 "github.com/vishvananda/netlink"
29 "go.uber.org/zap"
30 "golang.org/x/sys/unix"
31)
32
33const (
34 resolvConfPath = "/etc/resolv.conf"
35 resolvConfSwapPath = "/etc/resolv.conf.new"
36)
37
38type Service struct {
Leopold Schabel68c58752019-11-14 21:00:59 +010039 *service.BaseService
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020040 config Config
41 dhcp4Client *nclient4.Client
42}
43
44type Config struct {
45}
46
47func NewNetworkService(config Config, logger *zap.Logger) (*Service, error) {
48 s := &Service{
49 config: config,
50 }
Leopold Schabel68c58752019-11-14 21:00:59 +010051 s.BaseService = service.NewBaseService("network", logger, s)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020052 return s, nil
53}
54
55func setResolvconf(nameservers []net.IP, searchDomains []string) error {
Leopold Schabel68c58752019-11-14 21:00:59 +010056 _ = os.Mkdir("/etc", 0755)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020057 newResolvConf, err := os.Create(resolvConfSwapPath)
58 if err != nil {
59 return err
60 }
61 defer newResolvConf.Close()
62 defer os.Remove(resolvConfSwapPath)
63 for _, ns := range nameservers {
64 if _, err := newResolvConf.WriteString(fmt.Sprintf("nameserver %v\n", ns)); err != nil {
65 return err
66 }
67 }
68 for _, searchDomain := range searchDomains {
69 if _, err := newResolvConf.WriteString(fmt.Sprintf("search %v", searchDomain)); err != nil {
70 return err
71 }
72 }
73 newResolvConf.Close()
74 // Atomically swap in new config
75 return unix.Rename(resolvConfSwapPath, resolvConfPath)
76}
77
78func addNetworkRoutes(link netlink.Link, addr net.IPNet, gw net.IP) error {
79 if err := netlink.AddrReplace(link, &netlink.Addr{IPNet: &addr}); err != nil {
80 return err
81 }
82 if err := netlink.RouteAdd(&netlink.Route{
Lorenz Brun45333b62019-11-11 15:26:27 +010083 Dst: &net.IPNet{IP: net.IPv4(0, 0, 0, 0), Mask: net.IPv4Mask(0, 0, 0, 0)},
84 Gw: gw,
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020085 Scope: netlink.SCOPE_UNIVERSE,
86 }); err != nil {
87 return fmt.Errorf("Failed to add default route: %w", err)
88 }
89 return nil
90}
91
92const (
93 stateInitialize = 1
94 stateSelect = 2
95 stateBound = 3
96 stateRenew = 4
97 stateRebind = 5
98)
99
100var dhcpBroadcastAddr = &net.UDPAddr{IP: net.IP{255, 255, 255, 255}, Port: 67}
101
102// TODO(lorenz): This is a super terrible DHCP client, but it works for QEMU slirp
103func (s *Service) dhcpClient(iface netlink.Link) error {
104 client, err := nclient4.New(iface.Attrs().Name)
105 if err != nil {
106 panic(err)
107 }
108 _, ack, err := client.Request(context.Background())
109 if err != nil {
110 panic(err)
111 }
112 s.Logger.Info("Network service got IP", zap.String("ip", ack.YourIPAddr.String()))
113 if err := setResolvconf(ack.DNS(), []string{}); err != nil {
114 s.Logger.Warn("Failed to set resolvconf", zap.Error(err))
115 }
116 if err := addNetworkRoutes(iface, net.IPNet{IP: ack.YourIPAddr, Mask: ack.SubnetMask()}, ack.GatewayIPAddr); err != nil {
117 s.Logger.Warn("Failed to add routes", zap.Error(err))
118 }
119 return nil
120}
121
122func (s *Service) OnStart() error {
123 s.Logger.Info("Starting network service")
124 links, err := netlink.LinkList()
125 if err != nil {
126 s.Logger.Fatal("Failed to list network links", zap.Error(err))
127 }
128 var ethernetLinks []netlink.Link
129 for _, link := range links {
130 attrs := link.Attrs()
131 if link.Type() == "device" && len(attrs.HardwareAddr) > 0 {
132 if len(attrs.HardwareAddr) == 6 { // Ethernet
133 if attrs.Flags&net.FlagUp != net.FlagUp {
134 netlink.LinkSetUp(link) // Attempt to take up all ethernet links
135 }
136 ethernetLinks = append(ethernetLinks, link)
137 } else {
138 s.Logger.Info("Ignoring non-Ethernet interface", zap.String("interface", attrs.Name))
139 }
Lorenz Brun45333b62019-11-11 15:26:27 +0100140 } else if link.Attrs().Name == "lo" {
141 if err := netlink.LinkSetUp(link); err != nil {
142 s.Logger.Error("Failed to take up loopback interface", zap.Error(err))
143 }
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200144 }
145 }
146 if len(ethernetLinks) == 1 {
147 link := ethernetLinks[0]
148 go s.dhcpClient(link)
149
150 } else {
151 s.Logger.Warn("Network service cannot yet handle more than one interface :(")
152 }
153 return nil
154}
155
156func (s *Service) OnStop() error {
157 return nil
158}