tailscale/wgengine/router/router_openbsd.go
Andrew Dunham 43dd4d1626 wgengine/router: add debug logging component logs
This uses the "component logging" feature to allow turning on additional
debug logs for the Router at runtime. Currently, it's only implemented
on Linux and with the CallbackRouter at this time.

Updates #13887

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: I251a7705765f950d118850f1e14f465729ba45c5
2024-10-22 15:27:54 -05:00

255 lines
6.2 KiB
Go

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package router
import (
"errors"
"fmt"
"log"
"net/netip"
"os/exec"
"github.com/tailscale/wireguard-go/tun"
"go4.org/netipx"
"tailscale.com/health"
"tailscale.com/net/netmon"
"tailscale.com/types/logger"
"tailscale.com/util/set"
)
// For now this router only supports the WireGuard userspace implementation.
// There is an experimental kernel version in the works for OpenBSD:
// https://git.zx2c4.com/wireguard-openbsd.
type openbsdRouter struct {
logf logger.Logf
netMon *netmon.Monitor
tunname string
local4 netip.Prefix
local6 netip.Prefix
routes set.Set[netip.Prefix]
}
func newUserspaceRouter(logf logger.Logf, tundev tun.Device, netMon *netmon.Monitor, health *health.Tracker) (Router, error) {
tunname, err := tundev.Name()
if err != nil {
return nil, err
}
return &openbsdRouter{
logf: logf,
netMon: netMon,
tunname: tunname,
}, nil
}
func cmd(args ...string) *exec.Cmd {
if len(args) == 0 {
log.Fatalf("exec.Cmd(%#v) invalid; need argv[0]", args)
}
return exec.Command(args[0], args[1:]...)
}
func (r *openbsdRouter) Up() error {
ifup := []string{"ifconfig", r.tunname, "up"}
if out, err := cmd(ifup...).CombinedOutput(); err != nil {
r.logf("running ifconfig failed: %v\n%s", err, out)
return err
}
return nil
}
func inet(p netip.Prefix) string {
if p.Addr().Is6() {
return "inet6"
}
return "inet"
}
func (r *openbsdRouter) Set(cfg *Config) error {
if cfg == nil {
cfg = &shutdownConfig
}
// TODO: support configuring multiple local addrs on interface.
if len(cfg.LocalAddrs) == 0 {
return nil
}
numIPv4 := 0
numIPv6 := 0
localAddr4 := netip.Prefix{}
localAddr6 := netip.Prefix{}
for _, addr := range cfg.LocalAddrs {
if addr.Addr().Is4() {
numIPv4++
localAddr4 = addr
}
if addr.Addr().Is6() {
numIPv6++
localAddr6 = addr
}
}
if numIPv4 > 1 || numIPv6 > 1 {
return errors.New("openbsd doesn't support setting multiple local addrs yet")
}
var errq error
if localAddr4 != r.local4 {
if r.local4.IsValid() {
addrdel := []string{"ifconfig", r.tunname,
"inet", r.local4.String(), "-alias"}
out, err := cmd(addrdel...).CombinedOutput()
if err != nil {
r.logf("addr del failed: %v: %v\n%s", addrdel, err, out)
if errq == nil {
errq = err
}
}
routedel := []string{"route", "-q", "-n",
"del", "-inet", r.local4.String(),
"-iface", r.local4.Addr().String()}
if out, err := cmd(routedel...).CombinedOutput(); err != nil {
r.logf("route del failed: %v: %v\n%s", routedel, err, out)
if errq == nil {
errq = err
}
}
}
if localAddr4.IsValid() {
addradd := []string{"ifconfig", r.tunname,
"inet", localAddr4.String(), "alias"}
out, err := cmd(addradd...).CombinedOutput()
if err != nil {
r.logf("addr add failed: %v: %v\n%s", addradd, err, out)
if errq == nil {
errq = err
}
}
routeadd := []string{"route", "-q", "-n",
"add", "-inet", localAddr4.String(),
"-iface", localAddr4.Addr().String()}
if out, err := cmd(routeadd...).CombinedOutput(); err != nil {
r.logf("route add failed: %v: %v\n%s", routeadd, err, out)
if errq == nil {
errq = err
}
}
}
}
if localAddr6.IsValid() {
// in https://github.com/tailscale/tailscale/issues/1307 we made
// FreeBSD use a /48 for IPv6 addresses, which is nice because we
// don't need to additionally add routing entries. Do that here too.
localAddr6 = netip.PrefixFrom(localAddr6.Addr(), 48)
}
if localAddr6 != r.local6 {
if r.local6.IsValid() {
addrdel := []string{"ifconfig", r.tunname,
"inet6", r.local6.String(), "delete"}
out, err := cmd(addrdel...).CombinedOutput()
if err != nil {
r.logf("addr del failed: %v: %v\n%s", addrdel, err, out)
if errq == nil {
errq = err
}
}
}
if localAddr6.IsValid() {
addradd := []string{"ifconfig", r.tunname,
"inet6", localAddr6.String()}
out, err := cmd(addradd...).CombinedOutput()
if err != nil {
r.logf("addr add failed: %v: %v\n%s", addradd, err, out)
if errq == nil {
errq = err
}
}
}
}
newRoutes := set.Set[netip.Prefix]{}
for _, route := range cfg.Routes {
newRoutes.Add(route)
}
for route := range r.routes {
if _, keep := newRoutes[route]; !keep {
net := netipx.PrefixIPNet(route)
nip := net.IP.Mask(net.Mask)
nstr := fmt.Sprintf("%v/%d", nip, route.Bits())
dst := localAddr4.Addr().String()
if route.Addr().Is6() {
dst = localAddr6.Addr().String()
}
routedel := []string{"route", "-q", "-n",
"del", "-" + inet(route), nstr,
"-iface", dst}
out, err := cmd(routedel...).CombinedOutput()
if err != nil {
r.logf("route del failed: %v: %v\n%s", routedel, err, out)
if errq == nil {
errq = err
}
}
}
}
for route := range newRoutes {
if _, exists := r.routes[route]; !exists {
net := netipx.PrefixIPNet(route)
nip := net.IP.Mask(net.Mask)
nstr := fmt.Sprintf("%v/%d", nip, route.Bits())
dst := localAddr4.Addr().String()
if route.Addr().Is6() {
dst = localAddr6.Addr().String()
}
routeadd := []string{"route", "-q", "-n",
"add", "-" + inet(route), nstr,
"-iface", dst}
out, err := cmd(routeadd...).CombinedOutput()
if err != nil {
r.logf("addr add failed: %v: %v\n%s", routeadd, err, out)
if errq == nil {
errq = err
}
}
}
}
r.local4 = localAddr4
r.local6 = localAddr6
r.routes = newRoutes
return errq
}
// UpdateMagicsockPort implements the Router interface. This implementation
// does nothing and returns nil because this router does not currently need
// to know what the magicsock UDP port is.
func (r *openbsdRouter) UpdateMagicsockPort(_ uint16, _ string) error {
return nil
}
// SetDebugLoggingEnabled implements the Router interface.
func (r *openbsdRouter) SetDebugLoggingEnabled(_ bool) {
// TODO(andrew-d): implement; see https://github.com/tailscale/tailscale/issues/13887
}
func (r *openbsdRouter) Close() error {
cleanUp(r.logf, r.tunname)
return nil
}
func cleanUp(logf logger.Logf, interfaceName string) {
out, err := cmd("ifconfig", interfaceName, "down").CombinedOutput()
if err != nil {
logf("ifconfig down: %v\n%s", err, out)
}
}