2019-03-27 17:26:09 +01:00
package wgquick
2019-03-26 13:44:38 +01:00
import (
2019-03-28 12:38:22 +01:00
"bytes"
"fmt"
2019-03-29 11:01:22 +01:00
"net"
2019-03-28 12:38:22 +01:00
"os"
"os/exec"
"strings"
2019-03-27 17:26:09 +01:00
"syscall"
2019-03-29 10:48:33 +01:00
"github.com/sirupsen/logrus"
"github.com/vishvananda/netlink"
2019-03-29 12:59:45 +01:00
"golang.org/x/sys/unix"
2019-10-30 18:04:10 +01:00
"golang.zx2c4.com/wireguard/wgctrl"
2019-03-27 17:40:06 +01:00
)
2019-03-28 12:38:22 +01:00
// Up sets and configures the wg interface. Mostly equivalent to `wg-quick up iface`
func Up ( cfg * Config , iface string , logger logrus . FieldLogger ) error {
log := logger . WithField ( "iface" , iface )
_ , err := netlink . LinkByName ( iface )
if err == nil {
return os . ErrExist
}
if _ , ok := err . ( netlink . LinkNotFoundError ) ; ! ok {
return err
}
for _ , dns := range cfg . DNS {
if err := execSh ( "resolvconf -a tun.%i -m 0 -x" , iface , log , fmt . Sprintf ( "nameserver %s\n" , dns ) ) ; err != nil {
return err
}
}
2019-03-28 13:58:11 +01:00
if cfg . PreUp != "" {
if err := execSh ( cfg . PreUp , iface , log ) ; err != nil {
return err
}
log . Infoln ( "applied pre-up command" )
2019-03-28 12:38:22 +01:00
}
if err := Sync ( cfg , iface , logger ) ; err != nil {
return err
}
2019-03-28 13:58:11 +01:00
if cfg . PostUp != "" {
if err := execSh ( cfg . PostUp , iface , log ) ; err != nil {
return err
}
log . Infoln ( "applied post-up command" )
2019-03-28 12:38:22 +01:00
}
return nil
}
2019-03-28 12:43:34 +01:00
// Down destroys the wg interface. Mostly equivalent to `wg-quick down iface`
2019-03-28 12:38:22 +01:00
func Down ( cfg * Config , iface string , logger logrus . FieldLogger ) error {
log := logger . WithField ( "iface" , iface )
link , err := netlink . LinkByName ( iface )
if err != nil {
return err
}
if len ( cfg . DNS ) > 1 {
if err := execSh ( "resolvconf -d tun.%s" , iface , log ) ; err != nil {
return err
}
}
2019-03-28 13:58:11 +01:00
if cfg . PreDown != "" {
if err := execSh ( cfg . PreDown , iface , log ) ; err != nil {
return err
}
log . Infoln ( "applied pre-down command" )
2019-03-28 12:38:22 +01:00
}
2019-03-28 13:58:11 +01:00
2019-03-28 12:38:22 +01:00
if err := netlink . LinkDel ( link ) ; err != nil {
return err
}
log . Infoln ( "link deleted" )
2019-03-28 13:58:11 +01:00
if cfg . PostDown != "" {
if err := execSh ( cfg . PostDown , iface , log ) ; err != nil {
return err
}
log . Infoln ( "applied post-down command" )
2019-03-28 12:38:22 +01:00
}
return nil
}
func execSh ( command string , iface string , log logrus . FieldLogger , stdin ... string ) error {
cmd := exec . Command ( "sh" , "-ce" , strings . ReplaceAll ( command , "%i" , iface ) )
if len ( stdin ) > 0 {
log = log . WithField ( "stdin" , strings . Join ( stdin , "" ) )
b := & bytes . Buffer { }
for _ , ln := range stdin {
if _ , err := fmt . Fprint ( b , ln ) ; err != nil {
return err
}
}
cmd . Stdin = b
}
out , err := cmd . CombinedOutput ( )
if err != nil {
log . WithError ( err ) . Errorf ( "failed to execute %s:\n%s" , cmd . Args , out )
return err
}
log . Infof ( "executed %s:\n%s" , cmd . Args , out )
return nil
}
2019-03-26 13:57:33 +01:00
// Sync the config to the current setup for given interface
2019-03-29 11:01:22 +01:00
// It perform 4 operations:
// * SyncLink --> makes sure link is up and type wireguard
// * SyncWireguardDevice --> configures allowedIP & other wireguard specific settings
// * SyncAddress --> synces linux addresses bounded to this interface
// * SyncRoutes --> synces all allowedIP routes to route to this interface
2019-03-28 12:38:22 +01:00
func Sync ( cfg * Config , iface string , logger logrus . FieldLogger ) error {
2019-03-26 13:57:33 +01:00
log := logger . WithField ( "iface" , iface )
2019-03-28 12:38:22 +01:00
link , err := SyncLink ( cfg , iface , log )
if err != nil {
log . WithError ( err ) . Errorln ( "cannot sync wireguard link" )
return err
}
log . Info ( "synced link" )
if err := SyncWireguardDevice ( cfg , link , log ) ; err != nil {
log . WithError ( err ) . Errorln ( "cannot sync wireguard link" )
return err
}
log . Info ( "synced link" )
if err := SyncAddress ( cfg , link , log ) ; err != nil {
log . WithError ( err ) . Errorln ( "cannot sync addresses" )
return err
}
log . Info ( "synced addresss" )
2019-03-29 11:01:22 +01:00
var managedRoutes [ ] net . IPNet
for _ , peer := range cfg . Peers {
for _ , rt := range peer . AllowedIPs {
managedRoutes = append ( managedRoutes , rt )
}
}
if err := SyncRoutes ( cfg , link , managedRoutes , log ) ; err != nil {
2019-03-28 12:38:22 +01:00
log . WithError ( err ) . Errorln ( "cannot sync routes" )
return err
}
log . Info ( "synced routed" )
log . Info ( "Successfully synced device" )
return nil
}
2019-03-29 11:01:22 +01:00
// SyncWireguardDevice synces wireguard vpn setting on the given link. It does not set routes/addresses beyond wg internal crypto-key routing, only handles wireguard specific settings
2019-03-28 12:38:22 +01:00
func SyncWireguardDevice ( cfg * Config , link netlink . Link , log logrus . FieldLogger ) error {
2019-10-30 18:04:10 +01:00
cl , err := wgctrl . New ( )
2019-03-28 12:38:22 +01:00
if err != nil {
log . WithError ( err ) . Errorln ( "cannot setup wireguard device" )
return err
}
if err := cl . ConfigureDevice ( link . Attrs ( ) . Name , cfg . Config ) ; err != nil {
log . WithError ( err ) . Error ( "cannot configure device" )
return err
}
return nil
}
// SyncLink synces link state with the config. It does not sync Wireguard settings, just makes sure the device is up and type wireguard
func SyncLink ( cfg * Config , iface string , log logrus . FieldLogger ) ( netlink . Link , error ) {
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-28 12:38:22 +01:00
return nil , err
2019-03-26 13:44:38 +01:00
}
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-28 12:38:22 +01:00
return nil , err
2019-03-26 13:44:38 +01:00
}
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-28 12:38:22 +01:00
return nil , err
2019-03-26 13:44:38 +01:00
}
}
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-28 12:38:22 +01:00
return nil , err
2019-03-26 13:44:38 +01:00
}
2019-03-26 13:57:33 +01:00
log . Info ( "set device up" )
2019-03-28 12:38:22 +01:00
return link , nil
2019-03-26 13:44:38 +01:00
}
2019-03-28 12:38:22 +01:00
// SyncAddress adds/deletes all lind assigned IPV4 addressed as specified in the config
func SyncAddress ( cfg * Config , link netlink . Link , 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
}
2019-03-27 17:40:06 +01:00
// nil addr means I've used it
2019-03-29 10:48:33 +01:00
presentAddresses := make ( map [ string ] netlink . Addr , 0 )
2019-03-26 13:44:38 +01:00
for _ , addr := range addrs {
2019-03-28 14:39:45 +01:00
log . WithFields ( map [ string ] interface { } {
2019-03-29 10:48:33 +01:00
"addr" : fmt . Sprint ( addr . IPNet ) ,
2019-03-28 14:39:45 +01:00
"label" : addr . Label ,
2019-03-29 10:48:33 +01:00
} ) . Debugf ( "found existing address: %v" , addr )
presentAddresses [ addr . IPNet . String ( ) ] = addr
2019-03-26 13:44:38 +01:00
}
for _ , addr := range cfg . Address {
2019-03-29 10:48:33 +01:00
log := log . WithField ( "addr" , addr . String ( ) )
2019-03-26 13:44:38 +01:00
_ , present := presentAddresses [ addr . String ( ) ]
2019-03-29 10:48:33 +01:00
presentAddresses [ addr . String ( ) ] = netlink . Addr { } // mark as present
2019-03-26 13:44:38 +01:00
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 {
2019-03-28 14:22:30 +01:00
IPNet : & addr ,
2019-03-28 14:39:45 +01:00
Label : cfg . AddressLabel ,
2019-03-26 13:44:38 +01:00
} ) ; 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
}
2019-03-27 17:40:06 +01:00
for _ , addr := range presentAddresses {
2019-03-29 10:48:33 +01:00
if addr . IPNet == nil {
2019-03-27 17:40:06 +01:00
continue
}
2019-03-28 14:39:45 +01:00
log := log . WithFields ( map [ string ] interface { } {
"addr" : addr . IPNet . String ( ) ,
"label" : addr . Label ,
} )
2019-03-29 10:48:33 +01:00
if err := netlink . AddrDel ( link , & addr ) ; err != nil {
2019-03-27 17:40:06 +01:00
log . WithError ( err ) . Error ( "cannot delete addr" )
return err
2019-03-26 13:44:38 +01:00
}
2019-03-27 17:40:06 +01:00
log . Info ( "addr deleted" )
2019-03-26 13:44:38 +01:00
}
return nil
}
2019-03-29 12:40:15 +01:00
func fillRouteDefaults ( rt * netlink . Route ) {
// fill defaults
if rt . Table == 0 {
2019-03-29 12:59:45 +01:00
rt . Table = unix . RT_CLASS_MAIN
2019-03-29 12:40:15 +01:00
}
if rt . Protocol == 0 {
2019-03-29 12:59:45 +01:00
rt . Protocol = unix . RTPROT_BOOT
2019-03-29 12:40:15 +01:00
}
if rt . Type == 0 {
2019-03-29 12:59:45 +01:00
rt . Type = unix . RTN_UNICAST
2019-03-29 12:40:15 +01:00
}
}
2019-03-28 12:43:34 +01:00
// SyncRoutes adds/deletes all route assigned IPV4 addressed as specified in the config
2019-03-29 11:01:22 +01:00
func SyncRoutes ( cfg * Config , link netlink . Link , managedRoutes [ ] net . IPNet , log logrus . FieldLogger ) error {
2019-03-29 12:40:15 +01:00
var wantedRoutes = make ( map [ string ] [ ] netlink . Route , len ( managedRoutes ) )
presentRoutes , err := netlink . RouteList ( link , syscall . AF_INET )
2019-03-26 13:44:38 +01:00
if err != nil {
log . Error ( err , "cannot read existing routes" )
return err
}
2019-03-29 11:01:22 +01:00
for _ , rt := range managedRoutes {
2019-03-29 12:40:15 +01:00
rt := rt // make copy
log . WithField ( "dst" , rt . String ( ) ) . Debug ( "managing route" )
nrt := netlink . Route {
2019-03-29 11:01:22 +01:00
LinkIndex : link . Attrs ( ) . Index ,
Dst : & rt ,
Table : cfg . Table ,
Protocol : cfg . RouteProtocol ,
2019-03-29 12:40:15 +01:00
Priority : cfg . RouteMetric }
fillRouteDefaults ( & nrt )
wantedRoutes [ rt . String ( ) ] = append ( wantedRoutes [ rt . String ( ) ] , nrt )
}
for _ , rtLst := range wantedRoutes {
for _ , rt := range rtLst {
rt := rt // make copy
log := log . WithFields ( map [ string ] interface { } {
"route" : rt . Dst . String ( ) ,
"protocol" : rt . Protocol ,
"table" : rt . Table ,
"type" : rt . Type ,
"metric" : rt . Priority ,
} )
if err := netlink . RouteReplace ( & rt ) ; err != nil {
log . WithError ( err ) . Errorln ( "cannot add/replace route" )
return err
}
log . Infoln ( "route added/replaced" )
2019-03-26 13:44:38 +01:00
}
}
2019-03-29 12:40:15 +01:00
checkWanted := func ( rt netlink . Route ) bool {
for _ , candidateRt := range wantedRoutes [ rt . Dst . String ( ) ] {
if rt . Equal ( candidateRt ) {
return true
}
2019-03-26 13:44:38 +01:00
}
2019-03-29 12:40:15 +01:00
return false
}
for _ , rt := range presentRoutes {
2019-03-28 14:39:45 +01:00
log := log . WithFields ( map [ string ] interface { } {
"route" : rt . Dst . String ( ) ,
"protocol" : rt . Protocol ,
"table" : rt . Table ,
"type" : rt . Type ,
2019-03-29 12:40:15 +01:00
"metric" : rt . Priority ,
2019-03-28 14:39:45 +01:00
} )
2019-03-29 12:59:45 +01:00
if ! ( rt . Table == cfg . Table || ( cfg . Table == 0 && rt . Table == unix . RT_CLASS_MAIN ) ) {
2019-03-29 12:40:15 +01:00
log . Debug ( "wrong table for route, skipping" )
continue
}
if ! ( rt . Protocol == cfg . RouteProtocol ) {
2019-03-29 10:50:17 +01:00
log . Infof ( "skipping route deletion, not owned by this daemon" )
2019-03-29 10:48:33 +01:00
continue
}
2019-03-29 12:40:15 +01:00
if checkWanted ( rt ) {
log . Debug ( "route wanted, skipping deleting" )
continue
}
2019-03-29 10:48:33 +01:00
if err := netlink . RouteDel ( & rt ) ; err != nil {
2019-03-29 10:50:17 +01:00
log . WithError ( err ) . Error ( "cannot delete route" )
2019-03-27 17:40:06 +01:00
return err
2019-03-26 13:44:38 +01:00
}
2019-03-27 17:40:06 +01:00
log . Info ( "route deleted" )
2019-03-26 13:44:38 +01:00
}
2019-03-29 12:40:15 +01:00
2019-03-26 13:44:38 +01:00
return nil
}