blob: e0bac79bfc5e625894d52cf1bf5eadd3d650a8be [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"
Lorenz Brundbac6cc2020-11-30 10:57:26 +010021 "errors"
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020022 "fmt"
Lorenz Brunf042e6f2020-06-24 16:46:09 +020023 "io/ioutil"
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020024 "net"
25 "os"
Lorenz Brundbac6cc2020-11-30 10:57:26 +010026 "sync"
27 "time"
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020028
Lorenz Brunb682ba52020-07-08 14:51:36 +020029 "github.com/google/nftables"
30 "github.com/google/nftables/expr"
Lorenz Brundbac6cc2020-11-30 10:57:26 +010031 "github.com/insomniacslk/dhcp/dhcpv4"
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020032 "github.com/vishvananda/netlink"
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020033 "golang.org/x/sys/unix"
Lorenz Brun52f7f292020-06-24 16:42:02 +020034
35 "git.monogon.dev/source/nexantic.git/core/internal/common/supervisor"
Lorenz Brunfa5c2fc2020-09-28 13:32:12 +020036 "git.monogon.dev/source/nexantic.git/core/internal/network/dns"
Lorenz Brundbac6cc2020-11-30 10:57:26 +010037 "git.monogon.dev/source/nexantic.git/core/pkg/dhcp4c"
38 dhcpcb "git.monogon.dev/source/nexantic.git/core/pkg/dhcp4c/callback"
Serge Bazanskic7359672020-10-30 16:38:57 +010039 "git.monogon.dev/source/nexantic.git/core/pkg/logtree"
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020040)
41
42const (
43 resolvConfPath = "/etc/resolv.conf"
44 resolvConfSwapPath = "/etc/resolv.conf.new"
45)
46
47type Service struct {
Serge Bazanskicdb8c782020-02-17 12:34:02 +010048 config Config
Lorenz Brundbac6cc2020-11-30 10:57:26 +010049 dhcp *dhcp4c.Client
50
51 // nftConn is a shared file descriptor handle to nftables, automatically initialized on first use.
52 nftConn nftables.Conn
53 natTable *nftables.Table
54 natPostroutingChain *nftables.Chain
55
56 // These are a temporary hack pending the removal of the GetIP interface
57 ipLock sync.Mutex
58 currentIPTmp net.IP
Serge Bazanskicdb8c782020-02-17 12:34:02 +010059
Serge Bazanskic7359672020-10-30 16:38:57 +010060 logger logtree.LeveledLogger
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020061}
62
63type Config struct {
Lorenz Brunfa5c2fc2020-09-28 13:32:12 +020064 CorednsRegistrationChan chan *dns.ExtraDirective
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020065}
66
Serge Bazanskib1b742f2020-03-24 13:58:19 +010067func New(config Config) *Service {
68 return &Service{
Serge Bazanskicdb8c782020-02-17 12:34:02 +010069 config: config,
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020070 }
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020071}
72
73func setResolvconf(nameservers []net.IP, searchDomains []string) error {
Leopold Schabel68c58752019-11-14 21:00:59 +010074 _ = os.Mkdir("/etc", 0755)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +020075 newResolvConf, err := os.Create(resolvConfSwapPath)
76 if err != nil {
77 return err
78 }
79 defer newResolvConf.Close()
80 defer os.Remove(resolvConfSwapPath)
81 for _, ns := range nameservers {
82 if _, err := newResolvConf.WriteString(fmt.Sprintf("nameserver %v\n", ns)); err != nil {
83 return err
84 }
85 }
86 for _, searchDomain := range searchDomains {
87 if _, err := newResolvConf.WriteString(fmt.Sprintf("search %v", searchDomain)); err != nil {
88 return err
89 }
90 }
91 newResolvConf.Close()
92 // Atomically swap in new config
93 return unix.Rename(resolvConfSwapPath, resolvConfPath)
94}
95
Lorenz Brunb682ba52020-07-08 14:51:36 +020096// nfifname converts an interface name into 16 bytes padded with zeroes (for nftables)
97func nfifname(n string) []byte {
98 b := make([]byte, 16)
99 copy(b, []byte(n+"\x00"))
100 return b
101}
102
Lorenz Brundbac6cc2020-11-30 10:57:26 +0100103func (s *Service) dhcpDNSCallback(old, new *dhcp4c.Lease) error {
104 oldServers := old.DNSServers()
105 newServers := new.DNSServers()
106 if newServers.Equal(oldServers) {
107 return nil // nothing to do
108 }
109 s.logger.Infof("Setting upstream DNS servers to %v", newServers)
110 s.config.CorednsRegistrationChan <- dns.NewUpstreamDirective(newServers)
111 return nil
112}
113
114// TODO(lorenz): Get rid of this once we have robust node resolution
115func (s *Service) getIPCallbackHack(old, new *dhcp4c.Lease) error {
116 if old == nil && new != nil {
117 s.ipLock.Lock()
118 s.currentIPTmp = new.AssignedIP
119 s.ipLock.Unlock()
120 }
121 return nil
122}
123
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100124func (s *Service) useInterface(ctx context.Context, iface netlink.Link) error {
Lorenz Brundbac6cc2020-11-30 10:57:26 +0100125 netIface, err := net.InterfaceByIndex(iface.Attrs().Index)
126 if err != nil {
127 return fmt.Errorf("cannot create Go net.Interface from netlink.Link: %w", err)
128 }
129 s.dhcp, err = dhcp4c.NewClient(netIface)
130 if err != nil {
131 return fmt.Errorf("failed to create DHCP client on interface %v: %w", iface.Attrs().Name, err)
132 }
133 s.dhcp.VendorClassIdentifier = "com.nexantic.smalltown.v1"
134 s.dhcp.RequestedOptions = []dhcpv4.OptionCode{dhcpv4.OptionRouter, dhcpv4.OptionNameServer}
135 s.dhcp.LeaseCallback = dhcpcb.Compose(dhcpcb.ManageIP(iface), dhcpcb.ManageDefaultRoute(iface), s.dhcpDNSCallback, s.getIPCallbackHack)
136 err = supervisor.Run(ctx, "dhcp", s.dhcp.Run)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200137 if err != nil {
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100138 return err
139 }
Lorenz Brunb682ba52020-07-08 14:51:36 +0200140
141 // Masquerade/SNAT all traffic going out of the external interface
Lorenz Brundbac6cc2020-11-30 10:57:26 +0100142 s.nftConn.AddRule(&nftables.Rule{
143 Table: s.natTable,
144 Chain: s.natPostroutingChain,
Lorenz Brunb682ba52020-07-08 14:51:36 +0200145 Exprs: []expr.Any{
146 &expr.Meta{Key: expr.MetaKeyOIFNAME, Register: 1},
147 &expr.Cmp{
148 Op: expr.CmpOpEq,
149 Register: 1,
150 Data: nfifname(iface.Attrs().Name),
151 },
152 &expr.Masq{},
153 },
154 })
155
Lorenz Brundbac6cc2020-11-30 10:57:26 +0100156 if err := s.nftConn.Flush(); err != nil {
Lorenz Brunb682ba52020-07-08 14:51:36 +0200157 panic(err)
158 }
159
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200160 return nil
161}
162
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100163// GetIP returns the current IP (and optionally waits for one to be assigned)
Serge Bazanskicdb8c782020-02-17 12:34:02 +0100164func (s *Service) GetIP(ctx context.Context, wait bool) (*net.IP, error) {
Lorenz Brundbac6cc2020-11-30 10:57:26 +0100165 for {
166 var currentIP net.IP
167 s.ipLock.Lock()
168 currentIP = s.currentIPTmp
169 s.ipLock.Unlock()
170 if currentIP == nil {
171 if !wait {
172 return nil, errors.New("no IP available")
173 }
174 select {
175 case <-ctx.Done():
176 return nil, ctx.Err()
177 case <-time.After(1 * time.Second):
178 continue
179 }
180 }
181 return &currentIP, nil
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100182 }
Lorenz Brunaa6b7342019-12-12 02:55:02 +0100183}
184
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100185func (s *Service) Run(ctx context.Context) error {
Lorenz Brunfa5c2fc2020-09-28 13:32:12 +0200186 logger := supervisor.Logger(ctx)
187 dnsSvc := dns.New(s.config.CorednsRegistrationChan)
188 supervisor.Run(ctx, "dns", dnsSvc.Run)
189 supervisor.Run(ctx, "interfaces", s.runInterfaces)
190
Lorenz Brundbac6cc2020-11-30 10:57:26 +0100191 s.natTable = s.nftConn.AddTable(&nftables.Table{
192 Family: nftables.TableFamilyIPv4,
193 Name: "nat",
194 })
195
196 s.natPostroutingChain = s.nftConn.AddChain(&nftables.Chain{
197 Name: "postrouting",
198 Hooknum: nftables.ChainHookPostrouting,
199 Priority: nftables.ChainPriorityNATSource,
200 Table: s.natTable,
201 Type: nftables.ChainTypeNAT,
202 })
203 if err := s.nftConn.Flush(); err != nil {
204 logger.Fatalf("Failed to set up nftables base chains: %v", err)
205 }
206
Lorenz Brunfa5c2fc2020-09-28 13:32:12 +0200207 if err := ioutil.WriteFile("/proc/sys/net/ipv4/ip_forward", []byte("1\n"), 0644); err != nil {
Serge Bazanskic7359672020-10-30 16:38:57 +0100208 logger.Fatalf("Failed to enable IPv4 forwarding: %v", err)
Lorenz Brunfa5c2fc2020-09-28 13:32:12 +0200209 }
210
211 // We're handling all DNS requests with CoreDNS, including local ones
212 if err := setResolvconf([]net.IP{{127, 0, 0, 1}}, []string{}); err != nil {
Serge Bazanskic7359672020-10-30 16:38:57 +0100213 logger.Fatalf("Failed to set resolv.conf: %v", err)
Lorenz Brunfa5c2fc2020-09-28 13:32:12 +0200214 }
215
216 supervisor.Signal(ctx, supervisor.SignalHealthy)
217 supervisor.Signal(ctx, supervisor.SignalDone)
218 return nil
219}
220
221func (s *Service) runInterfaces(ctx context.Context) error {
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100222 s.logger = supervisor.Logger(ctx)
Lorenz Brunfa5c2fc2020-09-28 13:32:12 +0200223 s.logger.Info("Starting network interface management")
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100224
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200225 links, err := netlink.LinkList()
226 if err != nil {
Serge Bazanskic7359672020-10-30 16:38:57 +0100227 s.logger.Fatalf("Failed to list network links: %s", err)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200228 }
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100229
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200230 var ethernetLinks []netlink.Link
231 for _, link := range links {
232 attrs := link.Attrs()
233 if link.Type() == "device" && len(attrs.HardwareAddr) > 0 {
234 if len(attrs.HardwareAddr) == 6 { // Ethernet
235 if attrs.Flags&net.FlagUp != net.FlagUp {
236 netlink.LinkSetUp(link) // Attempt to take up all ethernet links
237 }
238 ethernetLinks = append(ethernetLinks, link)
239 } else {
Serge Bazanskic7359672020-10-30 16:38:57 +0100240 s.logger.Infof("Ignoring non-Ethernet interface %s", attrs.Name)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200241 }
Lorenz Brun45333b62019-11-11 15:26:27 +0100242 } else if link.Attrs().Name == "lo" {
243 if err := netlink.LinkSetUp(link); err != nil {
Serge Bazanskic7359672020-10-30 16:38:57 +0100244 s.logger.Errorf("Failed to bring up loopback interface: %v", err)
Lorenz Brun45333b62019-11-11 15:26:27 +0100245 }
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200246 }
247 }
Serge Bazanskicdb8c782020-02-17 12:34:02 +0100248 if len(ethernetLinks) != 1 {
Serge Bazanskic7359672020-10-30 16:38:57 +0100249 s.logger.Warningf("Network service needs exactly one link, bailing")
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100250 } else {
251 link := ethernetLinks[0]
252 if err := s.useInterface(ctx, link); err != nil {
253 return fmt.Errorf("failed to bring up link %s: %w", link.Attrs().Name, err)
254 }
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200255 }
Serge Bazanskicdb8c782020-02-17 12:34:02 +0100256
Serge Bazanskib1b742f2020-03-24 13:58:19 +0100257 supervisor.Signal(ctx, supervisor.SignalHealthy)
258 supervisor.Signal(ctx, supervisor.SignalDone)
Hendrik Hofstadt0d7c91e2019-10-23 21:44:47 +0200259 return nil
260}