1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
|
/* SPDX-License-Identifier: GPL-2.0
*
* Copyright (C) 2021 Jason A. Donenfeld. All Rights Reserved.
*/
package main
import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/http"
"net/url"
"regexp"
"strings"
"golang.org/x/crypto/curve25519"
"inet.af/netaddr"
)
const vpnProviderMaxEndpointsInParallel = 20
type vpnEndpoint struct {
publicKey []byte
endpoint netaddr.IPPort
name string
country string
}
type vpnConf struct {
privateKey []byte
ips []netaddr.IP
dnses []netaddr.IP
}
type apiRelaysWireGuardV1Key []byte
func (key *apiRelaysWireGuardV1Key) UnmarshalText(text []byte) error {
k, err := base64.StdEncoding.DecodeString(string(text))
if err != nil {
return err
}
if len(k) != 32 {
return errors.New("key must be 32 bytes")
}
*key = k
return nil
}
type apiRelaysWireGuardV1Relay struct {
Hostname string `json:"hostname"`
EndpointV4 netaddr.IP `json:"ipv4_addr_in"`
EndpointV6 netaddr.IP `json:"ipv6_addr_in"`
PublicKey apiRelaysWireGuardV1Key `json:"public_key"`
MultihopPort uint16 `json:"multihop_port"`
}
type apiRelaysWireGuardV1City struct {
Name string `json:"name"`
Code string `json:"code"`
Latitude float64 `json:"latitude"`
Longitude float64 `json:"Longitude"`
Relays []apiRelaysWireGuardV1Relay `json:"relays"`
}
type apiRelaysWireGuardV1Country struct {
Name string `json:"name"`
Code string `json:"code"`
Cities []apiRelaysWireGuardV1City `json:"cities"`
}
type apiRelaysWireGuardV1Root struct {
Countries []apiRelaysWireGuardV1Country `json:"countries"`
}
func getVpnEndpoints() ([]vpnEndpoint, error) {
log.Println("Getting VPN server list")
resp, err := http.Get("https://api.mullvad.net/public/relays/wireguard/v1/")
if err != nil {
return nil, err
}
bytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var relays apiRelaysWireGuardV1Root
err = json.Unmarshal(bytes, &relays)
if err != nil {
return nil, err
}
var endpoints []vpnEndpoint
for _, country := range relays.Countries {
for _, city := range country.Cities {
for _, relay := range city.Relays {
endpoints = append(endpoints, vpnEndpoint{
publicKey: relay.PublicKey,
endpoint: netaddr.IPPortFrom(relay.EndpointV4, 51820),
name: strings.TrimSuffix(relay.Hostname, "-wireguard"),
})
}
}
}
return endpoints, nil
}
func registerVpnConf(privateKey []byte, accountId string) (*vpnConf, error) {
log.Println("Registering VPN private key")
publicKey, err := curve25519.X25519(privateKey, curve25519.Basepoint)
if err != nil {
return nil, err
}
resp, err := http.PostForm("https://api.mullvad.net/wg/", url.Values{
"account": {accountId},
"pubkey": {base64.StdEncoding.EncodeToString(publicKey)},
})
if err != nil {
return nil, err
}
ipBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if match, _ := regexp.Match(`^[0-9a-f:/.,]+$`, ipBytes); !match {
return nil, fmt.Errorf("registration rejected: %q", string(ipBytes))
}
conf := &vpnConf{privateKey: privateKey, dnses: []netaddr.IP{netaddr.MustParseIP("193.138.218.74")}}
for _, ipStr := range strings.Split(string(ipBytes), ",") {
ip, err := netaddr.ParseIPPrefix(strings.TrimSpace(ipStr))
if err != nil {
return nil, err
}
conf.ips = append(conf.ips, ip.IP())
}
return conf, nil
}
|