feat(settings): parse Wireguard settings from /gluetun/wireguard/wg0.conf (#1120)
This commit is contained in:
1
go.mod
1
go.mod
@@ -24,6 +24,7 @@ require (
|
||||
golang.org/x/text v0.11.0
|
||||
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
inet.af/netaddr v0.0.0-20220811202034-502d2d690317
|
||||
)
|
||||
|
||||
|
||||
4
go.sum
4
go.sum
@@ -91,8 +91,6 @@ github.com/qdm12/golibs v0.0.0-20210603202746-e5494e9c2ebb/go.mod h1:15RBzkun0i8
|
||||
github.com/qdm12/golibs v0.0.0-20210723175634-a75ca7fd74c2/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg=
|
||||
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6 h1:bge5AL7cjHJMPz+5IOz5yF01q/l8No6+lIEBieA8gMg=
|
||||
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg=
|
||||
github.com/qdm12/gosettings v0.3.0-rc13 h1:fag+/hFPBUcNk3a5ifUbwNS2VgXFpxindkl8mQNk76U=
|
||||
github.com/qdm12/gosettings v0.3.0-rc13/go.mod h1:JRV3opOpHvnKlIA29lKQMdYw1WSMVMfHYLLHPHol5ME=
|
||||
github.com/qdm12/gosettings v0.4.0-rc1 h1:UYA92yyeDPbmZysIuG65yrpZVPtdIoRmtEHft/AyI38=
|
||||
github.com/qdm12/gosettings v0.4.0-rc1/go.mod h1:JRV3opOpHvnKlIA29lKQMdYw1WSMVMfHYLLHPHol5ME=
|
||||
github.com/qdm12/goshutdown v0.3.0 h1:pqBpJkdwlZlfTEx4QHtS8u8CXx6pG0fVo6S1N0MpSEM=
|
||||
@@ -228,6 +226,8 @@ golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde/go.mod h1:m
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
|
||||
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
|
||||
gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
|
||||
|
||||
3
internal/configuration/sources/files/helpers_test.go
Normal file
3
internal/configuration/sources/files/helpers_test.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package files
|
||||
|
||||
func ptrTo[T any](x T) *T { return &x }
|
||||
16
internal/configuration/sources/files/provider.go
Normal file
16
internal/configuration/sources/files/provider.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (s *Source) readProvider() (provider settings.Provider, err error) {
|
||||
provider.ServerSelection, err = s.readServerSelection()
|
||||
if err != nil {
|
||||
return provider, fmt.Errorf("server selection: %w", err)
|
||||
}
|
||||
|
||||
return provider, nil
|
||||
}
|
||||
@@ -4,10 +4,15 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
type Source struct{}
|
||||
type Source struct {
|
||||
wireguardConfigPath string
|
||||
}
|
||||
|
||||
func New() *Source {
|
||||
return &Source{}
|
||||
const wireguardConfigPath = "/gluetun/wireguard/wg0.conf"
|
||||
return &Source{
|
||||
wireguardConfigPath: wireguardConfigPath,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Source) String() string { return "files" }
|
||||
|
||||
16
internal/configuration/sources/files/serverselection.go
Normal file
16
internal/configuration/sources/files/serverselection.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (s *Source) readServerSelection() (selection settings.ServerSelection, err error) {
|
||||
selection.Wireguard, err = s.readWireguardSelection()
|
||||
if err != nil {
|
||||
return selection, fmt.Errorf("wireguard: %w", err)
|
||||
}
|
||||
|
||||
return selection, nil
|
||||
}
|
||||
@@ -7,10 +7,20 @@ import (
|
||||
)
|
||||
|
||||
func (s *Source) readVPN() (vpn settings.VPN, err error) {
|
||||
vpn.Provider, err = s.readProvider()
|
||||
if err != nil {
|
||||
return vpn, fmt.Errorf("provider: %w", err)
|
||||
}
|
||||
|
||||
vpn.OpenVPN, err = s.readOpenVPN()
|
||||
if err != nil {
|
||||
return vpn, fmt.Errorf("OpenVPN: %w", err)
|
||||
}
|
||||
|
||||
vpn.Wireguard, err = s.readWireguard()
|
||||
if err != nil {
|
||||
return vpn, fmt.Errorf("wireguard: %w", err)
|
||||
}
|
||||
|
||||
return vpn, nil
|
||||
}
|
||||
|
||||
111
internal/configuration/sources/files/wireguard.go
Normal file
111
internal/configuration/sources/files/wireguard.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
regexINISectionNotExist = regexp.MustCompile(`^section ".+" does not exist$`)
|
||||
regexINIKeyNotExist = regexp.MustCompile(`key ".*" not exists$`)
|
||||
)
|
||||
|
||||
func (s *Source) readWireguard() (wireguard settings.Wireguard, err error) {
|
||||
fileStringPtr, err := ReadFromFile(s.wireguardConfigPath)
|
||||
if err != nil {
|
||||
return wireguard, fmt.Errorf("reading file: %w", err)
|
||||
}
|
||||
|
||||
if fileStringPtr == nil {
|
||||
return wireguard, nil
|
||||
}
|
||||
|
||||
rawData := []byte(*fileStringPtr)
|
||||
iniFile, err := ini.Load(rawData)
|
||||
if err != nil {
|
||||
return wireguard, fmt.Errorf("loading ini from reader: %w", err)
|
||||
}
|
||||
|
||||
interfaceSection, err := iniFile.GetSection("Interface")
|
||||
if err == nil {
|
||||
err = parseWireguardInterfaceSection(interfaceSection, &wireguard)
|
||||
if err != nil {
|
||||
return wireguard, fmt.Errorf("parsing interface section: %w", err)
|
||||
}
|
||||
} else if !regexINISectionNotExist.MatchString(err.Error()) {
|
||||
// can never happen
|
||||
return wireguard, fmt.Errorf("getting interface section: %w", err)
|
||||
}
|
||||
|
||||
return wireguard, nil
|
||||
}
|
||||
|
||||
func parseWireguardInterfaceSection(interfaceSection *ini.Section,
|
||||
wireguard *settings.Wireguard) (err error) {
|
||||
wireguard.PrivateKey, err = parseINIWireguardKey(interfaceSection, "PrivateKey")
|
||||
if err != nil {
|
||||
return err // error is already wrapped correctly
|
||||
}
|
||||
|
||||
wireguard.PreSharedKey, err = parseINIWireguardKey(interfaceSection, "PreSharedKey")
|
||||
if err != nil {
|
||||
return err // error is already wrapped correctly
|
||||
}
|
||||
|
||||
wireguard.Addresses, err = parseINIWireguardAddress(interfaceSection)
|
||||
if err != nil {
|
||||
return err // error is already wrapped correctly
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseINIWireguardKey(section *ini.Section, keyName string) (
|
||||
key *string, err error) {
|
||||
iniKey, err := section.GetKey(keyName)
|
||||
if err != nil {
|
||||
if regexINIKeyNotExist.MatchString(err.Error()) {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
// can never happen
|
||||
return nil, fmt.Errorf("getting %s key: %w", keyName, err)
|
||||
}
|
||||
|
||||
key = new(string)
|
||||
*key = iniKey.String()
|
||||
_, err = wgtypes.ParseKey(*key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing %s: %s: %w", keyName, *key, err)
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func parseINIWireguardAddress(section *ini.Section) (
|
||||
addresses []netip.Prefix, err error) {
|
||||
addressKey, err := section.GetKey("Address")
|
||||
if err != nil {
|
||||
if regexINIKeyNotExist.MatchString(err.Error()) {
|
||||
return nil, nil
|
||||
}
|
||||
// can never happen
|
||||
return nil, fmt.Errorf("getting Address key: %w", err)
|
||||
}
|
||||
|
||||
addressStrings := strings.Split(addressKey.String(), ",")
|
||||
addresses = make([]netip.Prefix, len(addressStrings))
|
||||
for i, addressString := range addressStrings {
|
||||
addressString = strings.TrimSpace(addressString)
|
||||
addresses[i], err = netip.ParsePrefix(addressString)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing address: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return addresses, nil
|
||||
}
|
||||
273
internal/configuration/sources/files/wireguard_test.go
Normal file
273
internal/configuration/sources/files/wireguard_test.go
Normal file
@@ -0,0 +1,273 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
func Test_Source_readWireguard(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("fail reading from file", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dirPath := t.TempDir()
|
||||
source := &Source{
|
||||
wireguardConfigPath: dirPath,
|
||||
}
|
||||
wireguard, err := source.readWireguard()
|
||||
assert.Equal(t, settings.Wireguard{}, wireguard)
|
||||
assert.Error(t, err)
|
||||
assert.Regexp(t, `reading file: read .+: is a directory`, err.Error())
|
||||
})
|
||||
|
||||
t.Run("no file", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
noFile := filepath.Join(t.TempDir(), "doesnotexist")
|
||||
source := &Source{
|
||||
wireguardConfigPath: noFile,
|
||||
}
|
||||
wireguard, err := source.readWireguard()
|
||||
assert.Equal(t, settings.Wireguard{}, wireguard)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
testCases := map[string]struct {
|
||||
fileContent string
|
||||
wireguard settings.Wireguard
|
||||
errMessage string
|
||||
}{
|
||||
"ini load error": {
|
||||
fileContent: "invalid",
|
||||
errMessage: "loading ini from reader: key-value delimiter not found: invalid",
|
||||
},
|
||||
"empty file": {},
|
||||
"interface section parsing error": {
|
||||
fileContent: `
|
||||
[Interface]
|
||||
PrivateKey = x
|
||||
`,
|
||||
errMessage: "parsing interface section: parsing PrivateKey: " +
|
||||
"x: wgtypes: failed to parse base64-encoded key: " +
|
||||
"illegal base64 data at input byte 0",
|
||||
},
|
||||
"success": {
|
||||
fileContent: `
|
||||
[Interface]
|
||||
PrivateKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=
|
||||
PreSharedKey = YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g=
|
||||
Address = 10.38.22.35/32
|
||||
DNS = 193.138.218.74
|
||||
|
||||
[Peer]
|
||||
`,
|
||||
wireguard: settings.Wireguard{
|
||||
PrivateKey: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="),
|
||||
PreSharedKey: ptrTo("YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g="),
|
||||
Addresses: []netip.Prefix{
|
||||
netip.PrefixFrom(netip.AddrFrom4([4]byte{10, 38, 22, 35}), 32),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for testName, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
configFile := filepath.Join(t.TempDir(), "wg.conf")
|
||||
err := os.WriteFile(configFile, []byte(testCase.fileContent), 0600)
|
||||
require.NoError(t, err)
|
||||
|
||||
source := &Source{
|
||||
wireguardConfigPath: configFile,
|
||||
}
|
||||
|
||||
wireguard, err := source.readWireguard()
|
||||
|
||||
assert.Equal(t, testCase.wireguard, wireguard)
|
||||
if testCase.errMessage != "" {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseWireguardInterfaceSection(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
iniData string
|
||||
wireguard settings.Wireguard
|
||||
errMessage string
|
||||
}{
|
||||
"private key error": {
|
||||
iniData: `[Interface]
|
||||
PrivateKey = x`,
|
||||
errMessage: "parsing PrivateKey: x: " +
|
||||
"wgtypes: failed to parse base64-encoded key: " +
|
||||
"illegal base64 data at input byte 0",
|
||||
},
|
||||
"pre shared key error": {
|
||||
iniData: `[Interface]
|
||||
PreSharedKey = x
|
||||
`,
|
||||
errMessage: "parsing PreSharedKey: x: " +
|
||||
"wgtypes: failed to parse base64-encoded key: " +
|
||||
"illegal base64 data at input byte 0",
|
||||
},
|
||||
"address error": {
|
||||
iniData: `[Interface]
|
||||
Address = x
|
||||
`,
|
||||
errMessage: "parsing address: netip.ParsePrefix(\"x\"): no '/'",
|
||||
},
|
||||
"success": {
|
||||
iniData: `
|
||||
[Interface]
|
||||
PrivateKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=
|
||||
PreSharedKey = YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g=
|
||||
Address = 10.38.22.35/32
|
||||
`,
|
||||
wireguard: settings.Wireguard{
|
||||
PrivateKey: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="),
|
||||
PreSharedKey: ptrTo("YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g="),
|
||||
Addresses: []netip.Prefix{
|
||||
netip.PrefixFrom(netip.AddrFrom4([4]byte{10, 38, 22, 35}), 32),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for testName, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
iniFile, err := ini.Load([]byte(testCase.iniData))
|
||||
require.NoError(t, err)
|
||||
iniSection, err := iniFile.GetSection("Interface")
|
||||
require.NoError(t, err)
|
||||
|
||||
var wireguard settings.Wireguard
|
||||
err = parseWireguardInterfaceSection(iniSection, &wireguard)
|
||||
|
||||
assert.Equal(t, testCase.wireguard, wireguard)
|
||||
if testCase.errMessage != "" {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseINIWireguardKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
fileContent string
|
||||
keyName string
|
||||
key *string
|
||||
errMessage string
|
||||
}{
|
||||
"key does not exist": {
|
||||
fileContent: `[Interface]`,
|
||||
keyName: "PrivateKey",
|
||||
},
|
||||
"bad Wireguard key": {
|
||||
fileContent: `[Interface]
|
||||
PrivateKey = x`,
|
||||
keyName: "PrivateKey",
|
||||
errMessage: "parsing PrivateKey: x: " +
|
||||
"wgtypes: failed to parse base64-encoded key: " +
|
||||
"illegal base64 data at input byte 0",
|
||||
},
|
||||
"success": {
|
||||
fileContent: `[Interface]
|
||||
PrivateKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=`,
|
||||
keyName: "PrivateKey",
|
||||
key: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="),
|
||||
},
|
||||
}
|
||||
|
||||
for testName, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
iniFile, err := ini.Load([]byte(testCase.fileContent))
|
||||
require.NoError(t, err)
|
||||
iniSection, err := iniFile.GetSection("Interface")
|
||||
require.NoError(t, err)
|
||||
|
||||
key, err := parseINIWireguardKey(iniSection, testCase.keyName)
|
||||
|
||||
assert.Equal(t, testCase.key, key)
|
||||
if testCase.errMessage != "" {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseINIWireguardAddress(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
fileContent string
|
||||
addresses []netip.Prefix
|
||||
errMessage string
|
||||
}{
|
||||
"key does not exist": {
|
||||
fileContent: `[Interface]`,
|
||||
},
|
||||
"bad address": {
|
||||
fileContent: `[Interface]
|
||||
Address = x`,
|
||||
errMessage: "parsing address: netip.ParsePrefix(\"x\"): no '/'",
|
||||
},
|
||||
"success": {
|
||||
fileContent: `[Interface]
|
||||
Address = 1.2.3.4/32, 5.6.7.8/32`,
|
||||
addresses: []netip.Prefix{
|
||||
netip.PrefixFrom(netip.AddrFrom4([4]byte{1, 2, 3, 4}), 32),
|
||||
netip.PrefixFrom(netip.AddrFrom4([4]byte{5, 6, 7, 8}), 32),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for testName, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
iniFile, err := ini.Load([]byte(testCase.fileContent))
|
||||
require.NoError(t, err)
|
||||
iniSection, err := iniFile.GetSection("Interface")
|
||||
require.NoError(t, err)
|
||||
|
||||
addresses, err := parseINIWireguardAddress(iniSection)
|
||||
|
||||
assert.Equal(t, testCase.addresses, addresses)
|
||||
if testCase.errMessage != "" {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
83
internal/configuration/sources/files/wireguardselection.go
Normal file
83
internal/configuration/sources/files/wireguardselection.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/govalid/port"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrEndpointHostNotIP = errors.New("endpoint host is not an IP")
|
||||
)
|
||||
|
||||
func (s *Source) readWireguardSelection() (selection settings.WireguardSelection, err error) {
|
||||
fileStringPtr, err := ReadFromFile(s.wireguardConfigPath)
|
||||
if err != nil {
|
||||
return selection, fmt.Errorf("reading file: %w", err)
|
||||
}
|
||||
|
||||
if fileStringPtr == nil {
|
||||
return selection, nil
|
||||
}
|
||||
|
||||
rawData := []byte(*fileStringPtr)
|
||||
iniFile, err := ini.Load(rawData)
|
||||
if err != nil {
|
||||
return selection, fmt.Errorf("loading ini from reader: %w", err)
|
||||
}
|
||||
|
||||
peerSection, err := iniFile.GetSection("Peer")
|
||||
if err == nil {
|
||||
err = parseWireguardPeerSection(peerSection, &selection)
|
||||
if err != nil {
|
||||
return selection, fmt.Errorf("parsing peer section: %w", err)
|
||||
}
|
||||
} else if !regexINISectionNotExist.MatchString(err.Error()) {
|
||||
// can never happen
|
||||
return selection, fmt.Errorf("getting peer section: %w", err)
|
||||
}
|
||||
|
||||
return selection, nil
|
||||
}
|
||||
|
||||
func parseWireguardPeerSection(peerSection *ini.Section,
|
||||
selection *settings.WireguardSelection) (err error) {
|
||||
publicKeyPtr, err := parseINIWireguardKey(peerSection, "PublicKey")
|
||||
if err != nil {
|
||||
return err // error is already wrapped correctly
|
||||
} else if publicKeyPtr != nil {
|
||||
selection.PublicKey = *publicKeyPtr
|
||||
}
|
||||
|
||||
endpointKey, err := peerSection.GetKey("Endpoint")
|
||||
if err == nil {
|
||||
endpoint := endpointKey.String()
|
||||
host, portString, err := net.SplitHostPort(endpoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("splitting endpoint: %w", err)
|
||||
}
|
||||
|
||||
ip, err := netip.ParseAddr(host)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", ErrEndpointHostNotIP, err)
|
||||
}
|
||||
|
||||
endpointPort, err := port.Validate(portString)
|
||||
if err != nil {
|
||||
return fmt.Errorf("port from Endpoint key: %w", err)
|
||||
}
|
||||
|
||||
selection.EndpointIP = ip
|
||||
selection.EndpointPort = &endpointPort
|
||||
} else if !regexINIKeyNotExist.MatchString(err.Error()) {
|
||||
// can never happen
|
||||
return fmt.Errorf("getting endpoint key: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
181
internal/configuration/sources/files/wireguardselection_test.go
Normal file
181
internal/configuration/sources/files/wireguardselection_test.go
Normal file
@@ -0,0 +1,181 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
func uint16Ptr(n uint16) *uint16 { return &n }
|
||||
|
||||
func Test_Source_readWireguardSelection(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("fail reading from file", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dirPath := t.TempDir()
|
||||
source := &Source{
|
||||
wireguardConfigPath: dirPath,
|
||||
}
|
||||
wireguard, err := source.readWireguardSelection()
|
||||
assert.Equal(t, settings.WireguardSelection{}, wireguard)
|
||||
assert.Error(t, err)
|
||||
assert.Regexp(t, `reading file: read .+: is a directory`, err.Error())
|
||||
})
|
||||
|
||||
t.Run("no file", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
noFile := filepath.Join(t.TempDir(), "doesnotexist")
|
||||
source := &Source{
|
||||
wireguardConfigPath: noFile,
|
||||
}
|
||||
wireguard, err := source.readWireguardSelection()
|
||||
assert.Equal(t, settings.WireguardSelection{}, wireguard)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
testCases := map[string]struct {
|
||||
fileContent string
|
||||
selection settings.WireguardSelection
|
||||
errMessage string
|
||||
}{
|
||||
"ini load error": {
|
||||
fileContent: "invalid",
|
||||
errMessage: "loading ini from reader: key-value delimiter not found: invalid",
|
||||
},
|
||||
"empty file": {},
|
||||
"peer section parsing error": {
|
||||
fileContent: `
|
||||
[Peer]
|
||||
PublicKey = x
|
||||
`,
|
||||
errMessage: "parsing peer section: parsing PublicKey: " +
|
||||
"x: wgtypes: failed to parse base64-encoded key: " +
|
||||
"illegal base64 data at input byte 0",
|
||||
},
|
||||
"success": {
|
||||
fileContent: `
|
||||
[Peer]
|
||||
PublicKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=
|
||||
Endpoint = 1.2.3.4:51820
|
||||
`,
|
||||
selection: settings.WireguardSelection{
|
||||
PublicKey: "QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=",
|
||||
EndpointIP: netip.AddrFrom4([4]byte{1, 2, 3, 4}),
|
||||
EndpointPort: uint16Ptr(51820),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for testName, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
configFile := filepath.Join(t.TempDir(), "wg.conf")
|
||||
err := os.WriteFile(configFile, []byte(testCase.fileContent), 0600)
|
||||
require.NoError(t, err)
|
||||
|
||||
source := &Source{
|
||||
wireguardConfigPath: configFile,
|
||||
}
|
||||
|
||||
wireguard, err := source.readWireguardSelection()
|
||||
|
||||
assert.Equal(t, testCase.selection, wireguard)
|
||||
if testCase.errMessage != "" {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseWireguardPeerSection(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
iniData string
|
||||
selection settings.WireguardSelection
|
||||
errMessage string
|
||||
}{
|
||||
"public key error": {
|
||||
iniData: `[Peer]
|
||||
PublicKey = x`,
|
||||
errMessage: "parsing PublicKey: x: " +
|
||||
"wgtypes: failed to parse base64-encoded key: " +
|
||||
"illegal base64 data at input byte 0",
|
||||
},
|
||||
"public key set": {
|
||||
iniData: `[Peer]
|
||||
PublicKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=`,
|
||||
selection: settings.WireguardSelection{
|
||||
PublicKey: "QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=",
|
||||
},
|
||||
},
|
||||
"missing port in endpoint": {
|
||||
iniData: `[Peer]
|
||||
Endpoint = x`,
|
||||
errMessage: "splitting endpoint: address x: missing port in address",
|
||||
},
|
||||
"endpoint host is not IP": {
|
||||
iniData: `[Peer]
|
||||
Endpoint = website.com:51820`,
|
||||
errMessage: "endpoint host is not an IP: ParseAddr(\"website.com\"): unexpected character (at \"website.com\")",
|
||||
},
|
||||
"endpoint port is not valid": {
|
||||
iniData: `[Peer]
|
||||
Endpoint = 1.2.3.4:518299`,
|
||||
errMessage: "port from Endpoint key: port cannot be higher than 65535: 518299",
|
||||
},
|
||||
"valid endpoint": {
|
||||
iniData: `[Peer]
|
||||
Endpoint = 1.2.3.4:51820`,
|
||||
selection: settings.WireguardSelection{
|
||||
EndpointIP: netip.AddrFrom4([4]byte{1, 2, 3, 4}),
|
||||
EndpointPort: uint16Ptr(51820),
|
||||
},
|
||||
},
|
||||
"all set": {
|
||||
iniData: `[Peer]
|
||||
PublicKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=
|
||||
Endpoint = 1.2.3.4:51820`,
|
||||
selection: settings.WireguardSelection{
|
||||
PublicKey: "QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=",
|
||||
EndpointIP: netip.AddrFrom4([4]byte{1, 2, 3, 4}),
|
||||
EndpointPort: uint16Ptr(51820),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for testName, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
iniFile, err := ini.Load([]byte(testCase.iniData))
|
||||
require.NoError(t, err)
|
||||
iniSection, err := iniFile.GetSection("Peer")
|
||||
require.NoError(t, err)
|
||||
|
||||
var selection settings.WireguardSelection
|
||||
err = parseWireguardPeerSection(iniSection, &selection)
|
||||
|
||||
assert.Equal(t, testCase.selection, selection)
|
||||
if testCase.errMessage != "" {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user