blob: b45b8db8921cefbc2cad071f00f63ce507778f31 [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"
Leopold Schabel68c58752019-11-14 21:00:59 +010022 "git.monogon.dev/source/nexantic.git/core/internal/common/service"
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020023 "net"
24 "os"
25
26 "github.com/insomniacslk/dhcp/dhcpv4/nclient4"
27 "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
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020039 config Config
40 dhcp4Client *nclient4.Client
41}
42
43type Config struct {
44}
45
46func NewNetworkService(config Config, logger *zap.Logger) (*Service, error) {
47 s := &Service{
48 config: config,
49 }
Leopold Schabel68c58752019-11-14 21:00:59 +010050 s.BaseService = service.NewBaseService("network", logger, s)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020051 return s, nil
52}
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
77func addNetworkRoutes(link netlink.Link, addr net.IPNet, gw net.IP) error {
78 if err := netlink.AddrReplace(link, &netlink.Addr{IPNet: &addr}); err != nil {
79 return err
80 }
81 if err := netlink.RouteAdd(&netlink.Route{
Leopold Schabel68c58752019-11-14 21:00:59 +010082 Dst: &net.IPNet{IP: net.IPv4(0, 0, 0, 0), Mask: net.IPv4Mask(0, 0, 0, 0)},
83 Gw: gw,
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020084
85 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 }
140 }
141 }
142 if len(ethernetLinks) == 1 {
143 link := ethernetLinks[0]
144 go s.dhcpClient(link)
145
146 } else {
147 s.Logger.Warn("Network service cannot yet handle more than one interface :(")
148 }
149 return nil
150}
151
152func (s *Service) OnStop() error {
153 return nil
154}