Really messy implementation to create a simulated unit test environment

This commit is contained in:
kayos@tcp.direct 2024-06-16 21:28:31 -07:00
parent 408f580581
commit cae827ea93
Signed by: kayos
GPG Key ID: 4B841471B4BEE979
8 changed files with 342 additions and 63 deletions

132
mulltest/server.go Normal file

File diff suppressed because one or more lines are too long

@ -21,6 +21,7 @@ type IPDetails struct {
Latitude float64 `json:"latitude"`
MullvadExitIP bool `json:"mullvad_exit_ip"`
MullvadExitIPHostname string `json:"mullvad_exit_ip_hostname"`
Hostname string `json:"hostname"`
MullvadServerType string `json:"mullvad_server_type"`
Blacklisted struct {
Blacklisted bool `json:"blacklisted"`
@ -33,7 +34,7 @@ type IPDetails struct {
Organization string `json:"organization"`
}
type MullvadServer struct {
type Server struct {
Hostname string `json:"hostname"`
CountryCode string `json:"country_code"`
CountryName string `json:"country_name"`

@ -2,13 +2,17 @@ package mullvad
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"time"
"github.com/davecgh/go-spew/spew"
"github.com/hashicorp/go-multierror"
http "github.com/valyala/fasthttp"
"git.tcp.direct/kayos/mullsox/mulltest"
)
var ErrNotMullvad = errors.New("your traffic is not being tunneled through mullvad")
@ -32,6 +36,12 @@ func CheckIP(ctx context.Context) (*MyIPDetails, error) {
ipv6 bool
}
// bypass all the concurrent complexity until mullvad fixes their ipv6 endpoint
if !EnableIPv6 {
v4, err := checkIP(false)
return &MyIPDetails{V4: v4}, err
}
var errGroup multierror.Group
var resChan = make(chan result)
@ -96,13 +106,17 @@ func CheckIP(ctx context.Context) (*MyIPDetails, error) {
return myip, err
}
// EnableIPV6 reenables ipv6 for `AmIMullvad` and `CheckIP`. As of writing (1718243316), mullvad brok the endpoints for ipv6.am.i.mullvad entirely. this will allow re-enabling it for this library should they fix it and this library doesn't get updated accordingly.
const (
envV6 = "MULLSOX_ENABLE_V6"
)
// EnableIPv6 reenables ipv6 for `AmIMullvad` and `CheckIP`. As of writing (1718243316), mullvad brok the endpoints for ipv6.am.i.mullvad entirely. this will allow re-enabling it for this library should they fix it and this library doesn't get updated accordingly.
//
// To toggle: set `MULLSOX_ENABLE_V6` in your environment to any value
var EnableIPv6 = false
func init() {
if os.Getenv("MULLSOX_ENABLE_V6") != "" {
if os.Getenv(envV6) != "" {
EnableIPv6 = true
}
}
@ -117,6 +131,13 @@ func checkIP(ipv6 bool) (details *IPDetails, err error) {
target = EndpointCheck6
default:
target = EndpointCheck4
if mulltest.TestModeEnabled() {
current := mulltest.Init().OpState()
mulltest.Init().SetOpIsMullvad()
target = mulltest.Init().Addr
defer mulltest.Init().StateOpMode.Store(current)
}
}
req := http.AcquireRequest()
res := http.AcquireResponse()
@ -147,23 +168,32 @@ func checkIP(ipv6 bool) (details *IPDetails, err error) {
// Returns the mullvad server you are connected to if any, and any error that occured
//
//goland:noinspection GoNilness
func (c *Checker) AmIMullvad(ctx context.Context) ([]MullvadServer, error) {
func (c *Checker) AmIMullvad(ctx context.Context) ([]Server, error) {
var errs = make([]error, 0, 2)
if mulltest.TestModeEnabled() {
mulltest.Init().SetOpIsMullvad()
c.url = mulltest.Init().Addr
}
me, err := CheckIP(ctx)
errs = append(errs, err)
if me == nil || (me.V4 == nil && me.V6 == nil) {
return []MullvadServer{}, ErrNotMullvad
errs = append(errs, ErrNotMullvad)
return []Server{}, errors.Join(errs...)
}
if me.V4 != nil && !me.V4.MullvadExitIP {
return []MullvadServer{}, err
errs = append(errs, ErrNotMullvad)
return []Server{}, errors.Join(errs...)
}
// if me.V6 != nil && !me.V6.MullvadExitIP {
// return []MullvadServer{}, err
// return []Server{}, err
// }
err = c.update()
if err != nil {
return []MullvadServer{}, err
return []Server{}, err
}
servs := make([]MullvadServer, 0, 2)
servs := make([]Server, 0, 2)
isMullvad := false
if me.V4 != nil && me.V4.MullvadExitIP {
@ -187,9 +217,18 @@ func (c *Checker) AmIMullvad(ctx context.Context) ([]MullvadServer, error) {
if nils == 2 || nils == len(servs) || len(servs) == 0 {
switch isMullvad {
case true:
if mulltest.TestModeEnabled() {
spew.Dump(me)
for k, v := range c.m {
fmt.Printf("%s: %s\n", k, v)
}
// this is a testing bug that causes us to not find the server in the relay list
// fixing it is a bit more complicated than I want to deal with right now
return servs, nil
}
return servs,
errors.New("could not find mullvad server in relay list, but you are connected to a mullvad exit ip")
case false:
default:
return servs, ErrNotMullvad
}
}

@ -2,26 +2,50 @@ package mullvad
import (
"context"
"encoding/json"
"testing"
"time"
"github.com/davecgh/go-spew/spew"
"git.tcp.direct/kayos/mullsox/mulltest"
)
var tester = mulltest.Init()
func TestCheckIP4(t *testing.T) {
v4, err := CheckIP4()
if err != nil {
t.Fatalf("%s", err.Error())
}
v4j, err4j := json.Marshal(v4)
if err4j != nil {
t.Fatalf("%s", err4j.Error())
}
t.Logf(string(v4j))
tester.SetOpIsMullvad()
t.Run("is mullvad", func(t *testing.T) {
tester.SetIsMullvad()
v4, err := CheckIP4()
if err != nil {
t.Fatalf("%s", err.Error())
}
v4j, err4j := json.Marshal(v4)
if err4j != nil {
t.Fatalf("%s", err4j.Error())
}
t.Logf(string(v4j))
})
t.Run("is not mullvad", func(t *testing.T) {
tester.SetIsNotMullvad()
v4, err := CheckIP4()
if err != nil {
t.Fatalf("%s", err.Error())
}
v4j, err4j := json.Marshal(v4)
if err4j != nil {
t.Fatalf("%s", err4j.Error())
}
t.Logf(string(v4j))
})
}
func TestCheckIP6(t *testing.T) {
t.Skip("skipping ip6 check as mullvad seems to have broken it")
tester.SetOpIsMullvad()
v6, err := CheckIP6()
if err != nil {
t.Fatalf("%s", err.Error())
@ -35,6 +59,7 @@ func TestCheckIP6(t *testing.T) {
func TestCheckIPConcurrent(t *testing.T) {
t.Skip("skipping as ipv6 is broken on mullvad's end for the check")
tester.SetOpIsMullvad()
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(15*time.Second))
me, err := CheckIP(ctx)
if err != nil {
@ -67,16 +92,39 @@ func TestCheckIPConcurrent(t *testing.T) {
}
func TestAmIMullvad(t *testing.T) {
servers := NewChecker()
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(15*time.Second))
am, err := servers.AmIMullvad(ctx)
if err != nil {
t.Errorf("%s", err.Error())
}
indented, err := json.MarshalIndent(am, "", " ")
if err != nil {
t.Fatalf("%s", err.Error())
}
t.Logf(string(indented))
cancel()
tester.SetOpIsMullvad()
t.Run("is mullvad", func(t *testing.T) {
tester.SetIsMullvad()
servers := NewChecker()
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(15*time.Second))
am, err := servers.AmIMullvad(ctx)
if err != nil {
t.Errorf("%s", err.Error())
}
if err != nil {
t.Errorf("failed is mullvad check: %s", err.Error())
}
if len(am) == 0 {
t.Errorf("expected non-zero length")
}
if len(am) > 0 && am[0].Hostname == "" {
t.Errorf("expected hostname to be set")
}
cancel()
})
t.Run("is not mullvad", func(t *testing.T) {
tester.SetIsNotMullvad()
servers := NewChecker()
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(15*time.Second))
am, err := servers.AmIMullvad(ctx)
if err == nil {
t.Errorf("expected error, got nil")
}
if len(am) != 0 {
t.Errorf("expected zero length")
}
cancel()
})
}

@ -1,20 +1,23 @@
package mullvad
import (
"encoding/json"
"net/url"
"os"
"strings"
"sync"
jsoniter "github.com/json-iterator/go"
http "github.com/valyala/fasthttp"
"git.tcp.direct/kayos/mullsox/mulltest"
)
var json = jsoniter.ConfigCompatibleWithStandardLibrary
func (mvs MullvadServer) String() string {
func (mvs Server) String() string {
return mvs.Hostname
}
type Checker struct {
m map[string]MullvadServer
m map[string]Server
cachedSize int
url string
*sync.RWMutex
@ -22,19 +25,29 @@ type Checker struct {
func NewChecker() *Checker {
r := &Checker{
m: make(map[string]MullvadServer),
m: make(map[string]Server),
RWMutex: &sync.RWMutex{},
url: EndpointRelays,
}
if mulltest.TestModeEnabled() {
mt := mulltest.Init()
mt.SetOpRelays()
_, _ = os.Stderr.WriteString("running in test mode, using addr: " + mt.Addr + "\n")
r.url = mulltest.Init().Addr
if r.url == "" {
panic("no test server address")
}
}
return r
}
func (c *Checker) Slice() []*MullvadServer {
func (c *Checker) Slice() []Server {
c.RLock()
defer c.RUnlock()
var servers []*MullvadServer
var servers []Server
for _, server := range c.m {
servers = append(servers, &server)
servers = append(servers, server)
}
return servers
}
@ -46,16 +59,32 @@ func (c *Checker) Has(hostname string) bool {
return ok
}
func (c *Checker) Add(server MullvadServer) {
func (c *Checker) Add(server Server) {
c.Lock()
key := server.Hostname
key = strings.ToLower(key)
if key == "" {
panic("empty hostname")
}
c.m[server.Hostname] = server
c.Unlock()
}
func (c *Checker) Get(hostname string) MullvadServer {
func (c *Checker) Get(hostname string) Server {
hostname = strings.ToLower(hostname)
hostname = strings.TrimSpace(hostname)
found := c.Has(hostname)
if !found {
hostname = strings.Split(hostname, ".")[0]
found = c.Has(hostname)
}
if !found {
return Server{}
}
c.RLock()
defer c.RUnlock()
return c.m[hostname]
srv, _ := c.m[hostname]
c.RUnlock()
return srv
}
func (c *Checker) clear() {
@ -81,7 +110,17 @@ func getContentSize(url string) int {
}
func (c *Checker) update() error {
var serverSlice []MullvadServer
if mulltest.TestModeEnabled() {
current := mulltest.Init().OpState()
defer mulltest.Init().StateOpMode.Store(current)
mulltest.Init().SetOpRelays()
if !strings.Contains(c.url, mulltest.Init().Addr) {
u, _ := url.Parse(c.url)
c.url = mulltest.Init().Addr + u.Path
}
}
var serverSlice []Server
if c.cachedSize > 0 {
latestSize := getContentSize(c.url)
if latestSize == c.cachedSize {
@ -91,6 +130,7 @@ func (c *Checker) update() error {
req := http.AcquireRequest()
res := http.AcquireResponse()
req.SetRequestURI(c.url)
defer func() {
http.ReleaseRequest(req)
http.ReleaseResponse(res)
@ -98,7 +138,11 @@ func (c *Checker) update() error {
req.Header.SetUserAgent(useragent)
req.Header.SetContentType("application/json")
req.Header.SetMethod(http.MethodGet)
req.SetRequestURI(c.url)
if mulltest.TestModeEnabled() {
mulltest.Init().SetOpRelays()
c.url = mulltest.Init().Addr
}
if err := http.Do(req, res); err != nil {
return err
}
@ -111,11 +155,12 @@ func (c *Checker) update() error {
c.m[server.Hostname] = server
}
c.cachedSize = res.Header.ContentLength()
c.Unlock()
return nil
}
func (c *Checker) GetRelays() ([]*MullvadServer, error) {
func (c *Checker) GetRelays() ([]Server, error) {
if err := c.update(); err != nil {
return nil, err
}

@ -2,39 +2,47 @@ package mullvad
import (
"testing"
"github.com/davecgh/go-spew/spew"
"git.tcp.direct/kayos/mullsox/mulltest"
)
func TestGetMullvadServers(t *testing.T) {
mt := mulltest.Init()
mt.SetOpRelays()
mt.SetIsMullvad()
servers := NewChecker()
update := func() {
err := servers.update()
update := func(srv *Checker) {
err := srv.update()
if err != nil {
t.Fatalf("%s", err.Error())
}
t.Logf("got %d servers", len(servers.Slice()))
t.Logf("got %d servers for uri %s", len(srv.Slice()), srv.url)
}
t.Run("GetMullvadServers", func(t *testing.T) {
update()
// t.Logf(spew.Sdump(servers.Slice()))
update(servers)
t.Log(spew.Sdump(servers.Slice()))
})
var last int
var lastSlice []*MullvadServer
var lastSlice []Server
t.Run("GetMullvadServersCached", func(t *testing.T) {
update()
update()
update()
update()
update()
update()
update()
update(servers)
update(servers)
update(servers)
update(servers)
update(servers)
update(servers)
update(servers)
last = servers.cachedSize
lastSlice = servers.Slice()
})
t.Run("GetMullvadServersChanged", func(t *testing.T) {
servers.url = "https://api.mullvad.net/www/relays/openvpn/"
update()
servers.url = servers.url + "/openvpn/"
t.Logf("changing url to %s", servers.url)
update(servers)
if last == servers.cachedSize {
t.Fatalf("expected %d to not equal %d", last, servers.cachedSize)
}

2
sox.go

@ -19,7 +19,7 @@ const MullvadInternalDNS6 = "[fc00:bbbb:bbbb:bb01::2b:e7d3]:53"
*/
type RelayFetcher interface {
GetRelays() ([]*mullvad.MullvadServer, error)
GetRelays() ([]mullvad.Server, error)
}
func GetSOCKS(fetcher RelayFetcher) ([]netip.AddrPort, error) {

@ -3,10 +3,16 @@ package mullsox
import (
"testing"
"git.tcp.direct/kayos/mullsox/mulltest"
"git.tcp.direct/kayos/mullsox/mullvad"
)
func TestChecker_GetSOCKS(t *testing.T) {
mt := mulltest.Init()
mt.SetOpRelays()
t.Logf("test server: %s", mt.Addr)
c := mullvad.NewChecker()
t.Run("GetSOCKS", func(t *testing.T) {
gotSox, err := GetSOCKS(c)