wg-quicker/wg.go

271 lines
7.4 KiB
Go
Raw Normal View History

2019-03-26 13:44:38 +01:00
package wgctl
import (
"bytes"
"encoding/base64"
"github.com/mdlayher/wireguardctrl"
"github.com/mdlayher/wireguardctrl/wgtypes"
2019-03-26 13:57:33 +01:00
"github.com/sirupsen/logrus"
2019-03-26 13:44:38 +01:00
"github.com/vishvananda/netlink"
"net"
"syscall"
"text/template"
)
type Config struct {
wgtypes.Config
2019-03-26 13:57:33 +01:00
// Address list of IP (v4 or v6) addresses (optionally with CIDR masks) to be assigned to the interface. May be specified multiple times.
2019-03-26 13:44:38 +01:00
Address []*net.IPNet
2019-03-26 13:57:33 +01:00
// list of IP (v4 or v6) addresses to be set as the interfaces DNS servers. May be specified multiple times. Upon bringing the interface up, this runs resolvconf -a tun.INTERFACE -m 0 -x and upon bringing it down, this runs resolvconf -d tun.INTERFACE. If these particular invocations of resolvconf(8) are undesirable, the PostUp and PostDown keys below may be used instead.
2019-03-26 14:01:02 +01:00
// Currently unsupported
2019-03-26 13:44:38 +01:00
DNS []net.IP
2019-03-26 13:57:33 +01:00
// —if not specified, the MTU is automatically determined from the endpoint addresses or the system default route, which is usually a sane choice. However, to manually specify an MTU to override this automatic discovery, this value may be specified explicitly.
2019-03-26 13:44:38 +01:00
MTU int
2019-03-26 14:01:02 +01:00
// Table — Controls the routing table to which routes are added.
2019-03-26 13:44:38 +01:00
Table int
// PreUp, PostUp, PreDown, PostDown — script snippets which will be executed by bash(1) before/after setting up/tearing down the interface, most commonly used to configure custom DNS options or firewall rules. The special string %i is expanded to INTERFACE. Each one may be specified multiple times, in which case the commands are executed in order.
2019-03-26 14:01:02 +01:00
// Currently unsupported
2019-03-26 13:44:38 +01:00
PreUp string
PostUp string
PreDown string
PostDown string
// SaveConfig — if set to true, the configuration is saved from the current state of the interface upon shutdown.
2019-03-26 14:01:02 +01:00
// Currently unsupported
2019-03-26 13:44:38 +01:00
SaveConfig bool
}
func (cfg *Config) String() string {
b, err := cfg.MarshalText()
if err != nil {
panic(err)
}
return string(b)
}
func (cfg *Config) MarshalText() (text []byte, err error) {
buff := &bytes.Buffer{}
if err := cfgTemplate.Execute(buff, cfg); err != nil {
return nil, err
}
return buff.Bytes(), nil
}
const wgtypeTemplateSpec = `[Interface]
{{- range := .Address }}
Address = {{ . }}
{{ end }}
{{- range := .DNS }}
DNS = {{ . }}
{{ end }}
PrivateKey = {{ .PrivateKey | wgKey }}
{{- if .ListenPort }}{{ "\n" }}ListenPort = {{ .ListenPort }}{{ end }}
{{- if .MTU }}{{ "\n" }}MTU = {{ .MTU }}{{ end }}
{{- if .Table }}{{ "\n" }}Table = {{ .Table }}{{ end }}
{{- if .PreUp }}{{ "\n" }}PreUp = {{ .PreUp }}{{ end }}
{{- if .PostUp }}{{ "\n" }}Table = {{ .Table }}{{ end }}
{{- if .PreDown }}{{ "\n" }}PreDown = {{ .PreDown }}{{ end }}
{{- if .PostDown }}{{ "\n" }}PostDown = {{ .PostDown }}{{ end }}
{{- if .SaveConfig }}{{ "\n" }}SaveConfig = {{ .SaveConfig }}{{ end }}
{{- range .Peers }}
[Peer]
PublicKey = {{ .PublicKey | wgKey }}
AllowedIps = {{ range $i, $el := .AllowedIPs }}{{if $i}}, {{ end }}{{ $el }}{{ end }}
{{- if .Endpoint }}{{ "\n" }}Endpoint = {{ .Endpoint }}{{ end }}
{{- end }}
`
func serializeKey(key *wgtypes.Key) string {
return base64.StdEncoding.EncodeToString(key[:])
}
2019-03-26 13:57:33 +01:00
// Parses base64 encoded key
2019-03-26 13:44:38 +01:00
func ParseKey(key string) (wgtypes.Key, error) {
var pkey wgtypes.Key
pkeySlice, err := base64.StdEncoding.DecodeString(key)
if err != nil {
return pkey, err
}
copy(pkey[:], pkeySlice[:])
return pkey, nil
}
var cfgTemplate = template.Must(
template.
New("wg-cfg").
Funcs(template.FuncMap(map[string]interface{}{"wgKey": serializeKey})).
Parse(wgtypeTemplateSpec))
2019-03-26 13:57:33 +01:00
// Sync the config to the current setup for given interface
func (cfg *Config) Sync(iface string, logger logrus.FieldLogger) error {
log := logger.WithField("iface", iface)
2019-03-26 13:44:38 +01:00
link, err := netlink.LinkByName(iface)
if err != nil {
if _, ok := err.(netlink.LinkNotFoundError); !ok {
2019-03-26 13:57:33 +01:00
log.WithError(err).Error("cannot read link")
2019-03-26 13:44:38 +01:00
return err
}
log.Info("link not found, creating")
wgLink := &netlink.GenericLink{
LinkAttrs: netlink.LinkAttrs{
Name: iface,
2019-03-26 13:58:26 +01:00
MTU: cfg.MTU,
2019-03-26 13:44:38 +01:00
},
LinkType: "wireguard",
}
if err := netlink.LinkAdd(wgLink); err != nil {
2019-03-26 13:57:33 +01:00
log.WithError(err).Error("cannot create link")
2019-03-26 13:44:38 +01:00
return err
}
link, err = netlink.LinkByName(iface)
if err != nil {
2019-03-26 13:57:33 +01:00
log.WithError(err).Error("cannot read link")
2019-03-26 13:44:38 +01:00
return err
}
}
if err := netlink.LinkSetUp(link); err != nil {
2019-03-26 13:57:33 +01:00
log.WithError(err).Error("cannot set link up")
2019-03-26 13:44:38 +01:00
return err
}
2019-03-26 13:57:33 +01:00
log.Info("set device up")
2019-03-26 13:44:38 +01:00
cl, err := wireguardctrl.New()
if err != nil {
log.Error(err, "cannot setup wireguard device")
return err
}
if err := cl.ConfigureDevice(iface, cfg.Config); err != nil {
2019-03-26 13:57:33 +01:00
log.WithError(err).Error("cannot configure device")
2019-03-26 13:44:38 +01:00
return err
}
2019-03-26 13:57:33 +01:00
if err := syncAddress(link, cfg, log); err != nil {
2019-03-26 13:44:38 +01:00
log.Error(err, "cannot sync addresses")
return err
}
2019-03-26 13:57:33 +01:00
if err := syncRoutes(link, cfg, log); err != nil {
2019-03-26 13:44:38 +01:00
log.Error(err, "cannot sync routes")
return err
}
log.Info("Successfully setup device", "iface", iface)
return nil
}
2019-03-26 13:57:33 +01:00
func syncAddress(link netlink.Link, cfg *Config, log logrus.FieldLogger) error {
2019-03-26 13:44:38 +01:00
addrs, err := netlink.AddrList(link, syscall.AF_INET)
if err != nil {
log.Error(err, "cannot read link address")
return err
}
presentAddresses := make(map[string]int, 0)
for _, addr := range addrs {
presentAddresses[addr.IPNet.String()] = 1
}
for _, addr := range cfg.Address {
2019-03-26 13:57:33 +01:00
log := log.WithField("addr", addr)
2019-03-26 13:44:38 +01:00
_, present := presentAddresses[addr.String()]
presentAddresses[addr.String()] = 2
if present {
2019-03-26 13:57:33 +01:00
log.Info("address present")
2019-03-26 13:44:38 +01:00
continue
}
if err := netlink.AddrAdd(link, &netlink.Addr{
IPNet: addr,
}); err != nil {
2019-03-26 13:57:33 +01:00
log.WithError(err).Error("cannot add addr")
2019-03-26 13:44:38 +01:00
return err
}
2019-03-26 13:57:33 +01:00
log.Info("address added")
2019-03-26 13:44:38 +01:00
}
for addr, p := range presentAddresses {
2019-03-26 13:57:33 +01:00
log := log.WithField("addr", addr)
2019-03-26 13:44:38 +01:00
if p < 2 {
nlAddr, err := netlink.ParseAddr(addr)
if err != nil {
2019-03-26 13:57:33 +01:00
log.WithError(err).Error("cannot parse del addr")
2019-03-26 13:44:38 +01:00
return err
}
if err := netlink.AddrAdd(link, nlAddr); err != nil {
2019-03-26 13:57:33 +01:00
log.WithError(err).Error("cannot delete addr")
2019-03-26 13:44:38 +01:00
return err
}
2019-03-26 13:57:33 +01:00
log.Info("addr deleted")
2019-03-26 13:44:38 +01:00
}
}
return nil
}
2019-03-26 13:57:33 +01:00
func syncRoutes(link netlink.Link, cfg *Config, log logrus.FieldLogger) error {
2019-03-26 13:44:38 +01:00
routes, err := netlink.RouteList(link, syscall.AF_INET)
if err != nil {
log.Error(err, "cannot read existing routes")
return err
}
presentRoutes := make(map[string]int, 0)
for _, r := range routes {
2019-03-26 14:01:02 +01:00
if r.Table == cfg.Table {
presentRoutes[r.Dst.String()] = 1
}
2019-03-26 13:44:38 +01:00
}
for _, peer := range cfg.Peers {
for _, rt := range peer.AllowedIPs {
_, present := presentRoutes[rt.String()]
presentRoutes[rt.String()] = 2
2019-03-26 13:57:33 +01:00
log := log.WithField("route", rt.String())
2019-03-26 13:44:38 +01:00
if present {
2019-03-26 13:57:33 +01:00
log.Info("route present")
2019-03-26 13:44:38 +01:00
continue
}
if err := netlink.RouteAdd(&netlink.Route{
LinkIndex: link.Attrs().Index,
Dst: &rt,
2019-03-26 14:01:02 +01:00
Table: cfg.Table,
2019-03-26 13:44:38 +01:00
}); err != nil {
2019-03-26 13:57:33 +01:00
log.WithError(err).Error("cannot setup route")
2019-03-26 13:44:38 +01:00
return err
}
2019-03-26 13:57:33 +01:00
log.Info("route added")
2019-03-26 13:44:38 +01:00
}
}
// Clean extra routes
for rtStr, p := range presentRoutes {
_, rt, err := net.ParseCIDR(rtStr)
2019-03-26 13:57:33 +01:00
log := log.WithField("route", rt.String())
2019-03-26 13:44:38 +01:00
if err != nil {
2019-03-26 13:57:33 +01:00
log.WithError(err).Error("cannot parse route")
2019-03-26 13:44:38 +01:00
return err
}
if p < 2 {
2019-03-26 13:57:33 +01:00
log.Info("extra manual route found")
2019-03-26 13:44:38 +01:00
if err := netlink.RouteDel(&netlink.Route{
LinkIndex: link.Attrs().Index,
Dst: rt,
2019-03-26 14:01:02 +01:00
Table: cfg.Table,
2019-03-26 13:44:38 +01:00
}); err != nil {
2019-03-26 13:57:33 +01:00
log.WithError(err).Error("cannot setup route")
2019-03-26 13:44:38 +01:00
return err
}
2019-03-26 13:57:33 +01:00
log.Info("route deleted")
2019-03-26 13:44:38 +01:00
}
}
return nil
}