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/mdlayher/wireguardctrl"
"github.com/sirupsen/logrus"
"github.com/vishvananda/netlink"
2019-03-26 13:44:38 +01:00
)
2019-03-27 17:40:06 +01:00
const (
defaultRoutingTable = 254
2019-03-29 10:48:33 +01:00
// From linux/rtnetlink.h
RTPROT_UNSPEC = 0
RTPROT_REDIRECT = 1 /* Route installed by ICMP redirects; not used by current IPv4 */
RTPROT_KERNEL = 2 /* Route installed by kernel */
RTPROT_BOOT = 3 /* Route installed during boot */
RTPROT_STATIC = 4 /* Route installed by administrator */
/ * Values of protocol >= RTPROT_STATIC are not interpreted by kernel ;
they are just passed from user and back as is .
It will be used by hypothetical multiple routing daemons .
Note that protocol values should be standardized in order to
avoid conflicts .
* /
RTPROT_GATED = 8 /* Apparently, GateD */
RTPROT_RA = 9 /* RDISC/ND router advertisements */
RTPROT_MRT = 10 /* Merit MRT */
RTPROT_ZEBRA = 11 /* Zebra */
RTPROT_BIRD = 12 /* BIRD */
RTPROT_DNROUTED = 13 /* DECnet routing daemon */
RTPROT_XORP = 14 /* XORP */
RTPROT_NTK = 15 /* Netsukuku */
RTPROT_DHCP = 16 /* DHCP client */
RTPROT_MROUTED = 17 /* Multicast daemon */
RTPROT_BABEL = 42 /* Babel daemon */
RTPROT_BGP = 186 /* BGP Routes */
RTPROT_ISIS = 187 /* ISIS Routes */
RTPROT_OSPF = 188 /* OSPF Routes */
RTPROT_RIP = 189 /* RIP Routes */
RTPROT_EIGRP = 192 /* EIGRP Routes */
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 {
cl , err := wireguardctrl . New ( )
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-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-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
}
2019-03-29 10:48:33 +01:00
presentRoutes := make ( map [ string ] netlink . Route , 0 )
2019-03-26 13:44:38 +01:00
for _ , r := range routes {
2019-03-28 14:39:45 +01:00
log := log . WithFields ( map [ string ] interface { } {
"route" : r . Dst . String ( ) ,
"protocol" : r . Protocol ,
"table" : r . Table ,
"type" : r . Type ,
} )
2019-03-29 10:48:33 +01:00
log . Debugf ( "detected existing route: %v" , r )
if ! ( r . Table == cfg . Table || ( cfg . Table == 0 && r . Table == defaultRoutingTable ) ) {
2019-03-26 14:35:31 +01:00
log . Debug ( "wrong table for route, skipping" )
2019-03-29 10:48:33 +01:00
continue
2019-03-26 14:01:02 +01:00
}
2019-03-29 10:48:33 +01:00
presentRoutes [ r . Dst . String ( ) ] = r
log . Debug ( "added route to consideration" )
2019-03-26 13:44:38 +01:00
}
2019-03-29 11:01:22 +01:00
for _ , rt := range managedRoutes {
log := log . WithField ( "route" , rt . String ( ) )
route , present := presentRoutes [ rt . String ( ) ]
presentRoutes [ rt . String ( ) ] = netlink . Route { } // mark as visited
if present {
if route . Dst != nil && route . Protocol != cfg . RouteProtocol {
log . Warnf ( "route present; proto=%d != defined root proto=%d" , route . Protocol , cfg . RouteProtocol )
} else {
log . Info ( "route present" )
2019-03-26 13:44:38 +01:00
}
2019-03-29 11:01:22 +01:00
continue
}
if err := netlink . RouteAdd ( & netlink . Route {
LinkIndex : link . Attrs ( ) . Index ,
Dst : & rt ,
Table : cfg . Table ,
Protocol : cfg . RouteProtocol ,
} ) ; err != nil {
log . WithError ( err ) . Error ( "cannot setup route" )
return err
2019-03-26 13:44:38 +01:00
}
2019-03-29 11:01:22 +01:00
log . Info ( "route added" )
2019-03-26 13:44:38 +01:00
}
// Clean extra routes
2019-03-27 17:40:06 +01:00
for _ , rt := range presentRoutes {
2019-03-29 10:48:33 +01:00
if rt . Dst == nil { // skip visited routes
2019-03-27 17:40:06 +01:00
continue
2019-03-26 13:44:38 +01:00
}
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-27 17:40:06 +01:00
log . Info ( "extra manual route found" )
2019-03-29 10:48:33 +01:00
// RTPROT_BOOT is default one when other proto isn't defined
if ! ( rt . Protocol == cfg . RouteProtocol || rt . Protocol == RTPROT_BOOT && cfg . RouteProtocol == 0 ) {
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
}
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
}
return nil
}