Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: introduce the wireguard experiment #1626

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/AlecAivazis/survey/v2 v2.3.7
github.com/Psiphon-Labs/psiphon-tunnel-core v1.0.11-0.20240424194431-3612a5a6fb4c
github.com/alecthomas/kingpin/v2 v2.4.0
github.com/amnezia-vpn/amneziawg-go v0.2.8
github.com/apex/log v1.9.0
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
github.com/cloudflare/circl v1.3.8
Expand Down Expand Up @@ -81,15 +82,17 @@ require (
github.com/segmentio/fasthash v1.0.3 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/testify v1.9.0 // indirect
github.com/tevino/abool/v2 v2.1.0 // indirect
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
gitlab.com/yawning/edwards25519-extra v0.0.0-20231005122941-2149dcafc266 // indirect
go.uber.org/mock v0.4.0 // indirect
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
golang.org/x/exp/typeparams v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gvisor.dev/gvisor v0.0.0-20230922204349-b3f36d574a7f // indirect
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 // indirect
)

require (
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjH
github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 h1:ez/4by2iGztzR4L0zgAOR8lTQK9VlyBVVd7G4omaOQs=
github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
github.com/amnezia-vpn/amneziawg-go v0.2.8 h1:J8PPx+hylx5nNZ5U1+ECFj9noGkcm2ThmSV9rBNDgy8=
github.com/amnezia-vpn/amneziawg-go v0.2.8/go.mod h1:12g0XRbFeGbpXvuCmBOV21YxLWSFnUFJnwgrzyHBUyk=
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/apex/log v1.9.0 h1:FHtw/xuaM8AgmvDDTI9fiwoAL25Sq2cxojnZICUU8l0=
Expand Down Expand Up @@ -531,6 +533,8 @@ github.com/templexxx/cpu v0.1.0/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6H
github.com/templexxx/xorsimd v0.4.1/go.mod h1:W+ffZz8jJMH2SXwuKu9WhygqBMbFnp14G2fqEr8qaNo=
github.com/templexxx/xorsimd v0.4.2 h1:ocZZ+Nvu65LGHmCLZ7OoCtg8Fx8jnHKK37SjvngUoVI=
github.com/templexxx/xorsimd v0.4.2/go.mod h1:HgwaPoDREdi6OnULpSfxhzaiiSUY4Fi3JPn1wpt28NI=
github.com/tevino/abool/v2 v2.1.0 h1:7w+Vf9f/5gmKT4m4qkayb33/92M+Um45F2BkHOR+L/c=
github.com/tevino/abool/v2 v2.1.0/go.mod h1:+Lmlqk6bHDWHqN1cbxqhwEAwMPXgc8I1SDEamtseuXY=
github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0=
github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk=
github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk=
Expand Down Expand Up @@ -757,6 +761,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
Expand Down Expand Up @@ -800,6 +806,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gvisor.dev/gvisor v0.0.0-20230922204349-b3f36d574a7f h1:w4K7S8+VKrhX67mFdUymQUsGVbEElPCN0v7U0DoLpUw=
gvisor.dev/gvisor v0.0.0-20230922204349-b3f36d574a7f/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
Expand Down
176 changes: 176 additions & 0 deletions internal/experiment/wireguard/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package wireguard

import (
"crypto/sha1"
"encoding/base64"
"encoding/hex"
"fmt"
"io"
)

var (
// defaultNameserver is the dns server using for resolving names inside the wg tunnel.
defaultNameserver = "8.8.8.8"
)

// Config contains the experiment config.
//
// This contains all the settings that user can set to modify the behaviour
// of this experiment. By tagging these variables with `ooni:"..."`, we allow
// miniooni's -O flag to find them and set them.
type Config struct {
Verbose bool `ooni:"Use extra-verbose mode in wireguard logs"`

// These flags modify what sensitive information is stored in the report and submitted to the backend.
PublicTarget bool `ooni:"Treat the target endpoint as public data (if true, it will be included in the report)"`
PublicAmneziaParameters bool `ooni:"Treat the AmneziaWG advanced security parameters as public data"`

// Safe_XXX options are not sent to the backend for archival by default.
SafeRemote string `ooni:"Remote to connect to using WireGuard"`
SafeIP string `ooni:"Allocated IP for this peer"`

// Keys are base-64 encoded
SafePrivateKey string `ooni:"Private key to connect to remote (base64)"`
SafePublicKey string `ooni:"Public key of the remote (base64)"`
SafePresharedKey string `ooni:"Pre-shared key for authentication (base64)"`

// Optional obfuscation parameters for AmneziaWG
SafeJc string `ooni:"jc"`
SafeJmin string `ooni:"jmin"`
SafeJmax string `ooni:"jmax"`
SafeS1 string `ooni:"s1"`
SafeS2 string `ooni:"s2"`
SafeH1 string `ooni:"h1"`
SafeH2 string `ooni:"h2"`
SafeH3 string `ooni:"h3"`
SafeH4 string `ooni:"h4"`
}

type wireguardOptions struct {
// common wireguard parameters
endpoint string
ip string
ns string

// keys are hex-encoded
pubKey string
privKey string
presharedKey string

// optional parameters for AmneziaWG nodes
jc string
jmin string
jmax string
s1 string
s2 string
h1 string
h2 string
h3 string
h4 string
}

// amneziaValues returns an array with all the amnezia-specific configuration
// parameters.
func (wo *wireguardOptions) amneziaValues() []string {
return []string{
wo.jc, wo.jmin, wo.jmax,
wo.s1, wo.s2,
wo.h1, wo.h2, wo.h3, wo.h4,
}
}

// validate returns true if this looks like a sensible wireguard configuration.
func (wo *wireguardOptions) validate() bool {
if wo.endpoint == "" || wo.ip == "" || wo.pubKey == "" || wo.privKey == "" || wo.presharedKey == "" {
return false
}
if isAnyFilled(wo.amneziaValues()...) {
return !isAnyEmpty(wo.amneziaValues()...)
}
return true
}

// isAmneziaFlavored returns true if none of the mandatory amnezia fields are empty.
func (wo *wireguardOptions) isAmneziaFlavored() bool {
return !isAnyEmpty(wo.amneziaValues()...)
}

// amneziaConfigHash is a hash representation of the custom parameters in this amneziaWG node.
// intended to be used if PublicAmneziaParameters=false, so that we can verify that we're testing
// the same node.
func (wo *wireguardOptions) configurationHash() string {
if !wo.isAmneziaFlavored() {
return ""
}
return sha1Sum(append(wo.amneziaValues(), wo.endpoint)...)
}

func sha1Sum(strings ...string) string {
hasher := sha1.New()
for _, str := range strings {
io.WriteString(hasher, str)
}
return fmt.Sprintf("%x", hasher.Sum(nil))
}

func newWireguardOptionsFromConfig(c *Config) (*wireguardOptions, error) {
o := &wireguardOptions{}

pub, err := base64.StdEncoding.DecodeString(c.SafePublicKey)
if err != nil {
return nil, fmt.Errorf("%w: cannot decode public key", ErrInvalidInput)
}
pubHex := hex.EncodeToString(pub)
o.pubKey = pubHex

priv, err := base64.StdEncoding.DecodeString(c.SafePrivateKey)
if err != nil {
return nil, fmt.Errorf("%w: cannot decode private key", ErrInvalidInput)
}
privHex := hex.EncodeToString(priv)
o.privKey = privHex

psk, err := base64.StdEncoding.DecodeString(c.SafePresharedKey)
if err != nil {
return nil, fmt.Errorf("%w: cannot decode pre-shared key", ErrInvalidInput)
}
pskHex := hex.EncodeToString(psk)
o.presharedKey = pskHex

// TODO(ainghazal): reconcile this with Input if c.PublicTarget=true
o.endpoint = c.SafeRemote

o.ip = c.SafeIP

// amnezia parameters
o.jc = c.SafeJc
o.jmin = c.SafeJmin
o.jmax = c.SafeJmax
o.s1 = c.SafeS1
o.s2 = c.SafeS2
o.h1 = c.SafeH1
o.h2 = c.SafeH2
o.h3 = c.SafeH3
o.h4 = c.SafeH4

o.ns = defaultNameserver
return o, nil
}

func isAnyFilled(fields ...string) bool {
for _, f := range fields {
if f != "" {
return true
}
}
return false
}

func isAnyEmpty(fields ...string) bool {
for _, f := range fields {
if f == "" {
return true
}
}
return false
}
Loading
Loading