github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/services/wireguard/endpoint/dvpnclient/client.go (about)

     1  /*
     2   * Copyright (C) 2022 The "MysteriumNetwork/node" Authors.
     3   *
     4   * This program is free software: you can redistribute it and/or modify
     5   * it under the terms of the GNU General Public License as published by
     6   * the Free Software Foundation, either version 3 of the License, or
     7   * (at your option) any later version.
     8   *
     9   * This program is distributed in the hope that it will be useful,
    10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12   * GNU General Public License for more details.
    13   *
    14   * You should have received a copy of the GNU General Public License
    15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16   */
    17  
    18  package dvpnclient
    19  
    20  import (
    21  	"encoding/base64"
    22  	"fmt"
    23  	"net"
    24  	"strings"
    25  	"time"
    26  
    27  	"github.com/pkg/errors"
    28  	"golang.zx2c4.com/wireguard/wgctrl"
    29  	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
    30  
    31  	"github.com/mysteriumnetwork/node/services/wireguard/connection/dns"
    32  	"github.com/mysteriumnetwork/node/services/wireguard/wgcfg"
    33  	"github.com/mysteriumnetwork/node/utils"
    34  	"github.com/mysteriumnetwork/node/utils/actionstack"
    35  	"github.com/mysteriumnetwork/node/utils/cmdutil"
    36  )
    37  
    38  type client struct {
    39  	iface      string
    40  	wgClient   *wgctrl.Client
    41  	dnsManager dns.Manager
    42  }
    43  
    44  // New creates new wireguard dvpn client.
    45  func New() (*client, error) {
    46  	wgClient, err := wgctrl.New()
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  	return &client{
    51  		wgClient:   wgClient,
    52  		dnsManager: dns.NewManager(),
    53  	}, nil
    54  }
    55  
    56  func (c *client) ConfigureDevice(config wgcfg.DeviceConfig) error {
    57  	rollback := actionstack.NewActionStack()
    58  
    59  	if err := c.up(config.IfaceName); err != nil {
    60  		return err
    61  	}
    62  	rollback.Push(func() {
    63  		_ = c.DestroyDevice(config.IfaceName)
    64  	})
    65  
    66  	err := c.configureDevice(config)
    67  	if err != nil {
    68  		rollback.Run()
    69  		return err
    70  	}
    71  
    72  	if config.Peer.Endpoint != nil {
    73  		gw := config.Subnet.IP.To4()
    74  		if gw == nil {
    75  			return fmt.Errorf("Subnet %s is not an IPv4 address", config.Subnet.String())
    76  		}
    77  
    78  		gw[3]--
    79  		if err := cmdutil.SudoExec("ip", "route", "add", "default", "via", gw.String(), "dev", config.IfaceName, "table", config.IfaceName); err != nil {
    80  			if !strings.Contains(err.Error(), "File exists") {
    81  				// Ignore error if the route already exist.
    82  				return err
    83  			}
    84  		}
    85  	}
    86  
    87  	return nil
    88  }
    89  
    90  func (c *client) ReConfigureDevice(config wgcfg.DeviceConfig) error {
    91  	return c.configureDevice(config)
    92  }
    93  
    94  func (c *client) configureDevice(config wgcfg.DeviceConfig) error {
    95  	if err := cmdutil.SudoExec("ip", "address", "replace", "dev", config.IfaceName, config.Subnet.String()); err != nil {
    96  		return err
    97  	}
    98  
    99  	peer, err := peerConfig(config.Peer)
   100  	if err != nil {
   101  		return err
   102  	}
   103  
   104  	privateKey, err := stringToKey(config.PrivateKey)
   105  	if err != nil {
   106  		return err
   107  	}
   108  
   109  	c.iface = config.IfaceName
   110  	deviceConfig := wgtypes.Config{
   111  		PrivateKey:   &privateKey,
   112  		ListenPort:   &config.ListenPort,
   113  		Peers:        []wgtypes.PeerConfig{peer},
   114  		ReplacePeers: true,
   115  	}
   116  
   117  	if err := c.wgClient.ConfigureDevice(c.iface, deviceConfig); err != nil {
   118  		return fmt.Errorf("could not configure kernel space device: %w", err)
   119  	}
   120  
   121  	if err := c.dnsManager.Set(dns.Config{
   122  		ScriptDir: config.DNSScriptDir,
   123  		IfaceName: config.IfaceName,
   124  		DNS:       config.DNS,
   125  	}); err != nil {
   126  		return fmt.Errorf("could not set DNS: %w", err)
   127  	}
   128  
   129  	return nil
   130  }
   131  
   132  func peerConfig(peer wgcfg.Peer) (wgtypes.PeerConfig, error) {
   133  	endpoint := peer.Endpoint
   134  	publicKey, err := stringToKey(peer.PublicKey)
   135  	if err != nil {
   136  		return wgtypes.PeerConfig{}, fmt.Errorf("could not convert string key to wgtypes.Key: %w", err)
   137  	}
   138  
   139  	// Apply keep alive interval
   140  	var keepAliveInterval *time.Duration
   141  	if peer.KeepAlivePeriodSeconds > 0 {
   142  		interval := time.Duration(peer.KeepAlivePeriodSeconds) * time.Second
   143  		keepAliveInterval = &interval
   144  	}
   145  
   146  	// Apply allowed IPs network
   147  	var allowedIPs []net.IPNet
   148  	for _, ip := range peer.AllowedIPs {
   149  		_, network, err := net.ParseCIDR(ip)
   150  		if err != nil {
   151  			return wgtypes.PeerConfig{}, fmt.Errorf("could not parse IP %q: %v", ip, err)
   152  		}
   153  		allowedIPs = append(allowedIPs, *network)
   154  	}
   155  
   156  	return wgtypes.PeerConfig{
   157  		Endpoint:                    endpoint,
   158  		PublicKey:                   publicKey,
   159  		AllowedIPs:                  allowedIPs,
   160  		PersistentKeepaliveInterval: keepAliveInterval,
   161  	}, nil
   162  }
   163  
   164  func (c *client) PeerStats(string) (wgcfg.Stats, error) {
   165  	d, err := c.wgClient.Device(c.iface)
   166  	if err != nil {
   167  		return wgcfg.Stats{}, err
   168  	}
   169  
   170  	if len(d.Peers) != 1 {
   171  		return wgcfg.Stats{}, errors.New("kernelspace: exactly 1 peer expected")
   172  	}
   173  
   174  	return wgcfg.Stats{
   175  		BytesReceived: uint64(d.Peers[0].ReceiveBytes),
   176  		BytesSent:     uint64(d.Peers[0].TransmitBytes),
   177  		LastHandshake: d.Peers[0].LastHandshakeTime,
   178  	}, nil
   179  }
   180  
   181  func (c *client) DestroyDevice(name string) error {
   182  	return cmdutil.SudoExec("ip", "link", "del", "dev", name)
   183  }
   184  
   185  func (c *client) up(iface string) error {
   186  	rollback := actionstack.NewActionStack()
   187  	if d, err := c.wgClient.Device(iface); err != nil || d.Name != iface {
   188  		if err := cmdutil.SudoExec("ip", "link", "add", "dev", iface, "type", "wireguard"); err != nil {
   189  			return err
   190  		}
   191  	}
   192  	rollback.Push(func() {
   193  		_ = c.DestroyDevice(iface)
   194  	})
   195  
   196  	if err := cmdutil.SudoExec("ip", "link", "set", "dev", iface, "up"); err != nil {
   197  		rollback.Run()
   198  		return err
   199  	}
   200  
   201  	return nil
   202  }
   203  
   204  func (c *client) Close() (err error) {
   205  	errs := utils.ErrorCollection{}
   206  	if err := c.DestroyDevice(c.iface); err != nil {
   207  		errs.Add(err)
   208  	}
   209  	if err := c.wgClient.Close(); err != nil {
   210  		errs.Add(err)
   211  	}
   212  	if err := c.dnsManager.Clean(); err != nil {
   213  		errs.Add(err)
   214  	}
   215  	if err := errs.Error(); err != nil {
   216  		return fmt.Errorf("could not close client: %w", err)
   217  	}
   218  	return nil
   219  }
   220  
   221  func stringToKey(key string) (wgtypes.Key, error) {
   222  	k, err := base64.StdEncoding.DecodeString(key)
   223  	if err != nil {
   224  		return wgtypes.Key{}, err
   225  	}
   226  	return wgtypes.NewKey(k)
   227  }