From e6fc792f4f4a4d3eb3aece4743f18476450e9b23 Mon Sep 17 00:00:00 2001 From: Zhurik Date: Wed, 11 Mar 2026 16:55:28 +0300 Subject: [PATCH] feat(wireguard): amneziawg implementation (#3150) --- .gitignore | 1 + Dockerfile | 17 ++ go.mod | 2 +- go.sum | 16 +- internal/configuration/settings/amneziawg.go | 228 ++++++++++++++++++ internal/configuration/settings/openvpn.go | 2 + internal/configuration/settings/wireguard.go | 25 +- .../configuration/sources/files/reader.go | 32 +++ .../configuration/sources/files/wireguard.go | 54 ++++- .../sources/files/wireguard_test.go | 17 +- .../configuration/sources/secrets/reader.go | 45 ++++ internal/provider/utils/wireguard.go | 22 ++ internal/provider/utils/wireguard_test.go | 22 ++ internal/wireguard/amnezia_settings.go | 58 +++++ internal/wireguard/common.go | 28 +++ internal/wireguard/log.go | 11 - internal/wireguard/log_test.go | 23 -- internal/wireguard/run.go | 35 ++- internal/wireguard/settings.go | 7 +- internal/wireguard/userspaces.go | 58 +++++ 20 files changed, 635 insertions(+), 68 deletions(-) create mode 100644 internal/configuration/settings/amneziawg.go create mode 100644 internal/wireguard/amnezia_settings.go create mode 100644 internal/wireguard/common.go delete mode 100644 internal/wireguard/log_test.go create mode 100644 internal/wireguard/userspaces.go diff --git a/.gitignore b/.gitignore index 755ec9c2..c630cce1 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ scratch.txt +.DS_Store diff --git a/Dockerfile b/Dockerfile index b2151dbe..1619e998 100644 --- a/Dockerfile +++ b/Dockerfile @@ -112,6 +112,23 @@ ENV VPN_SERVICE_PROVIDER=pia \ WIREGUARD_ADDRESSES_SECRETFILE=/run/secrets/wireguard_addresses \ WIREGUARD_MTU= \ WIREGUARD_IMPLEMENTATION=auto \ + # Wireguard AmneziaWG userspace obfuscation (requires WIREGUARD_IMPLEMENTATION=amneziawg) + AMNEZIAWG_JC=0 \ + AMNEZIAWG_JMIN=0 \ + AMNEZIAWG_JMAX=0 \ + AMNEZIAWG_S1=0 \ + AMNEZIAWG_S2=0 \ + AMNEZIAWG_S3=0 \ + AMNEZIAWG_S4=0 \ + AMNEZIAWG_H1= \ + AMNEZIAWG_H2= \ + AMNEZIAWG_H3= \ + AMNEZIAWG_H4= \ + AMNEZIAWG_I1= \ + AMNEZIAWG_I2= \ + AMNEZIAWG_I3= \ + AMNEZIAWG_I4= \ + AMNEZIAWG_I5= \ # VPN server port forwarding VPN_PORT_FORWARDING=off \ VPN_PORT_FORWARDING_PROVIDER= \ diff --git a/go.mod b/go.mod index cc748458..81c4b930 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.25.0 require ( github.com/ProtonMail/go-srp v0.0.7 + github.com/amnezia-vpn/amneziawg-go v0.2.16 github.com/breml/rootcerts v0.3.4 github.com/fatih/color v1.18.0 github.com/golang/mock v1.6.0 @@ -59,7 +60,6 @@ require ( golang.org/x/crypto v0.48.0 // indirect golang.org/x/mod v0.32.0 // indirect golang.org/x/sync v0.19.0 // indirect - golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.41.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect google.golang.org/protobuf v1.35.1 // indirect diff --git a/go.sum b/go.sum index 0f37e981..1bbb26ea 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ github.com/ProtonMail/go-crypto v1.3.0-proton h1:tAQKQRZX/73VmzK6yHSCaRUOvS/3OYS github.com/ProtonMail/go-crypto v1.3.0-proton/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= github.com/ProtonMail/go-srp v0.0.7 h1:Sos3Qk+th4tQR64vsxGIxYpN3rdnG9Wf9K4ZloC1JrI= github.com/ProtonMail/go-srp v0.0.7/go.mod h1:giCp+7qRnMIcCvI6V6U3S1lDDXDQYx2ewJ6F/9wdlJk= +github.com/amnezia-vpn/amneziawg-go v0.2.16 h1:XY6HOq/xtqH8ZXMncRWkjFs85EKdN10NLNnw23kTpE0= +github.com/amnezia-vpn/amneziawg-go v0.2.16/go.mod h1:nRkPpIzjCxMW8pZKXTRkpqAQVlmFJdVOGkeQSC7wbms= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/breml/rootcerts v0.3.4 h1:9i7WNl/ctd9OEAOaTfLy//Wrlfxq/tRQ7v4okYFN9Ys= @@ -27,8 +29,8 @@ github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= @@ -111,6 +113,8 @@ github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqTosly github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -165,8 +169,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= @@ -193,8 +197,8 @@ gopkg.in/ini.v1 v1.67.1/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ= -gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY= +gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 h1:ze1vwAdliUAr68RQ5NtufWaXaOg8WUO2OACzEV+TNdE= +gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk= kernel.org/pub/linux/libs/security/libcap/cap v1.2.70 h1:QnLPkuDWWbD5C+3DUA2IUXai5TK6w2zff+MAGccqdsw= kernel.org/pub/linux/libs/security/libcap/cap v1.2.70/go.mod h1:/iBwcj9nbLejQitYvUm9caurITQ6WyNHibJk6Q9fiS4= kernel.org/pub/linux/libs/security/libcap/psx v1.2.70 h1:HsB2G/rEQiYyo1bGoQqHZ/Bvd6x1rERQTNdPr1FyWjI= diff --git a/internal/configuration/settings/amneziawg.go b/internal/configuration/settings/amneziawg.go new file mode 100644 index 00000000..7836f092 --- /dev/null +++ b/internal/configuration/settings/amneziawg.go @@ -0,0 +1,228 @@ +package settings + +import ( + "errors" + "fmt" + "strconv" + "strings" + + "github.com/qdm12/gosettings" + "github.com/qdm12/gosettings/reader" + "github.com/qdm12/gotree" +) + +type AmneziaWg struct { + JunkPacketCount *uint16 `json:"junk_packet_count"` + JunkPacketMin *uint16 `json:"junk_packet_min"` + JunkPacketMax *uint16 `json:"junk_packet_max"` + PaddingS1 *uint16 `json:"padding_s1"` + PaddingS2 *uint16 `json:"padding_s2"` + PaddingS3 *uint16 `json:"padding_s3"` + PaddingS4 *uint16 `json:"padding_s4"` + HeaderH1 *string `json:"header_h1"` + HeaderH2 *string `json:"header_h2"` + HeaderH3 *string `json:"header_h3"` + HeaderH4 *string `json:"header_h4"` + InitPacketI1 *string `json:"init_packet_i1"` + InitPacketI2 *string `json:"init_packet_i2"` + InitPacketI3 *string `json:"init_packet_i3"` + InitPacketI4 *string `json:"init_packet_i4"` + InitPacketI5 *string `json:"init_packet_i5"` +} + +func (s *AmneziaWg) read(r *reader.Reader) error { + uint16Fields := map[string]*uint16{ + "AMNEZIAWG_JC": s.JunkPacketCount, + "AMNEZIAWG_JMIN": s.JunkPacketMin, + "AMNEZIAWG_JMAX": s.JunkPacketMax, + "AMNEZIAWG_S1": s.PaddingS1, + "AMNEZIAWG_S2": s.PaddingS2, + "AMNEZIAWG_S3": s.PaddingS3, + "AMNEZIAWG_S4": s.PaddingS4, + } + for key, dst := range uint16Fields { + v, err := r.Uint16Ptr(key) + if err != nil { + return err + } else if v != nil { + *dst = *v + } + } + stringFields := map[string]*string{ + "AMNEZIAWG_H1": s.HeaderH1, + "AMNEZIAWG_H2": s.HeaderH2, + "AMNEZIAWG_H3": s.HeaderH3, + "AMNEZIAWG_H4": s.HeaderH4, + "AMNEZIAWG_I1": s.InitPacketI1, + "AMNEZIAWG_I2": s.InitPacketI2, + "AMNEZIAWG_I3": s.InitPacketI3, + "AMNEZIAWG_I4": s.InitPacketI4, + "AMNEZIAWG_I5": s.InitPacketI5, + } + opt := reader.ForceLowercase(false) + for key, dst := range stringFields { + v := r.Get(key, opt) + if v != nil { + *dst = *v + } + } + return nil +} + +func (s AmneziaWg) copy() (copied AmneziaWg) { + return AmneziaWg{ + JunkPacketCount: gosettings.CopyPointer(s.JunkPacketCount), + JunkPacketMin: gosettings.CopyPointer(s.JunkPacketMin), + JunkPacketMax: gosettings.CopyPointer(s.JunkPacketMax), + PaddingS1: gosettings.CopyPointer(s.PaddingS1), + PaddingS2: gosettings.CopyPointer(s.PaddingS2), + PaddingS3: gosettings.CopyPointer(s.PaddingS3), + PaddingS4: gosettings.CopyPointer(s.PaddingS4), + HeaderH1: gosettings.CopyPointer(s.HeaderH1), + HeaderH2: gosettings.CopyPointer(s.HeaderH2), + HeaderH3: gosettings.CopyPointer(s.HeaderH3), + HeaderH4: gosettings.CopyPointer(s.HeaderH4), + InitPacketI1: gosettings.CopyPointer(s.InitPacketI1), + InitPacketI2: gosettings.CopyPointer(s.InitPacketI2), + InitPacketI3: gosettings.CopyPointer(s.InitPacketI3), + InitPacketI4: gosettings.CopyPointer(s.InitPacketI4), + InitPacketI5: gosettings.CopyPointer(s.InitPacketI5), + } +} + +//nolint:dupl +func (s *AmneziaWg) overrideWith(other AmneziaWg) { + s.JunkPacketCount = gosettings.OverrideWithPointer(s.JunkPacketCount, other.JunkPacketCount) + s.JunkPacketMin = gosettings.OverrideWithPointer(s.JunkPacketMin, other.JunkPacketMin) + s.JunkPacketMax = gosettings.OverrideWithPointer(s.JunkPacketMax, other.JunkPacketMax) + s.PaddingS1 = gosettings.OverrideWithPointer(s.PaddingS1, other.PaddingS1) + s.PaddingS2 = gosettings.OverrideWithPointer(s.PaddingS2, other.PaddingS2) + s.PaddingS3 = gosettings.OverrideWithPointer(s.PaddingS3, other.PaddingS3) + s.PaddingS4 = gosettings.OverrideWithPointer(s.PaddingS4, other.PaddingS4) + s.HeaderH1 = gosettings.OverrideWithPointer(s.HeaderH1, other.HeaderH1) + s.HeaderH2 = gosettings.OverrideWithPointer(s.HeaderH2, other.HeaderH2) + s.HeaderH3 = gosettings.OverrideWithPointer(s.HeaderH3, other.HeaderH3) + s.HeaderH4 = gosettings.OverrideWithPointer(s.HeaderH4, other.HeaderH4) + s.InitPacketI1 = gosettings.OverrideWithPointer(s.InitPacketI1, other.InitPacketI1) + s.InitPacketI2 = gosettings.OverrideWithPointer(s.InitPacketI2, other.InitPacketI2) + s.InitPacketI3 = gosettings.OverrideWithPointer(s.InitPacketI3, other.InitPacketI3) + s.InitPacketI4 = gosettings.OverrideWithPointer(s.InitPacketI4, other.InitPacketI4) + s.InitPacketI5 = gosettings.OverrideWithPointer(s.InitPacketI5, other.InitPacketI5) +} + +func (s *AmneziaWg) setDefaults() { + s.JunkPacketCount = gosettings.DefaultPointer(s.JunkPacketCount, 0) + s.JunkPacketMin = gosettings.DefaultPointer(s.JunkPacketMin, 0) + s.JunkPacketMax = gosettings.DefaultPointer(s.JunkPacketMax, 0) + s.PaddingS1 = gosettings.DefaultPointer(s.PaddingS1, 0) + s.PaddingS2 = gosettings.DefaultPointer(s.PaddingS2, 0) + s.PaddingS3 = gosettings.DefaultPointer(s.PaddingS3, 0) + s.PaddingS4 = gosettings.DefaultPointer(s.PaddingS4, 0) + s.HeaderH1 = gosettings.DefaultPointer(s.HeaderH1, "") + s.HeaderH2 = gosettings.DefaultPointer(s.HeaderH2, "") + s.HeaderH3 = gosettings.DefaultPointer(s.HeaderH3, "") + s.HeaderH4 = gosettings.DefaultPointer(s.HeaderH4, "") + s.InitPacketI1 = gosettings.DefaultPointer(s.InitPacketI1, "") + s.InitPacketI2 = gosettings.DefaultPointer(s.InitPacketI2, "") + s.InitPacketI3 = gosettings.DefaultPointer(s.InitPacketI3, "") + s.InitPacketI4 = gosettings.DefaultPointer(s.InitPacketI4, "") + s.InitPacketI5 = gosettings.DefaultPointer(s.InitPacketI5, "") +} + +func (s AmneziaWg) toLinesNode() (node *gotree.Node) { + node = gotree.New("Amneziawg parameters:") + + uintFields := []struct { + key string + val *uint16 + }{ + {"jc", s.JunkPacketCount}, + {"jmin", s.JunkPacketMin}, + {"jmax", s.JunkPacketMax}, + {"s1", s.PaddingS1}, + {"s2", s.PaddingS2}, + {"s3", s.PaddingS3}, + {"s4", s.PaddingS4}, + } + for _, f := range uintFields { + node.Appendf("%s: %d", f.key, *f.val) + } + + stringFields := []struct { + key string + val *string + }{ + {"h1", s.HeaderH1}, + {"h2", s.HeaderH2}, + {"h3", s.HeaderH3}, + {"h4", s.HeaderH4}, + {"i1", s.InitPacketI1}, + {"i2", s.InitPacketI2}, + {"i3", s.InitPacketI3}, + {"i4", s.InitPacketI4}, + {"i5", s.InitPacketI5}, + } + for _, f := range stringFields { + node.Appendf("%s: %s", f.key, *f.val) + } + + return node +} + +var ( + ErrJunkPacketBounds = errors.New("junk packet minimum must be lower than or equal to maximum") + ErrJunkPacketMinMaxNotSet = errors.New("junk packet min and max must be set when junk packet count is set") + ErrJunkPacketCountNotSet = errors.New("junk packet count must be set when junk packet min or max is set") + ErrHeaderRangeMalformed = errors.New("header range is malformed") +) + +func (s AmneziaWg) validate() error { + if *s.JunkPacketCount == 0 { + if *s.JunkPacketMin != 0 || *s.JunkPacketMax != 0 { + return fmt.Errorf("%w: jc=%d and jmin=%d and jmax=%d", + ErrJunkPacketCountNotSet, s.JunkPacketCount, *s.JunkPacketMin, *s.JunkPacketMax) + } + } else { + if *s.JunkPacketMin == 0 || *s.JunkPacketMax == 0 { + return fmt.Errorf("%w: jc=%d and jmin=%d and jmax=%d", + ErrJunkPacketMinMaxNotSet, s.JunkPacketCount, *s.JunkPacketMin, *s.JunkPacketMax) + } else if *s.JunkPacketMin > *s.JunkPacketMax { + return fmt.Errorf("%w: jmin=%d and jmax=%d", + ErrJunkPacketBounds, *s.JunkPacketMin, *s.JunkPacketMax) + } + } + + nameToHeaderRange := map[string]string{ + "h1": *s.HeaderH1, + "h2": *s.HeaderH2, + "h3": *s.HeaderH3, + "h4": *s.HeaderH4, + } + for name, headerRange := range nameToHeaderRange { + if headerRange == "" { + continue + } + fields := strings.Split(headerRange, "-") + switch len(fields) { + case 1: + _, err := strconv.Atoi(fields[0]) + if err != nil { + return fmt.Errorf("%w: %s value %s is not a number", + ErrHeaderRangeMalformed, name, headerRange) + } + case 2: //nolint:mnd + for _, field := range fields { + _, err := strconv.Atoi(field) + if err != nil { + return fmt.Errorf("%w: %s value %s is not a valid range", + ErrHeaderRangeMalformed, name, headerRange) + } + } + default: + return fmt.Errorf("%w: %s value %s must be in the form n or n-m", + ErrHeaderRangeMalformed, name, headerRange) + } + } + + return nil +} diff --git a/internal/configuration/settings/openvpn.go b/internal/configuration/settings/openvpn.go index 7cffd4d3..b826bd49 100644 --- a/internal/configuration/settings/openvpn.go +++ b/internal/configuration/settings/openvpn.go @@ -268,6 +268,8 @@ func (o *OpenVPN) copy() (copied OpenVPN) { // overrideWith overrides fields of the receiver // settings object with any field set in the other // settings. +// +//nolint:dupl func (o *OpenVPN) overrideWith(other OpenVPN) { o.Version = gosettings.OverrideWithComparable(o.Version, other.Version) o.User = gosettings.OverrideWithPointer(o.User, other.User) diff --git a/internal/configuration/settings/wireguard.go b/internal/configuration/settings/wireguard.go index b096968b..e1940f73 100644 --- a/internal/configuration/settings/wireguard.go +++ b/internal/configuration/settings/wireguard.go @@ -42,10 +42,12 @@ type Wireguard struct { // 0 indicating to use PMTUD. MTU *uint32 `json:"mtu"` // Implementation is the Wireguard implementation to use. - // It can be "auto", "userspace" or "kernelspace". + // It can be "auto", "userspace", "kernelspace" or "amneziawg". // It defaults to "auto" and cannot be the empty string // in the internal state. Implementation string `json:"implementation"` + // AmneziaWG contains obfuscation parameters + AmneziaWG AmneziaWg `json:"amneziawg"` } var regexpInterfaceName = regexp.MustCompile(`^[a-zA-Z0-9_]+$`) @@ -136,11 +138,16 @@ func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error) ErrWireguardInterfaceNotValid, w.Interface, regexpInterfaceName) } - validImplementations := []string{"auto", "userspace", "kernelspace"} + validImplementations := []string{"auto", "userspace", "kernelspace", "amneziawg"} if err := validate.IsOneOf(w.Implementation, validImplementations...); err != nil { return fmt.Errorf("%w: %w", ErrWireguardImplementationNotValid, err) } + err = w.AmneziaWG.validate() + if err != nil { + return fmt.Errorf("amneziawg settings: %w", err) + } + return nil } @@ -154,6 +161,7 @@ func (w *Wireguard) copy() (copied Wireguard) { Interface: w.Interface, MTU: w.MTU, Implementation: w.Implementation, + AmneziaWG: w.AmneziaWG.copy(), } } @@ -167,6 +175,7 @@ func (w *Wireguard) overrideWith(other Wireguard) { w.Interface = gosettings.OverrideWithComparable(w.Interface, other.Interface) w.MTU = gosettings.OverrideWithComparable(w.MTU, other.MTU) w.Implementation = gosettings.OverrideWithComparable(w.Implementation, other.Implementation) + w.AmneziaWG.overrideWith(other.AmneziaWG) } func (w *Wireguard) setDefaults(vpnProvider string) { @@ -191,6 +200,7 @@ func (w *Wireguard) setDefaults(vpnProvider string) { w.Interface = gosettings.DefaultComparable(w.Interface, "wg0") w.MTU = gosettings.DefaultPointer(w.MTU, 0) w.Implementation = gosettings.DefaultComparable(w.Implementation, "auto") + w.AmneziaWG.setDefaults() } func (w Wireguard) String() string { @@ -232,7 +242,11 @@ func (w Wireguard) toLinesNode() (node *gotree.Node) { } if w.Implementation != "auto" { - node.Appendf("Implementation: %s", w.Implementation) + implNode := node.Appendf("Implementation: %s", w.Implementation) + + if w.Implementation == "amneziawg" { + implNode.AppendNode(w.AmneziaWG.toLinesNode()) + } } return node @@ -245,6 +259,11 @@ func (w *Wireguard) read(r *reader.Reader) (err error) { reader.RetroKeys("WIREGUARD_INTERFACE"), reader.ForceLowercase(false)) w.Implementation = r.String("WIREGUARD_IMPLEMENTATION") + err = w.AmneziaWG.read(r) + if err != nil { + return err + } + addressStrings := r.CSV("WIREGUARD_ADDRESSES", reader.RetroKeys("WIREGUARD_ADDRESS")) // WARNING: do not initialize w.Addresses to an empty slice // or the defaults for nordvpn will not work. diff --git a/internal/configuration/sources/files/reader.go b/internal/configuration/sources/files/reader.go index 477e39df..3ca658c7 100644 --- a/internal/configuration/sources/files/reader.go +++ b/internal/configuration/sources/files/reader.go @@ -69,6 +69,38 @@ func (s *Source) Get(key string) (value string, isSet bool) { return strPtrToStringIsSet(s.lazyLoadWireguardConf().EndpointIP) case "wireguard_endpoint_port": return strPtrToStringIsSet(s.lazyLoadWireguardConf().EndpointPort) + case "wireguard_jc": + return strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.Jc) + case "wireguard_jmin": + return strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.Jmin) + case "wireguard_jmax": + return strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.Jmax) + case "wireguard_s1": + return strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.S1) + case "wireguard_s2": + return strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.S2) + case "wireguard_s3": + return strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.S3) + case "wireguard_s4": + return strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.S4) + case "wireguard_h1": + return strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.H1) + case "wireguard_h2": + return strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.H2) + case "wireguard_h3": + return strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.H3) + case "wireguard_h4": + return strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.H4) + case "wireguard_i1": + return strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.I1) + case "wireguard_i2": + return strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.I2) + case "wireguard_i3": + return strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.I3) + case "wireguard_i4": + return strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.I4) + case "wireguard_i5": + return strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.I5) } value, isSet, err := ReadFromFile(path) diff --git a/internal/configuration/sources/files/wireguard.go b/internal/configuration/sources/files/wireguard.go index 0b86f662..d65310bb 100644 --- a/internal/configuration/sources/files/wireguard.go +++ b/internal/configuration/sources/files/wireguard.go @@ -25,13 +25,54 @@ func (s *Source) lazyLoadWireguardConf() WireguardConfig { return s.cached.wireguardConf } +type amneziaWgConfig struct { + Jc *string + Jmin *string + Jmax *string + S1 *string + S2 *string + S3 *string + S4 *string + H1 *string + H2 *string + H3 *string + H4 *string + I1 *string + I2 *string + I3 *string + I4 *string + I5 *string +} + +func parseWireguardAmneziaInterfaceSection(interfaceSection *ini.Section) amneziaWgConfig { + return amneziaWgConfig{ + Jc: getINIKeyFromSection(interfaceSection, "Jc"), + Jmin: getINIKeyFromSection(interfaceSection, "Jmin"), + Jmax: getINIKeyFromSection(interfaceSection, "Jmax"), + S1: getINIKeyFromSection(interfaceSection, "S1"), + S2: getINIKeyFromSection(interfaceSection, "S2"), + S3: getINIKeyFromSection(interfaceSection, "S3"), + S4: getINIKeyFromSection(interfaceSection, "S4"), + H1: getINIKeyFromSection(interfaceSection, "H1"), + H2: getINIKeyFromSection(interfaceSection, "H2"), + H3: getINIKeyFromSection(interfaceSection, "H3"), + H4: getINIKeyFromSection(interfaceSection, "H4"), + I1: getINIKeyFromSection(interfaceSection, "I1"), + I2: getINIKeyFromSection(interfaceSection, "I2"), + I3: getINIKeyFromSection(interfaceSection, "I3"), + I4: getINIKeyFromSection(interfaceSection, "I4"), + I5: getINIKeyFromSection(interfaceSection, "I5"), + } +} + type WireguardConfig struct { - PrivateKey *string - PreSharedKey *string - Addresses *string - PublicKey *string - EndpointIP *string - EndpointPort *string + PrivateKey *string + PreSharedKey *string + Addresses *string + PublicKey *string + EndpointIP *string + EndpointPort *string + AmneziaParams amneziaWgConfig } var regexINISectionNotExist = regexp.MustCompile(`^section ".+" does not exist$`) @@ -48,6 +89,7 @@ func ParseWireguardConf(path string) (config WireguardConfig, err error) { interfaceSection, err := iniFile.GetSection("Interface") if err == nil { config.PrivateKey, config.Addresses = parseWireguardInterfaceSection(interfaceSection) + config.AmneziaParams = parseWireguardAmneziaInterfaceSection(interfaceSection) } else if !regexINISectionNotExist.MatchString(err.Error()) { // can never happen return WireguardConfig{}, fmt.Errorf("getting interface section: %w", err) diff --git a/internal/configuration/sources/files/wireguard_test.go b/internal/configuration/sources/files/wireguard_test.go index 53d9c838..2fb78de1 100644 --- a/internal/configuration/sources/files/wireguard_test.go +++ b/internal/configuration/sources/files/wireguard_test.go @@ -97,9 +97,10 @@ func Test_parseWireguardInterfaceSection(t *testing.T) { t.Parallel() testCases := map[string]struct { - iniData string - privateKey *string - addresses *string + iniData string + privateKey *string + addresses *string + amneziaParams amneziaWgConfig }{ "no_fields": { iniData: `[Interface]`, @@ -115,9 +116,17 @@ PrivateKey = x [Interface] PrivateKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8= Address = 10.38.22.35/32 +Jc = 4 +H1 = 721391205 +I1 = `, privateKey: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="), addresses: ptrTo("10.38.22.35/32"), + amneziaParams: amneziaWgConfig{ + Jc: ptrTo("4"), + H1: ptrTo("721391205"), + I1: ptrTo(""), + }, }, } @@ -131,9 +140,11 @@ Address = 10.38.22.35/32 require.NoError(t, err) privateKey, addresses := parseWireguardInterfaceSection(iniSection) + amneziaWgConfig := parseWireguardAmneziaInterfaceSection(iniSection) assert.Equal(t, testCase.privateKey, privateKey) assert.Equal(t, testCase.addresses, addresses) + assert.Equal(t, testCase.amneziaParams, amneziaWgConfig) }) } } diff --git a/internal/configuration/sources/secrets/reader.go b/internal/configuration/sources/secrets/reader.go index 7de0590f..d03126d6 100644 --- a/internal/configuration/sources/secrets/reader.go +++ b/internal/configuration/sources/secrets/reader.go @@ -83,6 +83,11 @@ func (s *Source) Get(key string) (value string, isSet bool) { return strPtrToStringIsSet(s.lazyLoadWireguardConf().EndpointPort) } + value, isSet, matched := s.getAmneziaWg(key) + if matched { + return value, isSet + } + value, isSet, err := files.ReadFromFile(path) if err != nil { s.warner.Warnf("skipping %s: reading file: %s", path, err) @@ -104,3 +109,43 @@ func (s *Source) KeyTransform(key string) string { return key } } + +func (s *Source) getAmneziaWg(key string) (value string, isSet, matched bool) { + switch key { + case "wireguard_jc": + value, isSet = strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.Jc) + case "wireguard_jmin": + value, isSet = strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.Jmin) + case "wireguard_jmax": + value, isSet = strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.Jmax) + case "wireguard_s1": + value, isSet = strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.S1) + case "wireguard_s2": + value, isSet = strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.S2) + case "wireguard_s3": + value, isSet = strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.S3) + case "wireguard_s4": + value, isSet = strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.S4) + case "wireguard_h1": + value, isSet = strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.H1) + case "wireguard_h2": + value, isSet = strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.H2) + case "wireguard_h3": + value, isSet = strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.H3) + case "wireguard_h4": + value, isSet = strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.H4) + case "wireguard_i1": + value, isSet = strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.I1) + case "wireguard_i2": + value, isSet = strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.I2) + case "wireguard_i3": + value, isSet = strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.I3) + case "wireguard_i4": + value, isSet = strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.I4) + case "wireguard_i5": + value, isSet = strPtrToStringIsSet(s.lazyLoadWireguardConf().AmneziaParams.I5) + default: + return "", false, false + } + return value, isSet, true +} diff --git a/internal/provider/utils/wireguard.go b/internal/provider/utils/wireguard.go index 05c2df8a..770cf4e2 100644 --- a/internal/provider/utils/wireguard.go +++ b/internal/provider/utils/wireguard.go @@ -16,6 +16,7 @@ func BuildWireguardSettings(connection models.Connection, settings.PreSharedKey = *userSettings.PreSharedKey settings.InterfaceName = userSettings.Interface settings.Implementation = userSettings.Implementation + settings.AmneziaWG = buildAmneziaWgSettings(userSettings.AmneziaWG) if *userSettings.MTU > 0 { settings.MTU = *userSettings.MTU } else { @@ -55,3 +56,24 @@ func BuildWireguardSettings(connection models.Connection, return settings } + +func buildAmneziaWgSettings(s settings.AmneziaWg) wireguard.AmneziaSettings { + return wireguard.AmneziaSettings{ + JunkPacketCount: *s.JunkPacketCount, + JunkPacketMin: *s.JunkPacketMin, + JunkPacketMax: *s.JunkPacketMax, + PaddingS1: *s.PaddingS1, + PaddingS2: *s.PaddingS2, + PaddingS3: *s.PaddingS3, + PaddingS4: *s.PaddingS4, + HeaderH1: *s.HeaderH1, + HeaderH2: *s.HeaderH2, + HeaderH3: *s.HeaderH3, + HeaderH4: *s.HeaderH4, + InitPacketI1: *s.InitPacketI1, + InitPacketI2: *s.InitPacketI2, + InitPacketI3: *s.InitPacketI3, + InitPacketI4: *s.InitPacketI4, + InitPacketI5: *s.InitPacketI5, + } +} diff --git a/internal/provider/utils/wireguard_test.go b/internal/provider/utils/wireguard_test.go index 42d973b3..1e16dabb 100644 --- a/internal/provider/utils/wireguard_test.go +++ b/internal/provider/utils/wireguard_test.go @@ -42,6 +42,24 @@ func Test_BuildWireguardSettings(t *testing.T) { PersistentKeepaliveInterval: ptrTo(time.Hour), Interface: "wg1", MTU: ptrTo(uint32(1000)), + AmneziaWG: settings.AmneziaWg{ + JunkPacketCount: ptrTo(uint16(1)), + JunkPacketMin: ptrTo(uint16(0)), + JunkPacketMax: ptrTo(uint16(0)), + PaddingS1: ptrTo(uint16(0)), + PaddingS2: ptrTo(uint16(0)), + PaddingS3: ptrTo(uint16(0)), + PaddingS4: ptrTo(uint16(0)), + HeaderH1: ptrTo("x"), + HeaderH2: ptrTo(""), + HeaderH3: ptrTo(""), + HeaderH4: ptrTo(""), + InitPacketI1: ptrTo(""), + InitPacketI2: ptrTo(""), + InitPacketI3: ptrTo(""), + InitPacketI4: ptrTo(""), + InitPacketI5: ptrTo(""), + }, }, ipv6Supported: false, settings: wireguard.Settings{ @@ -60,6 +78,10 @@ func Test_BuildWireguardSettings(t *testing.T) { RulePriority: 101, IPv6: boolPtr(false), MTU: 1000, + AmneziaWG: wireguard.AmneziaSettings{ + JunkPacketCount: 1, + HeaderH1: "x", + }, }, }, } diff --git a/internal/wireguard/amnezia_settings.go b/internal/wireguard/amnezia_settings.go new file mode 100644 index 00000000..8b7d670a --- /dev/null +++ b/internal/wireguard/amnezia_settings.go @@ -0,0 +1,58 @@ +package wireguard + +import ( + "fmt" + "strings" +) + +type AmneziaSettings struct { + JunkPacketCount uint16 + JunkPacketMin uint16 + JunkPacketMax uint16 + PaddingS1 uint16 + PaddingS2 uint16 + PaddingS3 uint16 + PaddingS4 uint16 + HeaderH1 string + HeaderH2 string + HeaderH3 string + HeaderH4 string + InitPacketI1 string + InitPacketI2 string + InitPacketI3 string + InitPacketI4 string + InitPacketI5 string +} + +func (s AmneziaSettings) uapiConfig() string { + uintFields := map[string]uint16{ + "jc": s.JunkPacketCount, + "jmin": s.JunkPacketMin, + "jmax": s.JunkPacketMax, + "s1": s.PaddingS1, + "s2": s.PaddingS2, + "s3": s.PaddingS3, + "s4": s.PaddingS4, + } + stringFields := map[string]string{ + "h1": s.HeaderH1, + "h2": s.HeaderH2, + "h3": s.HeaderH3, + "h4": s.HeaderH4, + "i1": s.InitPacketI1, + "i2": s.InitPacketI2, + "i3": s.InitPacketI3, + "i4": s.InitPacketI4, + "i5": s.InitPacketI5, + } + lines := make([]string, 0, len(uintFields)+len(stringFields)) + + for key, val := range uintFields { + lines = append(lines, fmt.Sprintf("%s=%d", key, val)) + } + + for key, val := range stringFields { + lines = append(lines, key+"="+val) + } + return strings.Join(lines, "\n") +} diff --git a/internal/wireguard/common.go b/internal/wireguard/common.go new file mode 100644 index 00000000..a4d9fb56 --- /dev/null +++ b/internal/wireguard/common.go @@ -0,0 +1,28 @@ +package wireguard + +import ( + "net" +) + +type tunDevice interface { + Close() error + Name() (string, error) +} + +type bind interface { + Close() error +} + +type userspaceDevice interface { + Close() + Wait() chan struct{} + IpcHandle(net.Conn) + IpcSet(string) error +} + +type userSpaceBackend struct { + createTun func(string, int) (tunDevice, error) + createBind func() bind + createDevice func(tunDevice, bind, Logger) userspaceDevice + preStart func(userspaceDevice, Settings) error +} diff --git a/internal/wireguard/log.go b/internal/wireguard/log.go index 69fd7603..c5120dd0 100644 --- a/internal/wireguard/log.go +++ b/internal/wireguard/log.go @@ -1,9 +1,5 @@ package wireguard -import ( - "golang.zx2c4.com/wireguard/device" -) - //go:generate mockgen -destination=log_mock_test.go -package wireguard . Logger type Logger interface { @@ -13,10 +9,3 @@ type Logger interface { Error(s string) Errorf(format string, args ...interface{}) } - -func makeDeviceLogger(logger Logger) (deviceLogger *device.Logger) { - return &device.Logger{ - Verbosef: logger.Debugf, - Errorf: logger.Errorf, - } -} diff --git a/internal/wireguard/log_test.go b/internal/wireguard/log_test.go deleted file mode 100644 index 1491dd34..00000000 --- a/internal/wireguard/log_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package wireguard - -import ( - "testing" - - "github.com/golang/mock/gomock" -) - -func Test_makeDeviceLogger(t *testing.T) { - t.Parallel() - - ctrl := gomock.NewController(t) - - logger := NewMockLogger(ctrl) - - deviceLogger := makeDeviceLogger(logger) - - logger.EXPECT().Debugf("test %d", 1) - deviceLogger.Verbosef("test %d", 1) - - logger.EXPECT().Errorf("test %d", 2) - deviceLogger.Errorf("test %d", 2) -} diff --git a/internal/wireguard/run.go b/internal/wireguard/run.go index d4f2d1f1..b6f06aad 100644 --- a/internal/wireguard/run.go +++ b/internal/wireguard/run.go @@ -7,9 +7,6 @@ import ( "net" "github.com/qdm12/gluetun/internal/netlink" - "golang.zx2c4.com/wireguard/conn" - "golang.zx2c4.com/wireguard/device" - "golang.zx2c4.com/wireguard/tun" "golang.zx2c4.com/wireguard/wgctrl" ) @@ -29,6 +26,7 @@ var ( ErrRouteAdd = errors.New("cannot add route for interface") ErrDeviceWaited = errors.New("device waited for") ErrKernelSupport = errors.New("kernel does not support Wireguard") + ErrAmneziaConfigure = errors.New("cannot configure AmneziaWG") ) // See https://git.zx2c4.com/wireguard-go/tree/main.go @@ -39,7 +37,8 @@ func (w *Wireguard) Run(ctx context.Context, waitError chan<- error, ready chan< return } - setupFunction := setupUserSpace + userspaceBackend := defaultUserSpaceBackend() + setupFunction := setupUserSpaceCommon switch w.settings.Implementation { case "auto": //nolint:goconst if !kernelSupported { @@ -55,6 +54,8 @@ func (w *Wireguard) Run(ctx context.Context, waitError chan<- error, ready chan< return } setupFunction = setupKernelSpace + case "amneziawg": + userspaceBackend = amneziaUserSpaceBackend() default: panic(fmt.Sprintf("unknown implementation %q", w.settings.Implementation)) } @@ -71,7 +72,7 @@ func (w *Wireguard) Run(ctx context.Context, waitError chan<- error, ready chan< defer closers.cleanup(w.logger) linkIndex, waitAndCleanup, err := setupFunction(ctx, - w.settings.InterfaceName, w.netlink, w.settings.MTU, &closers, w.logger) + w.settings.InterfaceName, w.netlink, w.settings.MTU, &closers, w.logger, w.settings, userspaceBackend) if err != nil { waitError <- err return @@ -136,7 +137,7 @@ type waitAndCleanupFunc func() error func setupKernelSpace(ctx context.Context, interfaceName string, netLinker NetLinker, mtu uint32, - closers *closers, logger Logger) ( + closers *closers, logger Logger, _ Settings, _ userSpaceBackend) ( linkIndex uint32, waitAndCleanup waitAndCleanupFunc, err error, ) { links, err := netLinker.LinkList() @@ -178,12 +179,14 @@ func setupKernelSpace(ctx context.Context, return linkIndex, waitAndCleanup, nil } -func setupUserSpace(ctx context.Context, +func setupUserSpaceCommon(ctx context.Context, interfaceName string, netLinker NetLinker, mtu uint32, - closers *closers, logger Logger) ( + closers *closers, logger Logger, + settings Settings, b userSpaceBackend, +) ( linkIndex uint32, waitAndCleanup waitAndCleanupFunc, err error, ) { - tun, err := tun.CreateTUN(interfaceName, int(mtu)) + tun, err := b.createTun(interfaceName, int(mtu)) if err != nil { return 0, nil, fmt.Errorf("%w: %s", ErrCreateTun, err) } @@ -206,12 +209,11 @@ func setupUserSpace(ctx context.Context, return netLinker.LinkDel(link.Index) }) - bind := conn.NewDefaultBind() + bind := b.createBind() closers.add("closing bind", stepSeven, bind.Close) - deviceLogger := makeDeviceLogger(logger) - device := device.NewDevice(tun, bind, deviceLogger) + device := b.createDevice(tun, bind, logger) closers.add("closing Wireguard device", stepSix, func() error { device.Close() @@ -232,6 +234,13 @@ func setupUserSpace(ctx context.Context, closers.add("closing UAPI listener", stepTwo, uapiListener.Close) + if b.preStart != nil { + err = b.preStart(device, settings) + if err != nil { + return 0, nil, err + } + } + // acceptAndHandle exits when uapiListener is closed uapiAcceptErrorCh := make(chan error) go acceptAndHandle(uapiListener, device, uapiAcceptErrorCh) @@ -255,7 +264,7 @@ func setupUserSpace(ctx context.Context, return link.Index, waitAndCleanup, nil } -func acceptAndHandle(uapi net.Listener, device *device.Device, +func acceptAndHandle(uapi net.Listener, device userspaceDevice, uapiAcceptErrorCh chan<- error, ) { for { // stopped by uapiFile.Close() diff --git a/internal/wireguard/settings.go b/internal/wireguard/settings.go index 66eee765..c9c10216 100644 --- a/internal/wireguard/settings.go +++ b/internal/wireguard/settings.go @@ -46,8 +46,11 @@ type Settings struct { // It defaults to false if left unset. IPv6 *bool // Implementation is the implementation to use. - // It can be auto, kernelspace or userspace, and defaults to auto. + // It can be auto, kernelspace, userspace or amneziawg, + // and defaults to auto. Implementation string + // AmneziaWG settings are extra obfuscation parameters + AmneziaWG AmneziaSettings } func (s *Settings) SetDefaults() { @@ -178,7 +181,7 @@ func (s *Settings) Check() (err error) { } switch s.Implementation { - case "auto", "kernelspace", "userspace": + case "auto", "kernelspace", "userspace", "amneziawg": default: return fmt.Errorf("%w: %s", ErrImplementationInvalid, s.Implementation) } diff --git a/internal/wireguard/userspaces.go b/internal/wireguard/userspaces.go new file mode 100644 index 00000000..f5220f20 --- /dev/null +++ b/internal/wireguard/userspaces.go @@ -0,0 +1,58 @@ +package wireguard + +import ( + amneziaconn "github.com/amnezia-vpn/amneziawg-go/conn" + amneziadevice "github.com/amnezia-vpn/amneziawg-go/device" + amneziatun "github.com/amnezia-vpn/amneziawg-go/tun" + wgconn "golang.zx2c4.com/wireguard/conn" + wgdevice "golang.zx2c4.com/wireguard/device" + wgtun "golang.zx2c4.com/wireguard/tun" +) + +func defaultUserSpaceBackend() userSpaceBackend { + return userSpaceBackend{ + createTun: func(name string, mtu int) (tunDevice, error) { + return wgtun.CreateTUN(name, mtu) + }, + createBind: func() bind { + return wgconn.NewDefaultBind() + }, + createDevice: func(td tunDevice, b bind, logger Logger) userspaceDevice { + wgtun, _ := td.(wgtun.Device) + wgBind, _ := b.(wgconn.Bind) + wgLogger := wgdevice.Logger{ + Verbosef: logger.Debugf, + Errorf: logger.Errorf, + } + device := wgdevice.NewDevice(wgtun, wgBind, &wgLogger) + return device + }, + preStart: nil, + } +} + +func amneziaUserSpaceBackend() userSpaceBackend { + return userSpaceBackend{ + createTun: func(name string, mtu int) (tunDevice, error) { + return amneziatun.CreateTUN(name, mtu) + }, + createBind: func() bind { + return amneziaconn.NewDefaultBind() + }, + createDevice: func(td tunDevice, b bind, logger Logger) userspaceDevice { + wgamneziaTun, _ := td.(amneziatun.Device) + wgamneziaBind, _ := b.(amneziaconn.Bind) + wgamneziaLogger := amneziadevice.Logger{ + Verbosef: logger.Debugf, + Errorf: logger.Errorf, + } + device := amneziadevice.NewDevice(wgamneziaTun, wgamneziaBind, &wgamneziaLogger) + return device + }, + preStart: func(ud userspaceDevice, s Settings) error { + uapiConfig := s.AmneziaWG.uapiConfig() + err := ud.IpcSet(uapiConfig) + return err + }, + } +}