blob: 414d97146053a2ce03d3123db94b2b5e90bd7c35 [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"
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020030 "golang.org/x/sys/unix"
Lorenz Brun52f7f292020-06-24 16:42:02 +020031
32 "git.monogon.dev/source/nexantic.git/core/internal/common/supervisor"
33 "git.monogon.dev/source/nexantic.git/core/internal/network/dhcp"
Lorenz Brunfa5c2fc2020-09-28 13:32:12 +020034 "git.monogon.dev/source/nexantic.git/core/internal/network/dns"
Serge Bazanskic7359672020-10-30 16:38:57 +010035 "git.monogon.dev/source/nexantic.git/core/pkg/logtree"
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020036)
37
38const (
39 resolvConfPath = "/etc/resolv.conf"
40 resolvConfSwapPath = "/etc/resolv.conf.new"
41)
42
43type Service struct {
Serge Bazanskicdb8c782020-02-17 12:34:02 +010044 config Config
Lorenz Brun52f7f292020-06-24 16:42:02 +020045 dhcp *dhcp.Client
Serge Bazanskicdb8c782020-02-17 12:34:02 +010046
Serge Bazanskic7359672020-10-30 16:38:57 +010047 logger logtree.LeveledLogger
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020048}
49
50type Config struct {
Lorenz Brunfa5c2fc2020-09-28 13:32:12 +020051 CorednsRegistrationChan chan *dns.ExtraDirective
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020052}
53
Serge Bazanskib1b742f2020-03-24 13:58:19 +010054func New(config Config) *Service {
55 return &Service{
Serge Bazanskicdb8c782020-02-17 12:34:02 +010056 config: config,
Lorenz Brun52f7f292020-06-24 16:42:02 +020057 dhcp: dhcp.New(),
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020058 }
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020059}
60
61func setResolvconf(nameservers []net.IP, searchDomains []string) error {
Leopold Schabel68c58752019-11-14 21:00:59 +010062 _ = os.Mkdir("/etc", 0755)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020063 newResolvConf, err := os.Create(resolvConfSwapPath)
64 if err != nil {
65 return err
66 }
67 defer newResolvConf.Close()
68 defer os.Remove(resolvConfSwapPath)
69 for _, ns := range nameservers {
70 if _, err := newResolvConf.WriteString(fmt.Sprintf("nameserver %v\n", ns)); err != nil {
71 return err
72 }
73 }
74 for _, searchDomain := range searchDomains {
75 if _, err := newResolvConf.WriteString(fmt.Sprintf("search %v", searchDomain)); err != nil {
76 return err
77 }
78 }
79 newResolvConf.Close()
80 // Atomically swap in new config
81 return unix.Rename(resolvConfSwapPath, resolvConfPath)
82}
83
Serge Bazanski1a5a6672020-02-18 10:09:43 +010084func (s *Service) addNetworkRoutes(link netlink.Link, addr net.IPNet, gw net.IP) error {
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020085 if err := netlink.AddrReplace(link, &netlink.Addr{IPNet: &addr}); err != nil {
Lorenz Brun52f7f292020-06-24 16:42:02 +020086 return fmt.Errorf("failed to add DHCP address to network interface \"%v\": %w", link.Attrs().Name, err)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020087 }
Serge Bazanski1a5a6672020-02-18 10:09:43 +010088
89 if gw.IsUnspecified() {
Serge Bazanskic7359672020-10-30 16:38:57 +010090 s.logger.Infof("No default route set, only local network %s will be reachable", addr.String())
Serge Bazanski1a5a6672020-02-18 10:09:43 +010091 return nil
92 }
93
94 route := &netlink.Route{
Lorenz Brun45333b62019-11-11 15:26:27 +010095 Dst: &net.IPNet{IP: net.IPv4(0, 0, 0, 0), Mask: net.IPv4Mask(0, 0, 0, 0)},
96 Gw: gw,
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020097 Scope: netlink.SCOPE_UNIVERSE,
Serge Bazanski1a5a6672020-02-18 10:09:43 +010098 }
99 if err := netlink.RouteAdd(route); err != nil {
100 return fmt.Errorf("could not add default route: netlink.RouteAdd(%+v): %v", route, err)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200101 }
102 return nil
103}
104
Lorenz Brunb682ba52020-07-08 14:51:36 +0200105// nfifname converts an interface name into 16 bytes padded with zeroes (for nftables)
106func nfifname(n string) []byte {
107 b := make([]byte, 16)
108 copy(b, []byte(n+"\x00"))
109 return b
110}
111
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100112func (s *Service) useInterface(ctx context.Context, iface netlink.Link) error {
Lorenz Brun52f7f292020-06-24 16:42:02 +0200113 err := supervisor.Run(ctx, "dhcp", s.dhcp.Run(iface))
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200114 if err != nil {
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100115 return err
116 }
Lorenz Brun52f7f292020-06-24 16:42:02 +0200117 status, err := s.dhcp.Status(ctx, true)
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100118 if err != nil {
Lorenz Brun52f7f292020-06-24 16:42:02 +0200119 return fmt.Errorf("could not get DHCP Status: %w", err)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200120 }
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100121
Lorenz Brunfa5c2fc2020-09-28 13:32:12 +0200122 // We're currently never removing this directive just like we're not removing routes and IPs
123 s.config.CorednsRegistrationChan <- dns.NewUpstreamDirective(status.DNS)
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100124
Lorenz Brun52f7f292020-06-24 16:42:02 +0200125 if err := s.addNetworkRoutes(iface, status.Address, status.Gateway); err != nil {
Serge Bazanskic7359672020-10-30 16:38:57 +0100126 s.logger.Warning("Failed to add routes: %v", err)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200127 }
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100128
Lorenz Brunb682ba52020-07-08 14:51:36 +0200129 c := nftables.Conn{}
130
131 nat := c.AddTable(&nftables.Table{
132 Family: nftables.TableFamilyIPv4,
133 Name: "nat",
134 })
135
136 postrouting := c.AddChain(&nftables.Chain{
137 Name: "postrouting",
138 Hooknum: nftables.ChainHookPostrouting,
139 Priority: nftables.ChainPriorityNATSource,
140 Table: nat,
141 Type: nftables.ChainTypeNAT,
142 })
143
144 // Masquerade/SNAT all traffic going out of the external interface
145 c.AddRule(&nftables.Rule{
146 Table: nat,
147 Chain: postrouting,
148 Exprs: []expr.Any{
149 &expr.Meta{Key: expr.MetaKeyOIFNAME, Register: 1},
150 &expr.Cmp{
151 Op: expr.CmpOpEq,
152 Register: 1,
153 Data: nfifname(iface.Attrs().Name),
154 },
155 &expr.Masq{},
156 },
157 })
158
159 if err := c.Flush(); err != nil {
160 panic(err)
161 }
162
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200163 return nil
164}
165
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100166// GetIP returns the current IP (and optionally waits for one to be assigned)
Serge Bazanskicdb8c782020-02-17 12:34:02 +0100167func (s *Service) GetIP(ctx context.Context, wait bool) (*net.IP, error) {
Lorenz Brun52f7f292020-06-24 16:42:02 +0200168 status, err := s.dhcp.Status(ctx, wait)
Serge Bazanskicdb8c782020-02-17 12:34:02 +0100169 if err != nil {
170 return nil, err
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100171 }
Lorenz Brun52f7f292020-06-24 16:42:02 +0200172 return &status.Address.IP, nil
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100173}
174
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100175func (s *Service) Run(ctx context.Context) error {
Lorenz Brunfa5c2fc2020-09-28 13:32:12 +0200176 logger := supervisor.Logger(ctx)
177 dnsSvc := dns.New(s.config.CorednsRegistrationChan)
178 supervisor.Run(ctx, "dns", dnsSvc.Run)
179 supervisor.Run(ctx, "interfaces", s.runInterfaces)
180
181 if err := ioutil.WriteFile("/proc/sys/net/ipv4/ip_forward", []byte("1\n"), 0644); err != nil {
Serge Bazanskic7359672020-10-30 16:38:57 +0100182 logger.Fatalf("Failed to enable IPv4 forwarding: %v", err)
Lorenz Brunfa5c2fc2020-09-28 13:32:12 +0200183 }
184
185 // We're handling all DNS requests with CoreDNS, including local ones
186 if err := setResolvconf([]net.IP{{127, 0, 0, 1}}, []string{}); err != nil {
Serge Bazanskic7359672020-10-30 16:38:57 +0100187 logger.Fatalf("Failed to set resolv.conf: %v", err)
Lorenz Brunfa5c2fc2020-09-28 13:32:12 +0200188 }
189
190 supervisor.Signal(ctx, supervisor.SignalHealthy)
191 supervisor.Signal(ctx, supervisor.SignalDone)
192 return nil
193}
194
195func (s *Service) runInterfaces(ctx context.Context) error {
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100196 s.logger = supervisor.Logger(ctx)
Lorenz Brunfa5c2fc2020-09-28 13:32:12 +0200197 s.logger.Info("Starting network interface management")
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100198
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200199 links, err := netlink.LinkList()
200 if err != nil {
Serge Bazanskic7359672020-10-30 16:38:57 +0100201 s.logger.Fatalf("Failed to list network links: %s", err)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200202 }
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100203
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200204 var ethernetLinks []netlink.Link
205 for _, link := range links {
206 attrs := link.Attrs()
207 if link.Type() == "device" && len(attrs.HardwareAddr) > 0 {
208 if len(attrs.HardwareAddr) == 6 { // Ethernet
209 if attrs.Flags&net.FlagUp != net.FlagUp {
210 netlink.LinkSetUp(link) // Attempt to take up all ethernet links
211 }
212 ethernetLinks = append(ethernetLinks, link)
213 } else {
Serge Bazanskic7359672020-10-30 16:38:57 +0100214 s.logger.Infof("Ignoring non-Ethernet interface %s", attrs.Name)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200215 }
Lorenz Brun45333b62019-11-11 15:26:27 +0100216 } else if link.Attrs().Name == "lo" {
217 if err := netlink.LinkSetUp(link); err != nil {
Serge Bazanskic7359672020-10-30 16:38:57 +0100218 s.logger.Errorf("Failed to bring up loopback interface: %v", err)
Lorenz Brun45333b62019-11-11 15:26:27 +0100219 }
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200220 }
221 }
Serge Bazanskicdb8c782020-02-17 12:34:02 +0100222 if len(ethernetLinks) != 1 {
Serge Bazanskic7359672020-10-30 16:38:57 +0100223 s.logger.Warningf("Network service needs exactly one link, bailing")
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100224 } else {
225 link := ethernetLinks[0]
226 if err := s.useInterface(ctx, link); err != nil {
227 return fmt.Errorf("failed to bring up link %s: %w", link.Attrs().Name, err)
228 }
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200229 }
Serge Bazanskicdb8c782020-02-17 12:34:02 +0100230
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100231 supervisor.Signal(ctx, supervisor.SignalHealthy)
232 supervisor.Signal(ctx, supervisor.SignalDone)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200233 return nil
234}