diff --git a/README.md b/README.md index b6303b3..b28e854 100644 --- a/README.md +++ b/README.md @@ -125,13 +125,3 @@ different cloud providers) can lead to a split-brain scenario where each side th There is currently no clean solution for this problem, but one could work around it by designating edge nodes which periodically restart `wesher` with the `--join` option pointing to the other side. Future versions might include the notion of a "static" node to more cleanly avoid this. - -### Broken connections on join/leave - -Currently `wesher` uses wireguard's management commands (`wg` and `wg-quick`) to talk to wireguard. The current -implementation is very naive and recreates the wireguard interface for every configuration change (e.g. nodes' joining -and leavning). Since wireguard's underlying traffic is essentially stateless, this does not directly impact TCP -connections on the overlay network. The connection should treat it as normal packet drops. -However, if your application is sensitive to ICMP errors (e.g. "no route to host"), or if you are using UDP, you might -experience connection resets and message loss, respectively. -This behavior will be improved in future versions. \ No newline at end of file diff --git a/cluster.go b/cluster.go index 22b1aa9..bfad099 100644 --- a/cluster.go +++ b/cluster.go @@ -107,7 +107,7 @@ func (c *cluster) LocalState(join bool) []byte { return nil } func (c *cluster) MergeRemoteState(buf []byte, join bool) {} type nodeMeta struct { - OverlayAddr net.IP + OverlayAddr net.IPNet PubKey string } @@ -115,7 +115,7 @@ func (c *cluster) NodeMeta(limit int) []byte { buf := &bytes.Buffer{} if err := gob.NewEncoder(buf).Encode(nodeMeta{ OverlayAddr: c.wg.OverlayAddr, - PubKey: c.wg.PubKey, + PubKey: c.wg.PubKey.String(), }); err != nil { logrus.Errorf("could not encode local state: %s", err) return nil @@ -128,6 +128,8 @@ func (c *cluster) NodeMeta(limit int) []byte { } func decodeNodeMeta(b []byte) (nodeMeta, error) { + // TODO: we blindly trust the info we get from the peers; We should be more defensive to limit the damage a leaked + // PSK can cause. nm := nodeMeta{} if err := gob.NewDecoder(bytes.NewReader(b)).Decode(&nm); err != nil { return nm, errwrap.Wrapf("could not decode: {{err}}", err) diff --git a/go.mod b/go.mod index 361e7a4..420c675 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,11 @@ require ( github.com/hashicorp/go-sockaddr v1.0.0 github.com/hashicorp/memberlist v0.1.3 github.com/mattn/go-isatty v0.0.7 + github.com/mdlayher/genetlink v0.0.0-20190617154021-985b2115c31a + github.com/pkg/errors v0.8.1 github.com/sirupsen/logrus v1.3.0 github.com/stevenroose/gonfig v0.1.4 + github.com/vishvananda/netlink v1.0.0 + github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f // indirect + golang.zx2c4.com/wireguard/wgctrl v0.0.0-20190629151639-28f4e240be2d ) diff --git a/go.sum b/go.sum index d259db3..489dc74 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,9 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= @@ -23,16 +26,34 @@ github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCO github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/jsimonetti/rtnetlink v0.0.0-20190503083013-8a08cb3e375e/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= +github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a h1:84IpUNXj4mCR9CuCEvSiCArMbzr/TMbuPIadKDwypkI= +github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mdlayher/genetlink v0.0.0-20190513144241-4cdc5dab577c h1:Z7vEAfVdgfkjIzGSOF6vLt8BGu31+DuCJqXlTI7oj3o= +github.com/mdlayher/genetlink v0.0.0-20190513144241-4cdc5dab577c/go.mod h1:Gxg/DEIMJtqdXDyq47mB98qcpBHmaLrvOAmKKNRE0Tg= +github.com/mdlayher/genetlink v0.0.0-20190617154021-985b2115c31a h1:oL6319kSfjW4J90/GXLRgcL5vHj82AsWgYv95vP0n+E= +github.com/mdlayher/genetlink v0.0.0-20190617154021-985b2115c31a/go.mod h1:JrQK8x2Z2oQUE84wv6QvW8/HgMzJIt8bN6vu28tNX1Q= +github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= +github.com/mdlayher/netlink v0.0.0-20190513144208-ba284d510044/go.mod h1:gOrA34zDL0K3RsACQe54bDYLF/CeFspQ9m5DOycycQ8= +github.com/mdlayher/netlink v0.0.0-20190614145538-d8264f87dbe3 h1:3IPcWjiboJFnnvHeXxT4pYw33BiPJn/DC5BKhcGEbGk= +github.com/mdlayher/netlink v0.0.0-20190614145538-d8264f87dbe3/go.mod h1:ISujvOTprADlNr00kvJIu0d23q57wk2NSV/PT/TEk4E= +github.com/mdlayher/netlink v0.0.0-20190617153422-f82a9b10b2bc h1:deLjDmcgzsCAO+7m2aeuyhQCqvn1LuCCSMLWUARnad8= +github.com/microsoft/go-winio v0.4.12 h1:3vDRRsUnj2dKE7QKoedntu9hbuD8gzaVd2E2UZioqx4= +github.com/microsoft/go-winio v0.4.12/go.mod h1:kcIxxtKZE55DEncT/EOvFiygPobhUWpSDqDb47poQOU= github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws= +github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= @@ -46,11 +67,23 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/vishvananda/netlink v1.0.0 h1:bqNY2lgheFIu1meHUFSH3d7vG93AFyqg3oGbJCOJgSM= +github.com/vishvananda/netlink v1.0.0/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= +github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f h1:nBX3nTcmxEtHSERBJaIo1Qa26VwRaopnZmfDQUXsF4I= +github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3 h1:KYQXGkl6vs02hK7pK4eIbw0NpNPedieTSTEiJ//bwGs= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 h1:1wopBVtVdWnn03fZelqdXTqk7U7zPQCb+T4rbU9ZEoU= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519 h1:x6rhz8Y9CjbgQkccRGmELH6K+LJj7tOoh3XWeC1yaQM= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190509222800-a4d6f7feada5/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -58,8 +91,18 @@ golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5 h1:x6r4Jo0KNzOOzYd8lbcRsqjuq golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190116161447-11f53e031339 h1:g/Jesu8+QLnA0CPzF3E1pURg0Byr7i6jLoX5sqjcAh0= golang.org/x/sys v0.0.0-20190116161447-11f53e031339/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190614084037-d442b75600c5 h1:tQrtnaPeNyfkuD2UMixVD6lAa7WngkIFvtWcdzNeq80= +golang.org/x/sys v0.0.0-20190614084037-d442b75600c5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.zx2c4.com/wireguard/wgctrl v0.0.0-20190629151639-28f4e240be2d h1:Y4Qa3H/pif1DkMBNwEwxdQ8H2+9Uq564oDIUOH4vGWE= +golang.zx2c4.com/wireguard/wgctrl v0.0.0-20190629151639-28f4e240be2d/go.mod h1:3bbJvD15soNWXiSdJMnTG8pIhM2gXvWIm56iXAkxi34= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= diff --git a/main.go b/main.go index 2658172..526ada3 100644 --- a/main.go +++ b/main.go @@ -53,13 +53,7 @@ func main() { for _, node := range nodes { logrus.Infof("\taddr: %s, overlay: %s, pubkey: %s", node.Addr, node.OverlayAddr, node.PubKey) } - if err := wg.downInterface(); err != nil { - logrus.Errorf("could not down interface: %s", err) - } - if err := wg.writeConf(nodes); err != nil { - logrus.Errorf("could not write config: %s", err) - } - if err := wg.upInterface(); err != nil { + if err := wg.setUpInterface(nodes); err != nil { logrus.Errorf("could not up interface: %s", err) } if !config.NoEtcHosts { @@ -88,7 +82,7 @@ func main() { func writeToEtcHosts(nodes []node) error { hosts := make(map[string][]string, len(nodes)) for _, n := range nodes { - hosts[n.OverlayAddr.String()] = []string{n.Name} + hosts[n.OverlayAddr.IP.String()] = []string{n.Name} } hostsFile := &etchosts.EtcHosts{ Logger: logrus.StandardLogger(), diff --git a/netlink.go b/netlink.go new file mode 100644 index 0000000..0bd1c6a --- /dev/null +++ b/netlink.go @@ -0,0 +1,18 @@ +package main + +import "github.com/vishvananda/netlink" + +// this is only necessary while this PR is open: +// https://github.com/vishvananda/netlink/pull/464 + +type wireguard struct { + netlink.LinkAttrs +} + +func (wg *wireguard) Attrs() *netlink.LinkAttrs { + return &wg.LinkAttrs +} + +func (wg *wireguard) Type() string { + return "wireguard" +} diff --git a/tests/wg b/tests/wg deleted file mode 100755 index fe94290..0000000 --- a/tests/wg +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/sh - -case $1 in - '') - echo "nothing to see here" - ;; - genkey) - echo "ILICZ3yBMCGAWNIq5Pn0bewBVimW3Q2yRVJ/Be+b1Uc=" - ;; - pubkey) - read x - echo "VceweY6x/QdGXEQ6frXrSd8CwUAInUmqIc6G/qi8FHo=" - ;; - *) - echo "what?" - exit 1 - ;; -esac \ No newline at end of file diff --git a/tests/wg-quick b/tests/wg-quick deleted file mode 100755 index 67663cc..0000000 --- a/tests/wg-quick +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh - -case $1 in - up) - echo "up $2" - ;; - pubkey) - echo "down $2" - ;; - *) - echo "what?" - exit 1 - ;; -esac \ No newline at end of file diff --git a/wireguard.go b/wireguard.go index b464d5f..e3fb1d8 100644 --- a/wireguard.go +++ b/wireguard.go @@ -1,53 +1,40 @@ package main import ( - "fmt" "hash/fnv" "net" "os" - "os/exec" - "strings" - "text/template" + + "github.com/pkg/errors" + "github.com/vishvananda/netlink" + "golang.zx2c4.com/wireguard/wgctrl" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) -const wgConfPath = "/etc/wireguard/%s.conf" -const wgConfTpl = ` -# this file was generated automatically by wesher - DO NOT MODIFY -[Interface] -PrivateKey = {{ .PrivKey }} -Address = {{ .OverlayAddr }} -ListenPort = {{ .Port }} - -{{ range .Nodes }} -[Peer] -PublicKey = {{ .PubKey }} -Endpoint = {{ .Addr }}:{{ $.Port }} -AllowedIPs = {{ .OverlayAddr }}/32 -{{ end }}` - type wgState struct { iface string - OverlayAddr net.IP + client *wgctrl.Client + OverlayAddr net.IPNet Port int - PrivKey string - PubKey string + PrivKey wgtypes.Key + PubKey wgtypes.Key } -var wgPath = "wg" -var wgQuickPath = "wg-quick" - func newWGConfig(iface string, port int) (*wgState, error) { - if err := exec.Command(wgPath).Run(); err != nil { - return nil, fmt.Errorf("could not exec wireguard: %s", err) + client, err := wgctrl.New() + if err != nil { + return nil, errors.Wrap(err, "could not instantiate wireguard client") } - privKey, pubKey, err := wgKeyPair() + privKey, err := wgtypes.GeneratePrivateKey() if err != nil { return nil, err } + pubKey := privKey.PublicKey() wgState := wgState{ iface: iface, + client: client, Port: port, PrivKey: privKey, PubKey: pubKey, @@ -69,51 +56,93 @@ func (wg *wgState) assignOverlayAddr(ipnet *net.IPNet, name string) { ip[len(ip)-i] = hb[len(hb)-i] } - wg.OverlayAddr = net.IP(ip) -} - -func (wg *wgState) writeConf(nodes []node) error { - tpl := template.Must(template.New("wgconf").Parse(wgConfTpl)) - out, err := os.OpenFile( - fmt.Sprintf(wgConfPath, wg.iface), - os.O_WRONLY|os.O_CREATE|os.O_TRUNC, - 0600, - ) - if err != nil { - return err + wg.OverlayAddr = net.IPNet{ + IP: net.IP(ip), + Mask: net.CIDRMask(size, size), // either /32 or /128, depending if ipv4 or ipv6 } - return tpl.Execute(out, struct { - *wgState - Nodes []node - }{wg, nodes}) } func (wg *wgState) downInterface() error { - if err := exec.Command(wgPath, "show", wg.iface).Run(); err != nil { - return nil // assume a failure means the interface is not there + if _, err := wg.client.Device(wg.iface); err != nil { + if os.IsNotExist(err) { + return nil // device already gone; noop + } + return err } - return exec.Command(wgQuickPath, "down", wg.iface).Run() + link, err := netlink.LinkByName(wg.iface) + if err != nil { + return err + } + return netlink.LinkDel(link) } -func (wg *wgState) upInterface() error { - return exec.Command(wgQuickPath, "up", wg.iface).Run() -} - -func wgKeyPair() (string, string, error) { - cmd := exec.Command(wgPath, "genkey") - outPriv := strings.Builder{} - cmd.Stdout = &outPriv - if err := cmd.Run(); err != nil { - return "", "", err +func (wg *wgState) setUpInterface(nodes []node) error { + if err := wg.createWgInterface(); err != nil { + return err } - cmd = exec.Command(wgPath, "pubkey") - outPub := strings.Builder{} - cmd.Stdout = &outPub - cmd.Stdin = strings.NewReader(outPriv.String()) - if err := cmd.Run(); err != nil { - return "", "", err + peerCfgs, err := wg.nodesToPeerConfigs(nodes) + if err != nil { + return errors.Wrap(err, "error converting received node information to wireguard format") + } + wg.client.ConfigureDevice(wg.iface, wgtypes.Config{ + PrivateKey: &wg.PrivKey, + ListenPort: &wg.Port, + ReplacePeers: true, + Peers: peerCfgs, + }) + + link, err := netlink.LinkByName(wg.iface) + if err != nil { + return errors.Wrapf(err, "could not get link information for %s", wg.iface) + } + netlink.AddrReplace(link, &netlink.Addr{ + IPNet: &wg.OverlayAddr, + }) + netlink.LinkSetMTU(link, 1420) // TODO: make MTU configurable? + netlink.LinkSetUp(link) + for _, node := range nodes { + netlink.RouteAdd(&netlink.Route{ + LinkIndex: link.Attrs().Index, + Dst: &node.OverlayAddr, + Scope: netlink.SCOPE_LINK, + }) } - return strings.TrimSpace(outPriv.String()), strings.TrimSpace(outPub.String()), nil + return nil +} + +func (wg *wgState) nodesToPeerConfigs(nodes []node) ([]wgtypes.PeerConfig, error) { + peerCfgs := make([]wgtypes.PeerConfig, len(nodes)) + for i, node := range nodes { + pubKey, err := wgtypes.ParseKey(node.PubKey) + if err != nil { + return nil, err + } + peerCfgs[i] = wgtypes.PeerConfig{ + PublicKey: pubKey, + ReplaceAllowedIPs: true, + Endpoint: &net.UDPAddr{ + IP: node.Addr, + Port: wg.Port, + }, + AllowedIPs: []net.IPNet{ + node.OverlayAddr, + }, + } + } + return peerCfgs, nil +} + +func (wg *wgState) createWgInterface() error { + if _, err := wg.client.Device(wg.iface); err == nil { + // device already exists, but we are running e2e tests, so we're using the user-mode implementation + if _, e2e := os.LookupEnv("WESHER_E2E_TESTS"); e2e { + return nil + } + } + if err := netlink.LinkAdd(&wireguard{LinkAttrs: netlink.LinkAttrs{Name: wg.iface}}); err != nil { + return errors.Wrapf(err, "could not create interface %s", wg.iface) + } + return nil } diff --git a/wireguard_test.go b/wireguard_test.go index 95ac0ca..6e9295c 100644 --- a/wireguard_test.go +++ b/wireguard_test.go @@ -6,38 +6,6 @@ import ( "testing" ) -func init() { - wgPath = "tests/wg" - wgQuickPath = "tests/wg-quick" -} - -func Test_wgKeyPair(t *testing.T) { - tests := []struct { - name string - want string - want1 string - wantErr bool - }{ - // see tests/wg for values - {"generate fixed values", "ILICZ3yBMCGAWNIq5Pn0bewBVimW3Q2yRVJ/Be+b1Uc=", "VceweY6x/QdGXEQ6frXrSd8CwUAInUmqIc6G/qi8FHo=", false}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, got1, err := wgKeyPair() - if (err != nil) != tt.wantErr { - t.Errorf("wgKeyPair() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("wgKeyPair() got = %v, want %v", got, tt.want) - } - if got1 != tt.want1 { - t.Errorf("wgKeyPair() got1 = %v, want %v", got1, tt.want1) - } - }) - } -} - func Test_wgState_assignOverlayAddr(t *testing.T) { type args struct { ipnet *net.IPNet @@ -66,7 +34,7 @@ func Test_wgState_assignOverlayAddr(t *testing.T) { wg := &wgState{} wg.assignOverlayAddr(tt.args.ipnet, tt.args.name) - if !reflect.DeepEqual(wg.OverlayAddr.String(), tt.want) { + if !reflect.DeepEqual(wg.OverlayAddr.IP.String(), tt.want) { t.Errorf("assignOverlayAddr() set = %s, want %s", wg.OverlayAddr, tt.want) } })