diff --git a/connection/connection.go b/connection/connection.go index 67387571cfd..7a95c766dd5 100644 --- a/connection/connection.go +++ b/connection/connection.go @@ -5,6 +5,7 @@ import ( "fmt" ice "github.com/pion/ice/v2" log "github.com/sirupsen/logrus" + "github.com/wiretrustee/wiretrustee/iface" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" "sync" "time" @@ -93,6 +94,7 @@ func (conn *Connection) Open(timeout time.Duration) error { // create an ice.Agent that will be responsible for negotiating and establishing actual peer-to-peer connection a, err := ice.NewAgent(&ice.AgentConfig{ + // MulticastDNSMode: ice.MulticastDNSModeQueryAndGather, NetworkTypes: []ice.NetworkType{ice.NetworkTypeUDP4}, Urls: conn.Config.StunTurnURLS, InterfaceFilter: func(s string) bool { @@ -144,10 +146,23 @@ func (conn *Connection) Open(timeout time.Duration) error { return err } - err = conn.wgProxy.Start(remoteConn) + pair, err := conn.agent.GetSelectedCandidatePair() if err != nil { return err } + // in case the remote peer is in the local network we don't need a Wireguard proxy, direct communication is possible. + if pair.Local.Type() == ice.CandidateTypeHost && pair.Remote.Type() == ice.CandidateTypeHost { + log.Debugf("remote peer %s is in the local network with an address %s", conn.Config.RemoteWgKey.String(), pair.Remote.Address()) + err = conn.wgProxy.StartLocal(fmt.Sprintf("%s:%d", pair.Remote.Address(), iface.WgPort)) + if err != nil { + return err + } + } else { + err = conn.wgProxy.Start(remoteConn) + if err != nil { + return err + } + } log.Infof("opened connection to peer %s", conn.Config.RemoteWgKey.String()) case <-time.After(timeout): @@ -298,7 +313,6 @@ func (conn *Connection) listenOnConnectionStateChanges() error { } log.Infof("will connect to peer %s via a selected connnection candidate pair %s", conn.Config.RemoteWgKey.String(), pair) } else if state == ice.ConnectionStateDisconnected || state == ice.ConnectionStateFailed { - // todo do we really wanna have a connection restart within connection itself? Think of moving it outside err := conn.Close() if err != nil { log.Warnf("error while closing connection to peer %s -> %s", conn.Config.RemoteWgKey.String(), err.Error()) diff --git a/connection/wgproxy.go b/connection/wgproxy.go index 8eae1d111b4..489b421cb66 100644 --- a/connection/wgproxy.go +++ b/connection/wgproxy.go @@ -42,6 +42,15 @@ func (p *WgProxy) Close() error { return nil } +func (p *WgProxy) StartLocal(host string) error { + err := iface.UpdatePeer(p.iface, p.remoteKey, p.allowedIps, DefaultWgKeepAlive, host) + if err != nil { + log.Errorf("error while configuring Wireguard peer [%s] %s", p.remoteKey, err.Error()) + return err + } + return nil +} + // Start starts a new proxy using the ICE connection func (p *WgProxy) Start(remoteConn *ice.Conn) error { diff --git a/go.mod b/go.mod index 1b3a90f855f..50e4a32ca36 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/vishvananda/netlink v1.1.0 github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf + golang.org/x/sys v0.0.0-20210510120138-977fb7262007 golang.zx2c4.com/wireguard v0.0.0-20210604143328-f9b48a961cd2 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210506160403-92e472f520a5 golang.zx2c4.com/wireguard/windows v0.3.14 diff --git a/iface/iface.go b/iface/iface.go index cb51e9ffdd3..5534cd6e4de 100644 --- a/iface/iface.go +++ b/iface/iface.go @@ -1,27 +1,25 @@ package iface import ( - "net" - "time" - log "github.com/sirupsen/logrus" "golang.zx2c4.com/wireguard/conn" "golang.zx2c4.com/wireguard/device" "golang.zx2c4.com/wireguard/tun" "golang.zx2c4.com/wireguard/wgctrl" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" + "net" + "time" ) const ( defaultMTU = 1280 + WgPort = 51820 ) -// Saves tun device object - is it required? var tunIface tun.Device -// Create Creates a new Wireguard interface, sets a given IP and brings it up. -// Will reuse an existing one. -func Create(iface string, address string) error { +// CreateWithUserspace Creates a new Wireguard interface, using wireguard-go userspace implementation +func CreateWithUserspace(iface string, address string) error { var err error tunIface, err = tun.CreateTUN(iface, defaultMTU) if err != nil { @@ -52,7 +50,7 @@ func Create(iface string, address string) error { log.Debugln("UAPI listener started") - err = assignAddr(address, tunIface) + err = assignAddr(address, iface) if err != nil { return err } @@ -85,10 +83,12 @@ func Configure(iface string, privateKey string) error { return err } fwmark := 0 + p := WgPort cfg := wgtypes.Config{ PrivateKey: &key, ReplacePeers: false, FirewallMark: &fwmark, + ListenPort: &p, } err = wg.ConfigureDevice(iface, cfg) if err != nil { diff --git a/iface/iface_darwin.go b/iface/iface_darwin.go index aa963e25e56..c3c86050b5f 100644 --- a/iface/iface_darwin.go +++ b/iface/iface_darwin.go @@ -2,19 +2,18 @@ package iface import ( log "github.com/sirupsen/logrus" - "golang.zx2c4.com/wireguard/tun" "net" "os/exec" "strings" ) -//const ( -// interfacePrefix = "utun" -//) +// Create Creates a new Wireguard interface, sets a given IP and brings it up. +func Create(iface string, address string) error { + return CreateWithUserspace(iface, address) +} // assignAddr Adds IP address to the tunnel interface and network route based on the range provided -func assignAddr(address string, tunDevice tun.Device) error { - ifaceName, err := tunDevice.Name() +func assignAddr(address string, ifaceName string) error { ip := strings.Split(address, "/") cmd := exec.Command("ifconfig", ifaceName, "inet", address, ip[0]) if out, err := cmd.CombinedOutput(); err != nil { diff --git a/iface/iface_linux.go b/iface/iface_linux.go index 346ac5a0a39..86c670785b6 100644 --- a/iface/iface_linux.go +++ b/iface/iface_linux.go @@ -3,24 +3,76 @@ package iface import ( log "github.com/sirupsen/logrus" "github.com/vishvananda/netlink" - "golang.zx2c4.com/wireguard/tun" - "os" ) -//const ( -// interfacePrefix = "wg" -//) +// Create Creates a new Wireguard interface, sets a given IP and brings it up. +// Will reuse an existing one. +func Create(iface string, address string) error { -// assignAddr Adds IP address to the tunnel interface -func assignAddr(address string, tunDevice tun.Device) error { - var err error + if WireguardModExists() { + log.Debug("using kernel Wireguard module") + return CreateWithKernel(iface, address) + } else { + return CreateWithUserspace(iface, address) + } +} + +// CreateWithKernel Creates a new Wireguard interface using kernel Wireguard module. +// Works for Linux and offers much better network performance +func CreateWithKernel(iface string, address string) error { attrs := netlink.NewLinkAttrs() - attrs.Name, err = tunDevice.Name() + attrs.Name = iface + + link := wgLink{ + attrs: &attrs, + } + + log.Debugf("adding device: %s", iface) + err := netlink.LinkAdd(&link) + if os.IsExist(err) { + log.Infof("interface %s already exists. Will reuse.", iface) + } else if err != nil { + return err + } + + log.Debugf("adding address %s to interface: %s", address, iface) + addr, _ := netlink.ParseAddr(address) + err = netlink.AddrAdd(&link, addr) + if os.IsExist(err) { + log.Infof("interface %s already has the address: %s", iface, address) + } else if err != nil { + return err + } + err = assignAddr(address, iface) + if err != nil { + return err + } + + // todo do a discovery + log.Debugf("setting MTU: %s", iface) + err = netlink.LinkSetMTU(&link, defaultMTU) + if err != nil { + log.Errorf("error setting MTU on interface: %s", iface) + return err + } + + log.Debugf("bringing up interface: %s", iface) + err = netlink.LinkSetUp(&link) if err != nil { + log.Errorf("error bringing up interface: %s", iface) return err } + return nil +} + +// assignAddr Adds IP address to the tunnel interface +func assignAddr(address, name string) error { + var err error + attrs := netlink.NewLinkAttrs() + attrs.Name = name + link := wgLink{ attrs: &attrs, } diff --git a/iface/iface_windows.go b/iface/iface_windows.go index 9ce7aeafebb..46966b23d63 100644 --- a/iface/iface_windows.go +++ b/iface/iface_windows.go @@ -8,16 +8,21 @@ import ( "net" ) +// Create Creates a new Wireguard interface, sets a given IP and brings it up. +func Create(iface string, address string) error { + return CreateWithUserspace(iface, address) +} + // assignAddr Adds IP address to the tunnel interface and network route based on the range provided -func assignAddr(address string, tunDevice tun.Device) error { - ifaceName, err := tunDevice.Name() - nativeTunDevice := tunDevice.(*tun.NativeTun) +func assignAddr(address string, ifaceName string) error { + + nativeTunDevice := tunIface.(*tun.NativeTun) luid := winipcfg.LUID(nativeTunDevice.LUID()) ip, ipnet, _ := net.ParseCIDR(address) log.Debugf("adding address %s to interface: %s", address, ifaceName) - err = luid.SetIPAddresses([]net.IPNet{{ip, ipnet.Mask}}) + err := luid.SetIPAddresses([]net.IPNet{{ip, ipnet.Mask}}) if err != nil { return err } diff --git a/iface/mod.go b/iface/mod.go new file mode 100644 index 00000000000..d3195b8085c --- /dev/null +++ b/iface/mod.go @@ -0,0 +1,144 @@ +// +build linux + +package iface + +// Holds logic to check existence of Wireguard kernel module +// Copied from https://github.com/paultag/go-modprobe + +import ( + "debug/elf" + "fmt" + "golang.org/x/sys/unix" + "os" + "path/filepath" + "strings" +) + +var ( + // get the root directory for the kernel modules. If this line panics, + // it's because getModuleRoot has failed to get the uname of the running + // kernel (likely a non-POSIX system, but maybe a broken kernel?) + moduleRoot = getModuleRoot() +) + +// Get the module root (/lib/modules/$(uname -r)/) +func getModuleRoot() string { + uname := unix.Utsname{} + if err := unix.Uname(&uname); err != nil { + panic(err) + } + + i := 0 + for ; uname.Release[i] != 0; i++ { + } + + return filepath.Join( + "/lib/modules", + string(uname.Release[:i]), + ) +} + +// modName will, given a file descriptor to a Kernel Module (.ko file), parse the +// binary to get the module name. For instance, given a handle to the file at +// `kernel/drivers/usb/gadget/legacy/g_ether.ko`, return `g_ether`. +func modName(file *os.File) (string, error) { + f, err := elf.NewFile(file) + if err != nil { + return "", err + } + + syms, err := f.Symbols() + if err != nil { + return "", err + } + + for _, sym := range syms { + if strings.Compare(sym.Name, "__this_module") == 0 { + section := f.Sections[sym.Section] + data, err := section.Data() + if err != nil { + return "", err + } + + if len(data) < 25 { + return "", fmt.Errorf("modprobe: data is short, __this_module is '%s'", data) + } + + data = data[24:] + i := 0 + for ; data[i] != 0x00; i++ { + } + return string(data[:i]), nil + } + } + + return "", fmt.Errorf("No name found. Is this a .ko or just an ELF?") +} + +// Open every single kernel module under the root, and parse the ELF headers to +// extract the module name. +func elfMap(root string) (map[string]string, error) { + ret := map[string]string{} + + err := filepath.Walk( + root, + func(path string, info os.FileInfo, err error) error { + + if err != nil { + // skip broken files + return nil + } + + if !info.Mode().IsRegular() { + return nil + } + fd, err := os.Open(path) + if err != nil { + return err + } + defer fd.Close() + name, err := modName(fd) + if err != nil { + /* For now, let's just ignore that and avoid adding to it */ + return nil + } + + ret[name] = path + return nil + }) + + if err != nil { + return nil, err + } + + return ret, nil +} + +// Open every single kernel module under the kernel module directory +// (/lib/modules/$(uname -r)/), and parse the ELF headers to extract the +// module name. +func generateMap() (map[string]string, error) { + return elfMap(moduleRoot) +} + +// WireguardModExists returns true if Wireguard kernel module exists. +func WireguardModExists() bool { + _, err := resolveModName("wireguard") + return err == nil +} + +// resolveModName will, given a module name (such as `wireguard`) return an absolute +// path to the .ko that provides that module. +func resolveModName(name string) (string, error) { + paths, err := generateMap() + if err != nil { + return "", err + } + + fsPath := paths[name] + if !strings.HasPrefix(fsPath, moduleRoot) { + return "", fmt.Errorf("module isn't in the module directory") + } + + return fsPath, nil +}