blob: c92b21a08d1af8b48a3965a3d9131e9b8d301005 [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"
Lorenz Brunf042e6f2020-06-24 16:46:09 +020022 "io/ioutil"
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020023 "net"
24 "os"
25
Lorenz Brunb682ba52020-07-08 14:51:36 +020026 "github.com/google/nftables"
27 "github.com/google/nftables/expr"
28
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020029 "github.com/vishvananda/netlink"
30 "go.uber.org/zap"
31 "golang.org/x/sys/unix"
Lorenz Brun52f7f292020-06-24 16:42:02 +020032
33 "git.monogon.dev/source/nexantic.git/core/internal/common/supervisor"
34 "git.monogon.dev/source/nexantic.git/core/internal/network/dhcp"
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020035)
36
37const (
38 resolvConfPath = "/etc/resolv.conf"
39 resolvConfSwapPath = "/etc/resolv.conf.new"
40)
41
42type Service struct {
Serge Bazanskicdb8c782020-02-17 12:34:02 +010043 config Config
Lorenz Brun52f7f292020-06-24 16:42:02 +020044 dhcp *dhcp.Client
Serge Bazanskicdb8c782020-02-17 12:34:02 +010045
Serge Bazanskib1b742f2020-03-24 13:58:19 +010046 logger *zap.Logger
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020047}
48
49type Config struct {
50}
51
Serge Bazanskib1b742f2020-03-24 13:58:19 +010052func New(config Config) *Service {
53 return &Service{
Serge Bazanskicdb8c782020-02-17 12:34:02 +010054 config: config,
Lorenz Brun52f7f292020-06-24 16:42:02 +020055 dhcp: dhcp.New(),
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020056 }
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020057}
58
59func setResolvconf(nameservers []net.IP, searchDomains []string) error {
Leopold Schabel68c58752019-11-14 21:00:59 +010060 _ = os.Mkdir("/etc", 0755)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020061 newResolvConf, err := os.Create(resolvConfSwapPath)
62 if err != nil {
63 return err
64 }
65 defer newResolvConf.Close()
66 defer os.Remove(resolvConfSwapPath)
67 for _, ns := range nameservers {
68 if _, err := newResolvConf.WriteString(fmt.Sprintf("nameserver %v\n", ns)); err != nil {
69 return err
70 }
71 }
72 for _, searchDomain := range searchDomains {
73 if _, err := newResolvConf.WriteString(fmt.Sprintf("search %v", searchDomain)); err != nil {
74 return err
75 }
76 }
77 newResolvConf.Close()
78 // Atomically swap in new config
79 return unix.Rename(resolvConfSwapPath, resolvConfPath)
80}
81
Serge Bazanski1a5a6672020-02-18 10:09:43 +010082func (s *Service) addNetworkRoutes(link netlink.Link, addr net.IPNet, gw net.IP) error {
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020083 if err := netlink.AddrReplace(link, &netlink.Addr{IPNet: &addr}); err != nil {
Lorenz Brun52f7f292020-06-24 16:42:02 +020084 return fmt.Errorf("failed to add DHCP address to network interface \"%v\": %w", link.Attrs().Name, err)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020085 }
Serge Bazanski1a5a6672020-02-18 10:09:43 +010086
87 if gw.IsUnspecified() {
Serge Bazanskib1b742f2020-03-24 13:58:19 +010088 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 +010089 return nil
90 }
91
92 route := &netlink.Route{
Lorenz Brun45333b62019-11-11 15:26:27 +010093 Dst: &net.IPNet{IP: net.IPv4(0, 0, 0, 0), Mask: net.IPv4Mask(0, 0, 0, 0)},
94 Gw: gw,
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020095 Scope: netlink.SCOPE_UNIVERSE,
Serge Bazanski1a5a6672020-02-18 10:09:43 +010096 }
97 if err := netlink.RouteAdd(route); err != nil {
98 return fmt.Errorf("could not add default route: netlink.RouteAdd(%+v): %v", route, err)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020099 }
100 return nil
101}
102
Lorenz Brunb682ba52020-07-08 14:51:36 +0200103// nfifname converts an interface name into 16 bytes padded with zeroes (for nftables)
104func nfifname(n string) []byte {
105 b := make([]byte, 16)
106 copy(b, []byte(n+"\x00"))
107 return b
108}
109
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100110func (s *Service) useInterface(ctx context.Context, iface netlink.Link) error {
Lorenz Brun52f7f292020-06-24 16:42:02 +0200111 err := supervisor.Run(ctx, "dhcp", s.dhcp.Run(iface))
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200112 if err != nil {
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100113 return err
114 }
Lorenz Brun52f7f292020-06-24 16:42:02 +0200115 status, err := s.dhcp.Status(ctx, true)
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100116 if err != nil {
Lorenz Brun52f7f292020-06-24 16:42:02 +0200117 return fmt.Errorf("could not get DHCP Status: %w", err)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200118 }
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100119
Lorenz Brun52f7f292020-06-24 16:42:02 +0200120 if err := setResolvconf(status.DNS, []string{}); err != nil {
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100121 s.logger.Warn("failed to set resolvconf", zap.Error(err))
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100122 }
123
Lorenz Brun52f7f292020-06-24 16:42:02 +0200124 if err := s.addNetworkRoutes(iface, status.Address, status.Gateway); err != nil {
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100125 s.logger.Warn("failed to add routes", zap.Error(err))
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200126 }
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100127
Lorenz Brunb682ba52020-07-08 14:51:36 +0200128 c := nftables.Conn{}
129
130 nat := c.AddTable(&nftables.Table{
131 Family: nftables.TableFamilyIPv4,
132 Name: "nat",
133 })
134
135 postrouting := c.AddChain(&nftables.Chain{
136 Name: "postrouting",
137 Hooknum: nftables.ChainHookPostrouting,
138 Priority: nftables.ChainPriorityNATSource,
139 Table: nat,
140 Type: nftables.ChainTypeNAT,
141 })
142
143 // Masquerade/SNAT all traffic going out of the external interface
144 c.AddRule(&nftables.Rule{
145 Table: nat,
146 Chain: postrouting,
147 Exprs: []expr.Any{
148 &expr.Meta{Key: expr.MetaKeyOIFNAME, Register: 1},
149 &expr.Cmp{
150 Op: expr.CmpOpEq,
151 Register: 1,
152 Data: nfifname(iface.Attrs().Name),
153 },
154 &expr.Masq{},
155 },
156 })
157
158 if err := c.Flush(); err != nil {
159 panic(err)
160 }
161
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200162 return nil
163}
164
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100165// GetIP returns the current IP (and optionally waits for one to be assigned)
Serge Bazanskicdb8c782020-02-17 12:34:02 +0100166func (s *Service) GetIP(ctx context.Context, wait bool) (*net.IP, error) {
Lorenz Brun52f7f292020-06-24 16:42:02 +0200167 status, err := s.dhcp.Status(ctx, wait)
Serge Bazanskicdb8c782020-02-17 12:34:02 +0100168 if err != nil {
169 return nil, err
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100170 }
Lorenz Brun52f7f292020-06-24 16:42:02 +0200171 return &status.Address.IP, nil
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100172}
173
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100174func (s *Service) Run(ctx context.Context) error {
175 s.logger = supervisor.Logger(ctx)
176 s.logger.Info("Starting network service")
177
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200178 links, err := netlink.LinkList()
179 if err != nil {
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100180 s.logger.Fatal("Failed to list network links", zap.Error(err))
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200181 }
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100182
Lorenz Brunf042e6f2020-06-24 16:46:09 +0200183 if err := ioutil.WriteFile("/proc/sys/net/ipv4/ip_forward", []byte("1\n"), 0644); err != nil {
184 s.logger.Panic("Failed to enable IPv4 forwarding", zap.Error(err))
185 }
186
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200187 var ethernetLinks []netlink.Link
188 for _, link := range links {
189 attrs := link.Attrs()
190 if link.Type() == "device" && len(attrs.HardwareAddr) > 0 {
191 if len(attrs.HardwareAddr) == 6 { // Ethernet
192 if attrs.Flags&net.FlagUp != net.FlagUp {
193 netlink.LinkSetUp(link) // Attempt to take up all ethernet links
194 }
195 ethernetLinks = append(ethernetLinks, link)
196 } else {
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100197 s.logger.Info("Ignoring non-Ethernet interface", zap.String("interface", attrs.Name))
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200198 }
Lorenz Brun45333b62019-11-11 15:26:27 +0100199 } else if link.Attrs().Name == "lo" {
200 if err := netlink.LinkSetUp(link); err != nil {
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100201 s.logger.Error("Failed to take up loopback interface", zap.Error(err))
Lorenz Brun45333b62019-11-11 15:26:27 +0100202 }
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200203 }
204 }
Serge Bazanskicdb8c782020-02-17 12:34:02 +0100205 if len(ethernetLinks) != 1 {
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100206 s.logger.Warn("Network service needs exactly one link, bailing")
207 } else {
208 link := ethernetLinks[0]
209 if err := s.useInterface(ctx, link); err != nil {
210 return fmt.Errorf("failed to bring up link %s: %w", link.Attrs().Name, err)
211 }
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200212 }
Serge Bazanskicdb8c782020-02-17 12:34:02 +0100213
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100214 supervisor.Signal(ctx, supervisor.SignalHealthy)
215 supervisor.Signal(ctx, supervisor.SignalDone)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200216 return nil
217}