Compare commits
3 Commits
Author | SHA1 | Date | |
---|---|---|---|
293f10ac51 | |||
359b3a6cc6 | |||
4a41defb69 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
.idea/
|
.idea/
|
||||||
*.save
|
*.save
|
||||||
*.swp
|
*.swp
|
||||||
|
mullf0x
|
||||||
|
110
api.go
110
api.go
@ -1,15 +1,18 @@
|
|||||||
package mullsox
|
package mullsox
|
||||||
|
|
||||||
import jsoniter "github.com/json-iterator/go"
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
json "github.com/pquerna/ffjson/ffjson"
|
||||||
|
"github.com/valyala/fasthttp"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
baseDomain = "mullvad.net"
|
baseDomain = "mullvad.net"
|
||||||
baseEndpoint = "am.i." + baseDomain
|
baseEndpoint = "am.i." + baseDomain
|
||||||
ipv4Endpoint = `https://ipv4.` + baseEndpoint
|
EndpointCheck4 = `https://ipv4.` + baseEndpoint
|
||||||
ipv6Endpoint = `https://ipv6.` + baseEndpoint
|
EndpointCheck6 = `https://ipv6.` + baseEndpoint
|
||||||
servEndpoint = `https://api.` + baseDomain + `www/relays/all/`
|
EndpointRelays = `https://api.` + baseDomain + `/www/relays/all/`
|
||||||
)
|
)
|
||||||
|
|
||||||
type MyIPDetails struct {
|
type MyIPDetails struct {
|
||||||
@ -33,22 +36,79 @@ type MyIPDetails struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type MullvadServer struct {
|
type MullvadServer struct {
|
||||||
Hostname string `json:"hostname"`
|
Hostname string `json:"hostname"`
|
||||||
CountryCode string `json:"country_code"`
|
CountryCode string `json:"country_code"`
|
||||||
CountryName string `json:"country_name"`
|
CountryName string `json:"country_name"`
|
||||||
CityCode string `json:"city_code"`
|
CityCode string `json:"city_code"`
|
||||||
CityName string `json:"city_name"`
|
CityName string `json:"city_name"`
|
||||||
Active bool `json:"active"`
|
Active bool `json:"active"`
|
||||||
Owned bool `json:"owned"`
|
Owned bool `json:"owned"`
|
||||||
Provider string `json:"provider"`
|
Provider string `json:"provider"`
|
||||||
Ipv4AddrIn string `json:"ipv4_addr_in"`
|
Ipv4AddrIn string `json:"ipv4_addr_in"`
|
||||||
Ipv6AddrIn *string `json:"ipv6_addr_in"`
|
Ipv6AddrIn string `json:"ipv6_addr_in"`
|
||||||
NetworkPortSpeed int `json:"network_port_speed"`
|
NetworkPortSpeed int `json:"network_port_speed"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
StatusMessages []interface{} `json:"status_messages"`
|
Pubkey string `json:"pubkey,omitempty"`
|
||||||
Pubkey string `json:"pubkey,omitempty"`
|
MultihopPort int `json:"multihop_port,omitempty"`
|
||||||
MultihopPort int `json:"multihop_port,omitempty"`
|
SocksName string `json:"socks_name,omitempty"`
|
||||||
SocksName string `json:"socks_name,omitempty"`
|
SSHFingerprintSHA256 string `json:"ssh_fingerprint_sha256,omitempty"`
|
||||||
SshFingerprintSha256 string `json:"ssh_fingerprint_sha256,omitempty"`
|
SSHFingerprintMD5 string `json:"ssh_fingerprint_md5,omitempty"`
|
||||||
SshFingerprintMd5 string `json:"ssh_fingerprint_md5,omitempty"`
|
}
|
||||||
|
|
||||||
|
func (mvs MullvadServer) String() string {
|
||||||
|
return mvs.Hostname
|
||||||
|
}
|
||||||
|
|
||||||
|
type relays []MullvadServer
|
||||||
|
|
||||||
|
func GetMullvadServers() (*relays, error) {
|
||||||
|
var servers = new(relays)
|
||||||
|
req := fasthttp.AcquireRequest()
|
||||||
|
res := fasthttp.AcquireResponse()
|
||||||
|
defer func() {
|
||||||
|
fasthttp.ReleaseRequest(req)
|
||||||
|
fasthttp.ReleaseResponse(res)
|
||||||
|
}()
|
||||||
|
req.Header.SetUserAgent("mulls0x/v0.0.1")
|
||||||
|
req.Header.SetContentType("application/json")
|
||||||
|
req.Header.SetMethod(http.MethodGet)
|
||||||
|
req.SetRequestURI(EndpointRelays)
|
||||||
|
if err := fasthttp.Do(req, res); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(res.Body(), servers); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return servers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetWireguardServers() ([]WireguardServer, error) {
|
||||||
|
srvs, err := GetMullvadServers()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return srvs.getWireguards()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (servers *relays) getWireguards() ([]WireguardServer, error) {
|
||||||
|
var wgs []WireguardServer
|
||||||
|
for _, srv := range *servers {
|
||||||
|
if srv.Type != "wireguard" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if srv.MultihopPort < 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pub, err := encodeBase64ToHex(srv.Pubkey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
wgs = append(wgs, WireguardServer{
|
||||||
|
Parent: &srv,
|
||||||
|
WGPublicKey: pub,
|
||||||
|
In4: srv.Ipv4AddrIn,
|
||||||
|
In6: srv.Ipv6AddrIn,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return wgs, nil
|
||||||
}
|
}
|
||||||
|
14
check.go
14
check.go
@ -6,6 +6,8 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
json "github.com/pquerna/ffjson/ffjson"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CheckIP4(ctx context.Context, h *http.Client) (details *MyIPDetails, err error) {
|
func CheckIP4(ctx context.Context, h *http.Client) (details *MyIPDetails, err error) {
|
||||||
@ -77,14 +79,13 @@ func CheckIP(ctx context.Context, h *http.Client) (v4details *MyIPDetails, v6det
|
|||||||
func checkIP(ctx context.Context, h *http.Client, ipv6 bool) (details *MyIPDetails, err error) {
|
func checkIP(ctx context.Context, h *http.Client, ipv6 bool) (details *MyIPDetails, err error) {
|
||||||
var (
|
var (
|
||||||
resp *http.Response
|
resp *http.Response
|
||||||
cytes []byte
|
|
||||||
target string
|
target string
|
||||||
)
|
)
|
||||||
switch ipv6 {
|
switch ipv6 {
|
||||||
case true:
|
case true:
|
||||||
target = ipv6Endpoint + "/json"
|
target = EndpointCheck6 + "/json"
|
||||||
default:
|
default:
|
||||||
target = ipv4Endpoint + "/json"
|
target = EndpointCheck4 + "/json"
|
||||||
}
|
}
|
||||||
req, _ := http.NewRequestWithContext(ctx, "GET", target, nil)
|
req, _ := http.NewRequestWithContext(ctx, "GET", target, nil)
|
||||||
resp, err = h.Do(req)
|
resp, err = h.Do(req)
|
||||||
@ -95,10 +96,11 @@ func checkIP(ctx context.Context, h *http.Client, ipv6 bool) (details *MyIPDetai
|
|||||||
err = fmt.Errorf("bad status code from %s : %s", target, resp.Status)
|
err = fmt.Errorf("bad status code from %s : %s", target, resp.Status)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cytes, err = io.ReadAll(resp.Body)
|
rbytes, err := io.ReadAll(resp.Body)
|
||||||
|
println(string(rbytes))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return nil, err
|
||||||
}
|
}
|
||||||
err = json.Unmarshal(cytes, &details)
|
json.Unmarshal(rbytes, &details)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/bytedance/sonic"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCheckIP4(t *testing.T) {
|
func TestCheckIP4(t *testing.T) {
|
||||||
@ -13,7 +15,7 @@ func TestCheckIP4(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("%s", err.Error())
|
t.Fatalf("%s", err.Error())
|
||||||
}
|
}
|
||||||
v4j, err4j := json.Marshal(v4)
|
v4j, err4j := sonic.Marshal(v4)
|
||||||
if err4j != nil {
|
if err4j != nil {
|
||||||
t.Fatalf("%s", err4j.Error())
|
t.Fatalf("%s", err4j.Error())
|
||||||
}
|
}
|
||||||
@ -26,7 +28,7 @@ func TestCheckIP6(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("%s", err.Error())
|
t.Fatalf("%s", err.Error())
|
||||||
}
|
}
|
||||||
v6j, err6j := json.Marshal(v6)
|
v6j, err6j := sonic.Marshal(v6)
|
||||||
if err6j != nil {
|
if err6j != nil {
|
||||||
t.Fatalf("%s", err6j.Error())
|
t.Fatalf("%s", err6j.Error())
|
||||||
}
|
}
|
||||||
@ -41,11 +43,11 @@ func TestCheckIPConcurrent(t *testing.T) {
|
|||||||
t.Fatalf("%s", err.Error())
|
t.Fatalf("%s", err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
v4j, err4j := json.Marshal(v4)
|
v4j, err4j := sonic.Marshal(v4)
|
||||||
if err4j != nil {
|
if err4j != nil {
|
||||||
t.Fatalf("%s", err4j.Error())
|
t.Fatalf("%s", err4j.Error())
|
||||||
}
|
}
|
||||||
v6j, err6j := json.Marshal(v6)
|
v6j, err6j := sonic.Marshal(v6)
|
||||||
if err6j != nil {
|
if err6j != nil {
|
||||||
t.Fatalf("%s", err6j.Error())
|
t.Fatalf("%s", err6j.Error())
|
||||||
}
|
}
|
||||||
|
37
cmd/mullf0x/main.go
Normal file
37
cmd/mullf0x/main.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.tcp.direct/kayos/mullsox"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
current, err := mullsox.CheckIP4(context.Background(), http.DefaultClient)
|
||||||
|
if err != nil {
|
||||||
|
println(err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
println("control group: " + current.IP)
|
||||||
|
mvu, err := mullsox.NewMullvadUser(
|
||||||
|
"default",
|
||||||
|
os.Args[1],
|
||||||
|
os.Args[2],
|
||||||
|
os.Args[3],
|
||||||
|
)
|
||||||
|
srvs, err := mullsox.GetWireguardServers()
|
||||||
|
if err != nil {
|
||||||
|
println(err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
for _, srv := range srvs {
|
||||||
|
ip, err := mvu.GetIPv4Out(srv)
|
||||||
|
if err != nil {
|
||||||
|
println(err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
println(srv.String() + ": " + ip)
|
||||||
|
}
|
||||||
|
}
|
9
go.mod
9
go.mod
@ -1,10 +1,3 @@
|
|||||||
module git.tcp.direct/kayos/mullsox
|
module git.tcp.direct/kayos/mullsox
|
||||||
|
|
||||||
go 1.18
|
go 1.19
|
||||||
|
|
||||||
require github.com/json-iterator/go v1.1.12
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
|
||||||
)
|
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
package mullsox
|
|
||||||
|
|
||||||
import "context"
|
|
||||||
|
|
||||||
func GetRelays(ctx context.Context) (ret chan MullvadServer) {
|
|
||||||
ret = make(chan MullvadServer)
|
|
||||||
|
|
||||||
}
|
|
25
relays_test.go
Normal file
25
relays_test.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package mullsox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetRelays(t *testing.T) {
|
||||||
|
srvs, err := GetMullvadServers()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s", err.Error())
|
||||||
|
}
|
||||||
|
// prettyPrint(srvs)
|
||||||
|
t.Run("GetWireguardServers", func(t *testing.T) {
|
||||||
|
wgs, err := srvs.getWireguards()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s", err.Error())
|
||||||
|
}
|
||||||
|
for _, wg := range wgs {
|
||||||
|
pp, _ := json.MarshalIndent(wg, "", "\t")
|
||||||
|
println(strings.ReplaceAll(string(pp), `"`, ""))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
25
user.go
Normal file
25
user.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package mullsox
|
||||||
|
|
||||||
|
type MullvadUser struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Account int `json:"account"`
|
||||||
|
WGPrivateKey string `json:"private_key"`
|
||||||
|
WGIPv4 string `json:"ipv4"`
|
||||||
|
WGDNS string `json:"dns"`
|
||||||
|
WireguardPorts map[int]*WireguardServer `json:"wireguard_ports"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMullvadUser(id, privateKey, mvip, dns string) (*MullvadUser, error) {
|
||||||
|
k, err := encodeBase64ToHex(privateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mvu := &MullvadUser{
|
||||||
|
ID: id,
|
||||||
|
WGIPv4: mvip,
|
||||||
|
WGDNS: dns,
|
||||||
|
WGPrivateKey: k,
|
||||||
|
// Account: account,
|
||||||
|
}
|
||||||
|
return mvu, nil
|
||||||
|
}
|
72
userspace.go
Normal file
72
userspace.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package mullsox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wireguard/conn"
|
||||||
|
"golang.zx2c4.com/wireguard/device"
|
||||||
|
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (mvu *MullvadUser) GetIPv4Out(server WireguardServer) (string, error) {
|
||||||
|
var dnsipas []netip.Addr //nolint:prealloc
|
||||||
|
for _, dnsip := range []string{mvu.WGDNS} {
|
||||||
|
dnsipas = append(dnsipas, netip.MustParseAddr(dnsip))
|
||||||
|
}
|
||||||
|
var localipas []netip.Addr //nolint:prealloc
|
||||||
|
for _, localip := range []string{mvu.WGIPv4} {
|
||||||
|
localipas = append(localipas, netip.MustParseAddr(localip))
|
||||||
|
}
|
||||||
|
tun, tnet, err := netstack.CreateNetTUN(
|
||||||
|
// VPN Adapter IPs
|
||||||
|
localipas,
|
||||||
|
// DNS
|
||||||
|
dnsipas,
|
||||||
|
// MTU
|
||||||
|
1420,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
// fwmark := binary.BigEndian.Uint32([]byte("[redacted]"))
|
||||||
|
dev := device.NewDevice(tun, conn.NewDefaultBind(), device.NewLogger(device.LogLevelVerbose, ""))
|
||||||
|
err = dev.IpcSet(fmt.Sprintf(`private_key=%s
|
||||||
|
fwmark=%d
|
||||||
|
public_key=%s
|
||||||
|
endpoint=%s:%d
|
||||||
|
allowed_ip=0.0.0.0/0`,
|
||||||
|
mvu.WGPrivateKey, fwmark, server.WGPublicKey, server.In4, server.Leapfrog))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
err = dev.Up()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
client := &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
DialContext: tnet.DialContext,
|
||||||
|
/*func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
addr = strings.ReplaceAll(
|
||||||
|
addr, "ipv4.am.i.mullvad.net", "193.138.218.116")
|
||||||
|
ctx, _ = context.WithDeadline(ctx, time.Now().Add(time.Second*20))
|
||||||
|
println("\x1b[32mDialing: ", addr, "\x1b[0m")
|
||||||
|
return tnet.DialContext(ctx, network, addr)
|
||||||
|
},*/
|
||||||
|
/* TLSClientConfig: &tls.Config{
|
||||||
|
ServerName: "ipv4.am.i.mullvad.net",
|
||||||
|
InsecureSkipVerify: false,
|
||||||
|
},
|
||||||
|
*/},
|
||||||
|
}
|
||||||
|
details, err := CheckIP4(context.Background(), client)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return details.IP, nil
|
||||||
|
}
|
35
util.go
Normal file
35
util.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package mullsox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func encodeBase64ToHex(key string) (string, error) {
|
||||||
|
decoded, err := base64.StdEncoding.DecodeString(key)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.New("invalid base64 string: " + key)
|
||||||
|
}
|
||||||
|
if len(decoded) != 32 {
|
||||||
|
return "", errors.New("key should be 32 bytes: " + key)
|
||||||
|
}
|
||||||
|
return hex.EncodeToString(decoded), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const padding = "+-+-+-+-+-+-+-+-+"
|
||||||
|
|
||||||
|
var arrowLeft = padding[:len(padding)-2] + "> "
|
||||||
|
var arrowRight = " <" + padding[:len(padding)-2]
|
||||||
|
|
||||||
|
func prettyPrint(srvs []MullvadServer) { //goland:noinspection ALL
|
||||||
|
for _, srv := range srvs {
|
||||||
|
border := padding + strings.Repeat("-", len(srv.String())) + padding
|
||||||
|
println(border + "\n" + arrowLeft + srv.String() + arrowRight + "\n" + border)
|
||||||
|
pp, _ := json.MarshalIndent(srv, "", "\t")
|
||||||
|
println(strings.ReplaceAll(string(pp), `"`, ""))
|
||||||
|
println("\n+" + strings.Repeat("-+", (len(border)/2)-1) + "\n")
|
||||||
|
}
|
||||||
|
}
|
16
wg_server.go
Normal file
16
wg_server.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package mullsox
|
||||||
|
|
||||||
|
type WireguardServer struct {
|
||||||
|
Parent *MullvadServer
|
||||||
|
WGPublicKey string
|
||||||
|
Leapfrog int
|
||||||
|
In4 string
|
||||||
|
In6 string
|
||||||
|
// Out addresses should be immutable
|
||||||
|
Out4 *string
|
||||||
|
Out6 *string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wgs WireguardServer) String() string {
|
||||||
|
return wgs.Parent.Hostname
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user