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 }