Compare commits
3 Commits
Author | SHA1 | Date | |
---|---|---|---|
293f10ac51 | |||
359b3a6cc6 | |||
4a41defb69 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
.idea/
|
||||
*.save
|
||||
*.swp
|
||||
mullf0x
|
||||
|
110
api.go
110
api.go
@ -1,15 +1,18 @@
|
||||
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 (
|
||||
baseDomain = "mullvad.net"
|
||||
baseEndpoint = "am.i." + baseDomain
|
||||
ipv4Endpoint = `https://ipv4.` + baseEndpoint
|
||||
ipv6Endpoint = `https://ipv6.` + baseEndpoint
|
||||
servEndpoint = `https://api.` + baseDomain + `www/relays/all/`
|
||||
baseDomain = "mullvad.net"
|
||||
baseEndpoint = "am.i." + baseDomain
|
||||
EndpointCheck4 = `https://ipv4.` + baseEndpoint
|
||||
EndpointCheck6 = `https://ipv6.` + baseEndpoint
|
||||
EndpointRelays = `https://api.` + baseDomain + `/www/relays/all/`
|
||||
)
|
||||
|
||||
type MyIPDetails struct {
|
||||
@ -33,22 +36,79 @@ type MyIPDetails struct {
|
||||
}
|
||||
|
||||
type MullvadServer struct {
|
||||
Hostname string `json:"hostname"`
|
||||
CountryCode string `json:"country_code"`
|
||||
CountryName string `json:"country_name"`
|
||||
CityCode string `json:"city_code"`
|
||||
CityName string `json:"city_name"`
|
||||
Active bool `json:"active"`
|
||||
Owned bool `json:"owned"`
|
||||
Provider string `json:"provider"`
|
||||
Ipv4AddrIn string `json:"ipv4_addr_in"`
|
||||
Ipv6AddrIn *string `json:"ipv6_addr_in"`
|
||||
NetworkPortSpeed int `json:"network_port_speed"`
|
||||
Type string `json:"type"`
|
||||
StatusMessages []interface{} `json:"status_messages"`
|
||||
Pubkey string `json:"pubkey,omitempty"`
|
||||
MultihopPort int `json:"multihop_port,omitempty"`
|
||||
SocksName string `json:"socks_name,omitempty"`
|
||||
SshFingerprintSha256 string `json:"ssh_fingerprint_sha256,omitempty"`
|
||||
SshFingerprintMd5 string `json:"ssh_fingerprint_md5,omitempty"`
|
||||
Hostname string `json:"hostname"`
|
||||
CountryCode string `json:"country_code"`
|
||||
CountryName string `json:"country_name"`
|
||||
CityCode string `json:"city_code"`
|
||||
CityName string `json:"city_name"`
|
||||
Active bool `json:"active"`
|
||||
Owned bool `json:"owned"`
|
||||
Provider string `json:"provider"`
|
||||
Ipv4AddrIn string `json:"ipv4_addr_in"`
|
||||
Ipv6AddrIn string `json:"ipv6_addr_in"`
|
||||
NetworkPortSpeed int `json:"network_port_speed"`
|
||||
Type string `json:"type"`
|
||||
Pubkey string `json:"pubkey,omitempty"`
|
||||
MultihopPort int `json:"multihop_port,omitempty"`
|
||||
SocksName string `json:"socks_name,omitempty"`
|
||||
SSHFingerprintSHA256 string `json:"ssh_fingerprint_sha256,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"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
json "github.com/pquerna/ffjson/ffjson"
|
||||
)
|
||||
|
||||
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) {
|
||||
var (
|
||||
resp *http.Response
|
||||
cytes []byte
|
||||
target string
|
||||
)
|
||||
switch ipv6 {
|
||||
case true:
|
||||
target = ipv6Endpoint + "/json"
|
||||
target = EndpointCheck6 + "/json"
|
||||
default:
|
||||
target = ipv4Endpoint + "/json"
|
||||
target = EndpointCheck4 + "/json"
|
||||
}
|
||||
req, _ := http.NewRequestWithContext(ctx, "GET", target, nil)
|
||||
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)
|
||||
return
|
||||
}
|
||||
cytes, err = io.ReadAll(resp.Body)
|
||||
rbytes, err := io.ReadAll(resp.Body)
|
||||
println(string(rbytes))
|
||||
if err != nil {
|
||||
return
|
||||
return nil, err
|
||||
}
|
||||
err = json.Unmarshal(cytes, &details)
|
||||
json.Unmarshal(rbytes, &details)
|
||||
return
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ import (
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
)
|
||||
|
||||
func TestCheckIP4(t *testing.T) {
|
||||
@ -13,7 +15,7 @@ func TestCheckIP4(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err.Error())
|
||||
}
|
||||
v4j, err4j := json.Marshal(v4)
|
||||
v4j, err4j := sonic.Marshal(v4)
|
||||
if err4j != nil {
|
||||
t.Fatalf("%s", err4j.Error())
|
||||
}
|
||||
@ -26,7 +28,7 @@ func TestCheckIP6(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err.Error())
|
||||
}
|
||||
v6j, err6j := json.Marshal(v6)
|
||||
v6j, err6j := sonic.Marshal(v6)
|
||||
if err6j != nil {
|
||||
t.Fatalf("%s", err6j.Error())
|
||||
}
|
||||
@ -41,11 +43,11 @@ func TestCheckIPConcurrent(t *testing.T) {
|
||||
t.Fatalf("%s", err.Error())
|
||||
}
|
||||
}
|
||||
v4j, err4j := json.Marshal(v4)
|
||||
v4j, err4j := sonic.Marshal(v4)
|
||||
if err4j != nil {
|
||||
t.Fatalf("%s", err4j.Error())
|
||||
}
|
||||
v6j, err6j := json.Marshal(v6)
|
||||
v6j, err6j := sonic.Marshal(v6)
|
||||
if err6j != nil {
|
||||
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
|
||||
|
||||
go 1.18
|
||||
|
||||
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
|
||||
)
|
||||
go 1.19
|
||||
|
@ -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