Wireguard support for Mullvad and Windscribe (#565)

- `internal/wireguard` client package with unit tests
- Implementation works with kernel space or user space if unavailable
- `WIREGUARD_PRIVATE_KEY`
- `WIREGUARD_ADDRESS`
- `WIREGUARD_PRESHARED_KEY`
- `WIREGUARD_PORT`
- `internal/netlink` package used by `internal/wireguard`
This commit is contained in:
Quentin McGaw
2021-08-22 14:58:39 -07:00
committed by GitHub
parent 0bfd58a3f5
commit 614eb10d67
70 changed files with 13595 additions and 148 deletions

View File

@@ -22,10 +22,12 @@ type serverData struct {
Provider string `json:"provider"`
IPv4 string `json:"ipv4_addr_in"`
IPv6 string `json:"ipv6_addr_in"`
Type string `json:"type"`
PubKey string `json:"pubkey"` // Wireguard public key
}
func fetchAPI(ctx context.Context, client *http.Client) (data []serverData, err error) {
const url = "https://api.mullvad.net/www/relays/openvpn/"
const url = "https://api.mullvad.net/www/relays/all/"
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {

View File

@@ -6,14 +6,17 @@ import (
"net"
"strings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
)
type hostToServer map[string]models.MullvadServer
var (
ErrParseIPv4 = errors.New("cannot parse IPv4 address")
ErrParseIPv6 = errors.New("cannot parse IPv6 address")
ErrNoIP = errors.New("no IP address for VPN server")
ErrParseIPv4 = errors.New("cannot parse IPv4 address")
ErrParseIPv6 = errors.New("cannot parse IPv6 address")
ErrVPNTypeNotSupported = errors.New("VPN type not supported")
)
func (hts hostToServer) add(data serverData) (err error) {
@@ -21,14 +24,8 @@ func (hts hostToServer) add(data serverData) (err error) {
return
}
ipv4 := net.ParseIP(data.IPv4)
if ipv4 == nil || ipv4.To4() == nil {
return fmt.Errorf("%w: %s", ErrParseIPv4, data.IPv4)
}
ipv6 := net.ParseIP(data.IPv6)
if ipv6 == nil || ipv6.To4() != nil {
return fmt.Errorf("%w: %s", ErrParseIPv6, data.IPv6)
if data.IPv4 == "" && data.IPv6 == "" {
return ErrNoIP
}
server, ok := hts[data.Hostname]
@@ -36,13 +33,40 @@ func (hts hostToServer) add(data serverData) (err error) {
return nil
}
switch data.Type {
case "openvpn":
server.VPN = constants.OpenVPN
case "wireguard":
server.VPN = constants.Wireguard
case "bridge":
// ignore bridge servers
return nil
default:
return fmt.Errorf("%w: %s", ErrVPNTypeNotSupported, data.Type)
}
if data.IPv4 != "" {
ipv4 := net.ParseIP(data.IPv4)
if ipv4 == nil || ipv4.To4() == nil {
return fmt.Errorf("%w: %s", ErrParseIPv4, data.IPv4)
}
server.IPs = []net.IP{ipv4}
}
if data.IPv6 != "" {
ipv6 := net.ParseIP(data.IPv6)
if ipv6 == nil || ipv6.To4() != nil {
return fmt.Errorf("%w: %s", ErrParseIPv6, data.IPv6)
}
server.IPsV6 = []net.IP{ipv6}
}
server.Country = data.Country
server.City = strings.ReplaceAll(data.City, ",", "")
server.Hostname = data.Hostname
server.ISP = data.Provider
server.Owned = data.Owned
server.IPs = []net.IP{ipv4}
server.IPsV6 = []net.IP{ipv6}
server.WgPubKey = data.PubKey
hts[data.Hostname] = server

View File

@@ -29,6 +29,7 @@ type groupData struct {
City string `json:"city"`
Nodes []serverData `json:"nodes"`
OvpnX509 string `json:"ovpn_x509"`
WgPubKey string `json:"wg_pubkey"`
}
type serverData struct {

View File

@@ -9,10 +9,14 @@ import (
"net"
"net/http"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
)
var ErrNotEnoughServers = errors.New("not enough servers found")
var (
ErrNotEnoughServers = errors.New("not enough servers found")
ErrNoWireguardKey = errors.New("no wireguard public key found")
)
func GetServers(ctx context.Context, client *http.Client, minServers int) (
servers []models.WindscribeServer, err error) {
@@ -26,19 +30,17 @@ func GetServers(ctx context.Context, client *http.Client, minServers int) (
for _, group := range regionData.Groups {
city := group.City
x5090Name := group.OvpnX509
wgPubKey := group.WgPubKey
for _, node := range group.Nodes {
const maxIPsPerNode = 3
ips := make([]net.IP, 0, maxIPsPerNode)
ips := make([]net.IP, 0, 2) // nolint:gomnd
if node.IP != nil {
ips = append(ips, node.IP)
}
if node.IP2 != nil {
ips = append(ips, node.IP2)
}
// if node.IP3 != nil { // Wireguard + Stealth
// ips = append(ips, node.IP3)
// }
server := models.WindscribeServer{
VPN: constants.OpenVPN,
Region: region,
City: city,
Hostname: node.Hostname,
@@ -46,6 +48,18 @@ func GetServers(ctx context.Context, client *http.Client, minServers int) (
IPs: ips,
}
servers = append(servers, server)
if node.IP3 == nil { // Wireguard + Stealth
continue
} else if wgPubKey == "" {
return nil, fmt.Errorf("%w: for node %s", ErrNoWireguardKey, node.Hostname)
}
server.VPN = constants.Wireguard
server.OvpnX509 = ""
server.WgPubKey = wgPubKey
server.IPs = []net.IP{node.IP3}
servers = append(servers, server)
}
}
}

View File

@@ -10,6 +10,9 @@ func sortServers(servers []models.WindscribeServer) {
sort.Slice(servers, func(i, j int) bool {
if servers[i].Region == servers[j].Region {
if servers[i].City == servers[j].City {
if servers[i].Hostname == servers[j].Hostname {
return servers[i].VPN < servers[j].VPN
}
return servers[i].Hostname < servers[j].Hostname
}
return servers[i].City < servers[j].City