-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This change moves the core features of dsnet into it's own lib. This means it's no longer tied to the CLI and instead offers a common way for implementations to manage WG with dsnet
- Loading branch information
Showing
5 changed files
with
340 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package lib | ||
|
||
import ( | ||
"fmt" | ||
|
||
"golang.zx2c4.com/wireguard/wgctrl" | ||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes" | ||
) | ||
|
||
func (s *Server) Up() error { | ||
if err := s.CreateLink(); err != nil { | ||
return err | ||
} | ||
return s.ConfigureDevice() | ||
} | ||
|
||
// ConfigureDevice sets up the WG interface | ||
func (s *Server) ConfigureDevice() error { | ||
wg, err := wgctrl.New() | ||
if err != nil { | ||
return err | ||
} | ||
defer wg.Close() | ||
|
||
dev, err := wg.Device(s.InterfaceName) | ||
|
||
if err != nil { | ||
return fmt.Errorf("could not retrieve device '%s' (%v)", s.InterfaceName, err) | ||
} | ||
|
||
peers := s.GetPeers() | ||
|
||
// compare peers to see if any exist on the device and not the config. If | ||
// so, they should be removed by appending a dummy peer with Remove:true + pubkey. | ||
knownKeys := make(map[wgtypes.Key]bool) | ||
|
||
for _, peer := range peers { | ||
knownKeys[peer.PublicKey] = true | ||
} | ||
|
||
// find deleted peers, and append dummy "remove" peers | ||
for _, peer := range dev.Peers { | ||
if !knownKeys[peer.PublicKey] { | ||
peers = append(peers, wgtypes.PeerConfig{ | ||
PublicKey: peer.PublicKey, | ||
Remove: true, | ||
}) | ||
} | ||
} | ||
|
||
wgConfig := wgtypes.Config{ | ||
PrivateKey: &s.PrivateKey.Key, | ||
ListenPort: &s.ListenPort, | ||
// ReplacePeers with the same peers results in those peers losing | ||
// connection, so it's not possible to do declarative configuration | ||
// idempotently with ReplacePeers like I had assumed. Instead, peers | ||
// must be removed imperatively with Remove:true. Peers can still be | ||
// added/updated with ConfigureDevice declaratively. | ||
ReplacePeers: false, | ||
Peers: peers, | ||
} | ||
|
||
err = wg.ConfigureDevice(s.InterfaceName, wgConfig) | ||
|
||
if err != nil { | ||
return fmt.Errorf("could not configure device '%s' (%v)", s.InterfaceName, err) | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package lib | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"net" | ||
|
||
"github.com/naggie/dsnet/utils" | ||
"github.com/vishvananda/netlink" | ||
) | ||
|
||
// CreateLink sets up the WG interface and link with the correct | ||
// address | ||
func (s *Server) CreateLink() error { | ||
if len(s.IP) == 0 && len(s.IP6) == 0 { | ||
return errors.New("no IPv4 or IPv6 ip defined in config") | ||
} | ||
|
||
linkAttrs := netlink.NewLinkAttrs() | ||
linkAttrs.Name = s.InterfaceName | ||
|
||
link := &netlink.GenericLink{ | ||
LinkAttrs: linkAttrs, | ||
LinkType: "wireguard", | ||
} | ||
|
||
err := netlink.LinkAdd(link) | ||
if err != nil && s.FallbackWGBin != "" { | ||
// return fmt.Errorf("could not add interface '%s' (%v), falling back to the userspace implementation", s.InterfaceName, err) | ||
cmdStr := fmt.Sprintf("%s %s", s.FallbackWGBin, s.InterfaceName) | ||
if err = utils.ShellOut(cmdStr, "Userspace implementation"); err != nil { | ||
return fmt.Errorf("failed to start userspace wireguard: %s", err) | ||
} | ||
} | ||
|
||
if len(s.IP) != 0 { | ||
addr := &netlink.Addr{ | ||
IPNet: &net.IPNet{ | ||
IP: s.IP, | ||
Mask: s.Network.IPNet.Mask, | ||
}, | ||
} | ||
|
||
err = netlink.AddrAdd(link, addr) | ||
if err != nil { | ||
return fmt.Errorf("could not add ipv4 addr %s to interface %s", addr.IP, err) | ||
} | ||
} | ||
|
||
if len(s.IP6) != 0 { | ||
addr6 := &netlink.Addr{ | ||
IPNet: &net.IPNet{ | ||
IP: s.IP6, | ||
Mask: s.Network6.IPNet.Mask, | ||
}, | ||
} | ||
|
||
err = netlink.AddrAdd(link, addr6) | ||
if err != nil { | ||
return fmt.Errorf("could not add ipv6 addr %s to interface %s", addr6.IP, err) | ||
} | ||
} | ||
|
||
// bring up interface (UNKNOWN state instead of UP, a wireguard quirk) | ||
err = netlink.LinkSetUp(link) | ||
|
||
if err != nil { | ||
return fmt.Errorf("could not bring up device '%s' (%v)", s.InterfaceName, err) | ||
} | ||
return nil | ||
} | ||
|
||
// DeleteLink removes the Netlink interface | ||
func (s *Server) DeleteLink() error { | ||
linkAttrs := netlink.NewLinkAttrs() | ||
linkAttrs.Name = s.InterfaceName | ||
|
||
link := &netlink.GenericLink{ | ||
LinkAttrs: linkAttrs, | ||
} | ||
|
||
err := netlink.LinkDel(link) | ||
if err != nil { | ||
return fmt.Errorf("failed to delete interface(%s): %s", s.InterfaceName, err) | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package lib | ||
|
||
import ( | ||
"net" | ||
"time" | ||
) | ||
|
||
type Peer struct { | ||
Hostname string | ||
Owner string | ||
Description string | ||
IP net.IP | ||
IP6 net.IP | ||
Added time.Time | ||
PublicKey JSONKey | ||
PrivateKey JSONKey | ||
PresharedKey JSONKey | ||
Networks []JSONIPNet | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package lib | ||
|
||
import ( | ||
"net" | ||
|
||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes" | ||
) | ||
|
||
type Server struct { | ||
ExternalHostname string | ||
ExternalIP net.IP | ||
ExternalIP6 net.IP | ||
ListenPort int | ||
Domain string | ||
InterfaceName string | ||
Network JSONIPNet | ||
Network6 JSONIPNet | ||
IP net.IP | ||
IP6 net.IP | ||
DNS net.IP | ||
PrivateKey JSONKey | ||
PostUp string | ||
PostDown string | ||
FallbackWGBin string | ||
Peers []Peer | ||
} | ||
|
||
func (s *Server) GetPeers() []wgtypes.PeerConfig { | ||
wgPeers := make([]wgtypes.PeerConfig, 0, len(s.Peers)) | ||
|
||
for _, peer := range s.Peers { | ||
// create a new PSK in memory to avoid passing the same value by | ||
// pointer to each peer (d'oh) | ||
presharedKey := peer.PresharedKey.Key | ||
|
||
// AllowedIPs = private IP + defined networks | ||
allowedIPs := make([]net.IPNet, 0, len(peer.Networks)+2) | ||
|
||
if len(peer.IP) > 0 { | ||
allowedIPs = append( | ||
allowedIPs, | ||
net.IPNet{ | ||
IP: peer.IP, | ||
Mask: net.IPMask{255, 255, 255, 255}, | ||
}, | ||
) | ||
} | ||
|
||
if len(peer.IP6) > 0 { | ||
allowedIPs = append( | ||
allowedIPs, | ||
net.IPNet{ | ||
IP: peer.IP6, | ||
Mask: net.IPMask{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, | ||
}, | ||
) | ||
} | ||
|
||
for _, net := range peer.Networks { | ||
allowedIPs = append(allowedIPs, net.IPNet) | ||
} | ||
|
||
wgPeers = append(wgPeers, wgtypes.PeerConfig{ | ||
PublicKey: peer.PublicKey.Key, | ||
Remove: false, | ||
UpdateOnly: false, | ||
PresharedKey: &presharedKey, | ||
Endpoint: nil, | ||
ReplaceAllowedIPs: true, | ||
AllowedIPs: allowedIPs, | ||
}) | ||
} | ||
|
||
return wgPeers | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
package lib | ||
|
||
import ( | ||
"fmt" | ||
"net" | ||
"strings" | ||
|
||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes" | ||
) | ||
|
||
type JSONIPNet struct { | ||
IPNet net.IPNet | ||
} | ||
|
||
func (n JSONIPNet) MarshalJSON() ([]byte, error) { | ||
if len(n.IPNet.IP) == 0 { | ||
return []byte("\"\""), nil | ||
} else { | ||
return []byte("\"" + n.IPNet.String() + "\""), nil | ||
} | ||
} | ||
|
||
func (n *JSONIPNet) UnmarshalJSON(b []byte) error { | ||
cidr := strings.Trim(string(b), "\"") | ||
|
||
if cidr == "" { | ||
// Leave as empty/uninitialised IPNet. A bit like omitempty behaviour, | ||
// but we can leave the field there and blank which is useful if the | ||
// user wishes to add the cidr manually. | ||
return nil | ||
} | ||
|
||
IP, IPNet, err := net.ParseCIDR(cidr) | ||
|
||
if err == nil { | ||
IPNet.IP = IP | ||
n.IPNet = *IPNet | ||
} | ||
|
||
return err | ||
} | ||
|
||
func (n *JSONIPNet) String() string { | ||
return n.IPNet.String() | ||
} | ||
|
||
type JSONKey struct { | ||
Key wgtypes.Key | ||
} | ||
|
||
func (k JSONKey) MarshalJSON() ([]byte, error) { | ||
return []byte("\"" + k.Key.String() + "\""), nil | ||
} | ||
|
||
func (k JSONKey) PublicKey() JSONKey { | ||
return JSONKey{ | ||
Key: k.Key.PublicKey(), | ||
} | ||
} | ||
|
||
func (k *JSONKey) UnmarshalJSON(b []byte) error { | ||
b64Key := strings.Trim(string(b), "\"") | ||
key, err := wgtypes.ParseKey(b64Key) | ||
k.Key = key | ||
return err | ||
} | ||
|
||
func GenerateJSONPrivateKey() (JSONKey, error) { | ||
privateKey, err := wgtypes.GeneratePrivateKey() | ||
|
||
if err != nil { | ||
return JSONKey{}, fmt.Errorf("failed to generate private key: %s", err) | ||
} | ||
|
||
return JSONKey{ | ||
Key: privateKey, | ||
}, nil | ||
} | ||
|
||
func GenerateJSONKey() (JSONKey, error) { | ||
privateKey, err := wgtypes.GenerateKey() | ||
|
||
if err != nil { | ||
return JSONKey{}, fmt.Errorf("failed to generate key: %s", err) | ||
} | ||
|
||
return JSONKey{ | ||
Key: privateKey, | ||
}, nil | ||
} |