mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-02-03 22:18:39 +00:00
all: add freebsd supports
This commit add support for ctrld to run on freebsd, supported platforms are amd64/arm64/armv6/armv7,386. Supporting freebsd also requires adding debian and openresolv resolvconf. Updates #47
This commit is contained in:
committed by
Cuong Manh Le
parent
4172fc09d0
commit
4c2d21a8f8
@@ -197,8 +197,7 @@ func initCLI() {
|
||||
setDependencies(sc)
|
||||
sc.Arguments = append([]string{"run"}, osArgs...)
|
||||
if dir, err := os.UserHomeDir(); err == nil {
|
||||
// WorkingDirectory is not supported on Windows.
|
||||
sc.WorkingDirectory = dir
|
||||
setWorkingDirectory(sc, dir)
|
||||
// No config path, generating config in HOME directory.
|
||||
noConfigStart := isNoConfigStart(cmd)
|
||||
writeDefaultConfig := !noConfigStart && configBase64 == ""
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-systemd/v22/dbus"
|
||||
@@ -24,10 +23,6 @@ systemd-resolved=false
|
||||
var networkManagerCtrldConfFile = filepath.Join(nmConfDir, nmCtrldConfFilename)
|
||||
|
||||
func setupNetworkManager() error {
|
||||
if runtime.GOOS != "linux" {
|
||||
mainLog.Debug().Msg("skipping NetworkManager setup, not on Linux")
|
||||
return nil
|
||||
}
|
||||
if content, _ := os.ReadFile(nmCtrldConfContent); string(content) == nmCtrldConfContent {
|
||||
mainLog.Debug().Msg("NetworkManager already setup, nothing to do")
|
||||
return nil
|
||||
@@ -48,10 +43,6 @@ func setupNetworkManager() error {
|
||||
}
|
||||
|
||||
func restoreNetworkManager() error {
|
||||
if runtime.GOOS != "linux" {
|
||||
mainLog.Debug().Msg("skipping NetworkManager restoring, not on Linux")
|
||||
return nil
|
||||
}
|
||||
err := os.Remove(networkManagerCtrldConfFile)
|
||||
if os.IsNotExist(err) {
|
||||
mainLog.Debug().Msg("NetworkManager is not available")
|
||||
13
cmd/ctrld/network_manager_others.go
Normal file
13
cmd/ctrld/network_manager_others.go
Normal file
@@ -0,0 +1,13 @@
|
||||
//go:build !linux
|
||||
|
||||
package main
|
||||
|
||||
func setupNetworkManager() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func restoreNetworkManager() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func reloadNetworkManager() {}
|
||||
@@ -1,6 +1,3 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
47
cmd/ctrld/os_freebsd.go
Normal file
47
cmd/ctrld/os_freebsd.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/Control-D-Inc/ctrld/internal/dns"
|
||||
"github.com/Control-D-Inc/ctrld/internal/resolvconffile"
|
||||
)
|
||||
|
||||
// set the dns server for the provided network interface
|
||||
func setDNS(iface *net.Interface, nameservers []string) error {
|
||||
r, err := dns.NewOSConfigurator(logf, iface.Name)
|
||||
if err != nil {
|
||||
mainLog.Error().Err(err).Msg("failed to create DNS OS configurator")
|
||||
return err
|
||||
}
|
||||
|
||||
ns := make([]netip.Addr, 0, len(nameservers))
|
||||
for _, nameserver := range nameservers {
|
||||
ns = append(ns, netip.MustParseAddr(nameserver))
|
||||
}
|
||||
|
||||
if err := r.SetDNS(dns.OSConfig{Nameservers: ns}); err != nil {
|
||||
mainLog.Error().Err(err).Msg("failed to set DNS")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func resetDNS(iface *net.Interface) error {
|
||||
r, err := dns.NewOSConfigurator(logf, iface.Name)
|
||||
if err != nil {
|
||||
mainLog.Error().Err(err).Msg("failed to create DNS OS configurator")
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.Close(); err != nil {
|
||||
mainLog.Error().Err(err).Msg("failed to rollback DNS setting")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func currentDNS(_ *net.Interface) []string {
|
||||
return resolvconffile.NameServers("")
|
||||
}
|
||||
13
cmd/ctrld/os_others.go
Normal file
13
cmd/ctrld/os_others.go
Normal file
@@ -0,0 +1,13 @@
|
||||
//go:build !linux && !darwin
|
||||
|
||||
package main
|
||||
|
||||
// TODO(cuonglm): implement.
|
||||
func allocateIP(ip string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO(cuonglm): implement.
|
||||
func deAllocateIP(ip string) error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,6 +1,3 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -14,16 +11,6 @@ import (
|
||||
ctrldnet "github.com/Control-D-Inc/ctrld/internal/net"
|
||||
)
|
||||
|
||||
// TODO(cuonglm): implement.
|
||||
func allocateIP(ip string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO(cuonglm): implement.
|
||||
func deAllocateIP(ip string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func setDNS(iface *net.Interface, nameservers []string) error {
|
||||
if len(nameservers) == 0 {
|
||||
return errors.New("empty DNS nameservers")
|
||||
|
||||
20
cmd/ctrld/prog_freebsd.go
Normal file
20
cmd/ctrld/prog_freebsd.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/kardianos/service"
|
||||
)
|
||||
|
||||
func (p *prog) preRun() {
|
||||
if !service.Interactive() {
|
||||
p.setDNS()
|
||||
}
|
||||
}
|
||||
|
||||
func setDependencies(svc *service.Config) {
|
||||
// TODO(cuonglm): remove once https://github.com/kardianos/service/issues/359 fixed.
|
||||
_ = os.MkdirAll("/usr/local/etc/rc.d", 0755)
|
||||
}
|
||||
|
||||
func setWorkingDirectory(svc *service.Config, dir string) {}
|
||||
@@ -18,3 +18,7 @@ func setDependencies(svc *service.Config) {
|
||||
"After=NetworkManager-wait-online.service",
|
||||
}
|
||||
}
|
||||
|
||||
func setWorkingDirectory(svc *service.Config, dir string) {
|
||||
svc.WorkingDirectory = dir
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build !linux
|
||||
// +build !linux
|
||||
//go:build !linux && !freebsd
|
||||
|
||||
package main
|
||||
|
||||
@@ -8,3 +7,8 @@ import "github.com/kardianos/service"
|
||||
func (p *prog) preRun() {}
|
||||
|
||||
func setDependencies(svc *service.Config) {}
|
||||
|
||||
func setWorkingDirectory(svc *service.Config, dir string) {
|
||||
// WorkingDirectory is not supported on Windows.
|
||||
svc.WorkingDirectory = dir
|
||||
}
|
||||
|
||||
153
internal/dns/debian_resolvconf.go
Normal file
153
internal/dns/debian_resolvconf.go
Normal file
@@ -0,0 +1,153 @@
|
||||
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux || freebsd || openbsd
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"tailscale.com/atomicfile"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
//go:embed resolvconf-workaround.sh
|
||||
var workaroundScript []byte
|
||||
|
||||
// resolvconfConfigName is the name of the config submitted to
|
||||
// resolvconf.
|
||||
// The name starts with 'tun' in order to match the hardcoded
|
||||
// interface order in debian resolvconf, which will place this
|
||||
// configuration ahead of regular network links. In theory, this
|
||||
// doesn't matter because we then fix things up to ensure our config
|
||||
// is the only one in use, but in case that fails, this will make our
|
||||
// configuration slightly preferred.
|
||||
// The 'inet' suffix has no specific meaning, but conventionally
|
||||
// resolvconf implementations encourage adding a suffix roughly
|
||||
// indicating where the config came from, and "inet" is the "none of
|
||||
// the above" value (rather than, say, "ppp" or "dhcp").
|
||||
const resolvconfConfigName = "ctrld.inet"
|
||||
|
||||
// resolvconfLibcHookPath is the directory containing libc update
|
||||
// scripts, which are run by Debian resolvconf when /etc/resolv.conf
|
||||
// has been updated.
|
||||
const resolvconfLibcHookPath = "/etc/resolvconf/update-libc.d"
|
||||
|
||||
// resolvconfHookPath is the name of the libc hook script we install
|
||||
// to force Ctrld's DNS config to take effect.
|
||||
var resolvconfHookPath = filepath.Join(resolvconfLibcHookPath, "ctrld")
|
||||
|
||||
// resolvconfManager manages DNS configuration using the Debian
|
||||
// implementation of the `resolvconf` program, written by Thomas Hood.
|
||||
type resolvconfManager struct {
|
||||
logf logger.Logf
|
||||
listRecordsPath string
|
||||
interfacesDir string
|
||||
scriptInstalled bool // libc update script has been installed
|
||||
}
|
||||
|
||||
var _ OSConfigurator = (*resolvconfManager)(nil)
|
||||
|
||||
func newDebianResolvconfManager(logf logger.Logf) (*resolvconfManager, error) {
|
||||
ret := &resolvconfManager{
|
||||
logf: logf,
|
||||
listRecordsPath: "/lib/resolvconf/list-records",
|
||||
interfacesDir: "/etc/resolvconf/run/interface", // panic fallback if nothing seems to work
|
||||
}
|
||||
|
||||
if _, err := os.Stat(ret.listRecordsPath); os.IsNotExist(err) {
|
||||
// This might be a Debian system from before the big /usr
|
||||
// merge, try /usr instead.
|
||||
ret.listRecordsPath = "/usr" + ret.listRecordsPath
|
||||
}
|
||||
// The runtime directory is currently (2020-04) canonically
|
||||
// /etc/resolvconf/run, but the manpage is making noise about
|
||||
// switching to /run/resolvconf and dropping the /etc path. So,
|
||||
// let's probe the possible directories and use the first one
|
||||
// that works.
|
||||
for _, path := range []string{
|
||||
"/etc/resolvconf/run/interface",
|
||||
"/run/resolvconf/interface",
|
||||
"/var/run/resolvconf/interface",
|
||||
} {
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
ret.interfacesDir = path
|
||||
break
|
||||
}
|
||||
}
|
||||
if ret.interfacesDir == "" {
|
||||
// None of the paths seem to work, use the canonical location
|
||||
// that the current manpage says to use.
|
||||
ret.interfacesDir = "/etc/resolvconf/run/interfaces"
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (m *resolvconfManager) deleteCtrldConfig() error {
|
||||
cmd := exec.Command("resolvconf", "-d", resolvconfConfigName)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("running %s: %s", cmd, out)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *resolvconfManager) SetDNS(config OSConfig) error {
|
||||
if !m.scriptInstalled {
|
||||
m.logf("injecting resolvconf workaround script")
|
||||
if err := os.MkdirAll(resolvconfLibcHookPath, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := atomicfile.WriteFile(resolvconfHookPath, workaroundScript, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
m.scriptInstalled = true
|
||||
}
|
||||
|
||||
if config.IsZero() {
|
||||
if err := m.deleteCtrldConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
stdin := new(bytes.Buffer)
|
||||
writeResolvConf(stdin, config.Nameservers, config.SearchDomains) // dns_direct.go
|
||||
|
||||
// This resolvconf implementation doesn't support exclusive
|
||||
// mode or interface priorities, so it will end up blending
|
||||
// our configuration with other sources. However, this will
|
||||
// get fixed up by the script we injected above.
|
||||
cmd := exec.Command("resolvconf", "-a", resolvconfConfigName)
|
||||
cmd.Stdin = stdin
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("running %s: %s", cmd, out)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *resolvconfManager) Close() error {
|
||||
if err := m.deleteCtrldConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if m.scriptInstalled {
|
||||
m.logf("removing resolvconf workaround script")
|
||||
os.Remove(resolvconfHookPath) // Best-effort
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *resolvconfManager) Mode() string {
|
||||
return "resolvconf"
|
||||
}
|
||||
@@ -144,6 +144,10 @@ type directManager struct {
|
||||
lastWarnContents []byte // last resolv.conf contents that we warned about
|
||||
}
|
||||
|
||||
func newDirectManager(logf logger.Logf) *directManager {
|
||||
return newDirectManagerOnFS(logf, directFS{})
|
||||
}
|
||||
|
||||
func newDirectManagerOnFS(logf logger.Logf, fs wholeFileFS) *directManager {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
m := &directManager{
|
||||
|
||||
39
internal/dns/manager_freebsd.go
Normal file
39
internal/dns/manager_freebsd.go
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
func NewOSConfigurator(logf logger.Logf, _ string) (OSConfigurator, error) {
|
||||
bs, err := os.ReadFile("/etc/resolv.conf")
|
||||
if os.IsNotExist(err) {
|
||||
return newDirectManager(logf), nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading /etc/resolv.conf: %w", err)
|
||||
}
|
||||
|
||||
switch resolvOwner(bs) {
|
||||
case "resolvconf":
|
||||
switch resolvconfStyle() {
|
||||
case "":
|
||||
return newDirectManager(logf), nil
|
||||
case "debian":
|
||||
return newDebianResolvconfManager(logf)
|
||||
case "openresolv":
|
||||
return newOpenresolvManager()
|
||||
default:
|
||||
logf("[unexpected] got unknown flavor of resolvconf %q, falling back to direct manager", resolvconfStyle())
|
||||
return newDirectManager(logf), nil
|
||||
}
|
||||
default:
|
||||
return newDirectManager(logf), nil
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,8 @@ type nmManager struct {
|
||||
dnsManager dbus.BusObject
|
||||
}
|
||||
|
||||
var _ OSConfigurator = (*nmManager)(nil)
|
||||
|
||||
func newNMManager(interfaceName string) (*nmManager, error) {
|
||||
conn, err := dbus.SystemBus()
|
||||
if err != nil {
|
||||
|
||||
57
internal/dns/openresolv.go
Normal file
57
internal/dns/openresolv.go
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux || freebsd || openbsd
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// openresolvManager manages DNS configuration using the openresolv
|
||||
// implementation of the `resolvconf` program.
|
||||
type openresolvManager struct{}
|
||||
|
||||
var _ OSConfigurator = (*openresolvManager)(nil)
|
||||
|
||||
func newOpenresolvManager() (openresolvManager, error) {
|
||||
return openresolvManager{}, nil
|
||||
}
|
||||
|
||||
func (m openresolvManager) deleteTailscaleConfig() error {
|
||||
cmd := exec.Command("resolvconf", "-f", "-d", "ctrld")
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("running %s: %s", cmd, out)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m openresolvManager) SetDNS(config OSConfig) error {
|
||||
if config.IsZero() {
|
||||
return m.deleteTailscaleConfig()
|
||||
}
|
||||
|
||||
var stdin bytes.Buffer
|
||||
writeResolvConf(&stdin, config.Nameservers, config.SearchDomains)
|
||||
|
||||
cmd := exec.Command("resolvconf", "-m", "0", "-x", "-a", "ctrld")
|
||||
cmd.Stdin = &stdin
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("running %s: %s", cmd, out)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m openresolvManager) Close() error {
|
||||
return m.deleteTailscaleConfig()
|
||||
}
|
||||
|
||||
func (m openresolvManager) Mode() string {
|
||||
return "resolvconf"
|
||||
}
|
||||
@@ -13,6 +13,8 @@ import (
|
||||
"tailscale.com/util/dnsname"
|
||||
)
|
||||
|
||||
var _ OSConfigurator = (*directManager)(nil)
|
||||
|
||||
// An OSConfigurator applies DNS settings to the operating system.
|
||||
type OSConfigurator interface {
|
||||
// SetDNS updates the OS's DNS configuration to match cfg.
|
||||
|
||||
63
internal/dns/resolvconf-workaround.sh
Normal file
63
internal/dns/resolvconf-workaround.sh
Normal file
@@ -0,0 +1,63 @@
|
||||
#!/bin/sh
|
||||
# Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
#
|
||||
# This script is a workaround for a vpn-unfriendly behavior of the
|
||||
# original resolvconf by Thomas Hood. Unlike the `openresolv`
|
||||
# implementation (whose binary is also called resolvconf,
|
||||
# confusingly), the original resolvconf lacks a way to specify
|
||||
# "exclusive mode" for a provider configuration. In practice, this
|
||||
# means that if Ctrld wants to install a DNS configuration, that
|
||||
# config will get "blended" with the configs from other sources,
|
||||
# rather than override those other sources.
|
||||
#
|
||||
# This script gets installed at /etc/resolvconf/update-libc.d, which
|
||||
# is a directory of hook scripts that get run after resolvconf's libc
|
||||
# helper has finished rewriting /etc/resolv.conf. It's meant to notify
|
||||
# consumers of resolv.conf of a new configuration.
|
||||
#
|
||||
# Instead, we use that hook mechanism to reach into resolvconf's
|
||||
# stuff, and rewrite the libc-generated resolv.conf to exclusively
|
||||
# contain Ctrld's configuration - effectively implementing
|
||||
# exclusive mode ourselves in post-production.
|
||||
|
||||
set -e
|
||||
|
||||
if [ -n "$CTRLD_RESOLVCONF_HOOK_LOOP" ]; then
|
||||
# Hook script being invoked by itself, skip.
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ ! -f ctrld.inet ]; then
|
||||
# Ctrld isn't trying to manage DNS, do nothing.
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! grep resolvconf /etc/resolv.conf >/dev/null; then
|
||||
# resolvconf isn't managing /etc/resolv.conf, do nothing.
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Write out a modified /etc/resolv.conf containing just our config.
|
||||
(
|
||||
if [ -f /etc/resolvconf/resolv.conf.d/head ]; then
|
||||
cat /etc/resolvconf/resolv.conf.d/head
|
||||
fi
|
||||
echo "# Ctrld workaround applied to set exclusive DNS configuration."
|
||||
cat tun-tailscale.inet
|
||||
if [ -f /etc/resolvconf/resolv.conf.d/base ]; then
|
||||
# Keep options and sortlist, discard other base things since
|
||||
# they're the things we're trying to override.
|
||||
grep -e 'sortlist ' -e 'options ' /etc/resolvconf/resolv.conf.d/base || true
|
||||
fi
|
||||
if [ -f /etc/resolvconf/resolv.conf.d/tail ]; then
|
||||
cat /etc/resolvconf/resolv.conf.d/tail
|
||||
fi
|
||||
) >/etc/resolv.conf
|
||||
|
||||
if [ -d /etc/resolvconf/update-libc.d ] ; then
|
||||
# Re-notify libc watchers that we've changed resolv.conf again.
|
||||
export CTRLD_RESOLVCONF_HOOK_LOOP=1
|
||||
exec run-parts /etc/resolvconf/update-libc.d
|
||||
fi
|
||||
@@ -105,6 +105,8 @@ type resolvedManager struct {
|
||||
newManager func(conn *dbus.Conn) dbus.BusObject
|
||||
}
|
||||
|
||||
var _ OSConfigurator = (*resolvedManager)(nil)
|
||||
|
||||
func newResolvedManager(logf logger.Logf, interfaceName string) (*resolvedManager, error) {
|
||||
iface, err := net.InterfaceByName(interfaceName)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user