138 lines
3.5 KiB
Go
138 lines
3.5 KiB
Go
/* 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
|
|
}
|