Compare commits

..

15 Commits

Author SHA1 Message Date
Quentin McGaw
e890c50da6 feat(firewall): support icmp rules 2024-12-25 20:05:55 +00:00
Quentin McGaw
ddd9f4d021 chore(natpmp): fix determinism for test Test_Client_ExternalAddress 2024-12-14 21:04:07 +00:00
dependabot[bot]
7e58b4baee Chore(deps): Bump github.com/stretchr/testify from 1.9.0 to 1.10.0 (#2600) 2024-12-14 21:19:30 +01:00
dependabot[bot]
a21fbb9a4f Chore(deps): Bump github.com/breml/rootcerts from 0.2.18 to 0.2.19 (#2601) 2024-12-14 21:19:11 +01:00
Quentin McGaw
3b7d27c919 hotfix(ci): use --device /dev/net/tun for test container 2024-12-14 20:15:42 +00:00
dependabot[bot]
68ddbfc0fe Chore(deps): Bump golang.org/x/net from 0.30.0 to 0.31.0 (#2578) 2024-11-18 10:46:04 +01:00
dependabot[bot]
a2047cb800 Chore(deps): Bump DavidAnson/markdownlint-cli2-action from 16 to 18 (#2588) 2024-11-18 10:45:49 +01:00
Quentin McGaw
fdd499146c fix(wireguard): point to Kubernetes wiki page when encountering IP rule add file exists error (#2526) 2024-11-15 18:47:06 +01:00
Quentin McGaw
37900341cf hotfix(firewall): fix unit test for previous PR 2024-11-15 17:46:10 +00:00
Jean-François Roy
36bb368cad fix(firewall): iptables list uses -n flag for testing iptables path (#2574)
Signed-off-by: Jean-Francois Roy <jf@devklog.net>
2024-11-15 16:47:08 +01:00
Quentin McGaw
f9bdb219d0 chore(deps): update gosettings to v0.4.4
- Better support for quote expressions especially for commands such as VPN_PORT_FORWARDING_UP_COMMAND
2024-11-12 09:11:48 +00:00
Quentin McGaw
0374c14e42 feat(portforwarding): VPN_PORT_FORWARDING_DOWN_COMMAND option 2024-11-10 10:18:29 +00:00
Alex Lavallee
a035a151bd feat(portforwarding): allow running script upon port forwarding success (#2399) 2024-11-10 09:49:02 +01:00
Quentin McGaw
e69966381d feat(fastestvpn): add aes-256-gcm to ciphers list 2024-11-09 15:44:05 +00:00
Quentin McGaw
94dfb2b1f2 fix(ipvanish): fix openvpn configuration
- update CA value
- add `comp-lzo` option
2024-11-09 15:43:51 +00:00
30 changed files with 576 additions and 88 deletions

View File

@@ -59,40 +59,13 @@ jobs:
- name: Run tests in test container
run: |
touch coverage.txt
docker run --rm \
docker run --rm --device /dev/net/tun \
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
test-container
- name: Build final image
run: docker build -t final-image .
- name: Run Wireguard
if: |
github.repository == 'qdm12/gluetun' &&
(
github.event_name == 'push' ||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
)
run: |
docker run -it --rm --cap-add=NET_ADMIN -e VPNSP=mullvad -e VPN_TYPE=wireguard \
-e WIREGUARD_PRIVATE_KEY=${{ secrets.WIREGUARD_PRIVATE_KEY }} \
-e WIREGUARD_ADDRESS=${{ secrets.WIREGUARD_ADDRESS }} \
final-image \
exit-once-connected
- name: Run OpenVPN
if: |
github.repository == 'qdm12/gluetun' &&
(
github.event_name == 'push' ||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
)
run: |
docker run -it --rm --cap-add=NET_ADMIN -e VPNSP=mullvad -e VPN_TYPE=openvpn \
-e OPENVPN_PASSWORD=${{ secrets.OPENVPN_PASSWORD }} \
final-image \
exit-once-connected
codeql:
runs-on: ubuntu-latest
permissions:

View File

@@ -20,7 +20,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: DavidAnson/markdownlint-cli2-action@v16
- uses: DavidAnson/markdownlint-cli2-action@v18
with:
globs: "**.md"
config: .markdownlint.json

View File

@@ -125,6 +125,8 @@ ENV VPN_SERVICE_PROVIDER=pia \
VPN_PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
VPN_PORT_FORWARDING_USERNAME= \
VPN_PORT_FORWARDING_PASSWORD= \
VPN_PORT_FORWARDING_UP_COMMAND= \
VPN_PORT_FORWARDING_DOWN_COMMAND= \
# # Cyberghost only:
OPENVPN_CERT= \
OPENVPN_KEY= \

View File

@@ -145,7 +145,6 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
tun Tun, netLinker netLinker, cmder RunStarter,
cli clier,
) error {
var exitOnceConnected bool
if len(args) > 1 { // cli operation
switch args[1] {
case "healthcheck":
@@ -160,8 +159,6 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
return cli.FormatServers(args[2:])
case "genkey":
return cli.GenKey(args[2:])
case "exit-once-connected":
exitOnceConnected = true
default:
return fmt.Errorf("%w: %s", errCommandUnknown, args[1])
}
@@ -383,7 +380,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
portForwardLogger := logger.New(log.SetComponent("port forwarding"))
portForwardLooper := portforward.NewLoop(allSettings.VPN.Provider.PortForwarding,
routingConf, httpClient, firewallConf, portForwardLogger, puid, pgid)
routingConf, httpClient, firewallConf, portForwardLogger, cmder, puid, pgid)
portForwardRunError, err := portForwardLooper.Start(ctx)
if err != nil {
return fmt.Errorf("starting port forwarding loop: %w", err)
@@ -480,8 +477,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
controlGroupHandler.Add(httpServerHandler)
healthLogger := logger.New(log.SetComponent("healthcheck"))
healthcheckServer := healthcheck.NewServer(allSettings.Health, healthLogger, vpnLooper,
exitOnceConnected)
healthcheckServer := healthcheck.NewServer(allSettings.Health, healthLogger, vpnLooper)
healthServerHandler, healthServerCtx, healthServerDone := goshutdown.NewGoRoutineHandler(
"HTTP health server", goroutine.OptionTimeout(defaultShutdownTimeout))
go healthcheckServer.Run(healthServerCtx, healthServerDone)

14
go.mod
View File

@@ -3,27 +3,27 @@ module github.com/qdm12/gluetun
go 1.23
require (
github.com/breml/rootcerts v0.2.18
github.com/breml/rootcerts v0.2.19
github.com/fatih/color v1.18.0
github.com/golang/mock v1.6.0
github.com/klauspost/compress v1.17.11
github.com/klauspost/pgzip v1.2.6
github.com/pelletier/go-toml/v2 v2.2.3
github.com/qdm12/dns/v2 v2.0.0-rc8
github.com/qdm12/gosettings v0.4.3
github.com/qdm12/gosettings v0.4.4
github.com/qdm12/goshutdown v0.3.0
github.com/qdm12/gosplash v0.2.0
github.com/qdm12/gotree v0.3.0
github.com/qdm12/log v0.1.0
github.com/qdm12/ss-server v0.6.0
github.com/stretchr/testify v1.9.0
github.com/stretchr/testify v1.10.0
github.com/ulikunitz/xz v0.5.11
github.com/vishvananda/netlink v1.2.1
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c
golang.org/x/net v0.30.0
golang.org/x/net v0.31.0
golang.org/x/sys v0.27.0
golang.org/x/text v0.19.0
golang.org/x/text v0.20.0
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
gopkg.in/ini.v1 v1.67.0
@@ -50,9 +50,9 @@ require (
github.com/qdm12/goservices v0.1.0 // indirect
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/crypto v0.29.0 // indirect
golang.org/x/mod v0.21.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sync v0.9.0 // indirect
golang.org/x/tools v0.26.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
google.golang.org/protobuf v1.35.1 // indirect

28
go.sum
View File

@@ -1,7 +1,7 @@
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.2.18 h1:KjZaNT7AX/akUjzpStuwTMQs42YHlPyc6NmdwShVba0=
github.com/breml/rootcerts v0.2.18/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
github.com/breml/rootcerts v0.2.19 h1:3D/qwAC1xoh82GmZ21mYzQ1NaLOICUVntIo+MRZYr4U=
github.com/breml/rootcerts v0.2.19/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -57,8 +57,8 @@ github.com/qdm12/dns/v2 v2.0.0-rc8 h1:kbgKPkbT+79nScfuZ0ZcVhksTGo8IUqQ8TTQGnQlZ1
github.com/qdm12/dns/v2 v2.0.0-rc8/go.mod h1:VaF02KWEL7xNV4oKfG4N9nEv/kR6bqyIcBReCV5NJhw=
github.com/qdm12/goservices v0.1.0 h1:9sODefm/yuIGS7ynCkEnNlMTAYn9GzPhtcK4F69JWvc=
github.com/qdm12/goservices v0.1.0/go.mod h1:/JOFsAnHFiSjyoXxa5FlfX903h20K5u/3rLzCjYVMck=
github.com/qdm12/gosettings v0.4.3 h1:oGAjiKVtml9oHVlPQo6H3yk6TmtWpVYicNeGFcM7AP8=
github.com/qdm12/gosettings v0.4.3/go.mod h1:CPrt2YC4UsURTrslmhxocVhMCW03lIrqdH2hzIf5prg=
github.com/qdm12/gosettings v0.4.4 h1:SM6tOZDf6k8qbjWU8KWyBF4mWIixfsKCfh9DGRLHlj4=
github.com/qdm12/gosettings v0.4.4/go.mod h1:CPrt2YC4UsURTrslmhxocVhMCW03lIrqdH2hzIf5prg=
github.com/qdm12/goshutdown v0.3.0 h1:pqBpJkdwlZlfTEx4QHtS8u8CXx6pG0fVo6S1N0MpSEM=
github.com/qdm12/goshutdown v0.3.0/go.mod h1:EqZ46No00kCTZ5qzdd3qIzY6ayhMt24QI8Mh8LVQYmM=
github.com/qdm12/gosplash v0.2.0 h1:DOxCEizbW6ZG+FgpH2oK1atT6bM8MHL9GZ2ywSS4zZY=
@@ -73,8 +73,8 @@ github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/vishvananda/netlink v1.2.1 h1:pfLv/qlJUwOTPvtWREA7c3PI4u81YkqZw1DYhI2HmLA=
@@ -87,8 +87,8 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
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=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -97,12 +97,12 @@ golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -117,8 +117,8 @@ golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

150
internal/command/split.go Normal file
View File

@@ -0,0 +1,150 @@
package command
import (
"bytes"
"errors"
"fmt"
"strings"
"unicode/utf8"
)
var (
ErrCommandEmpty = errors.New("command is empty")
ErrSingleQuoteUnterminated = errors.New("unterminated single-quoted string")
ErrDoubleQuoteUnterminated = errors.New("unterminated double-quoted string")
ErrEscapeUnterminated = errors.New("unterminated backslash-escape")
)
// Split splits a command string into a slice of arguments.
// This is especially important for commands such as:
// /bin/sh -c "echo hello"
// which should be split into: ["/bin/sh", "-c", "echo hello"]
// It supports backslash-escapes, single-quotes and double-quotes.
// It does not support:
// - the $" quoting style.
// - expansion (brace, shell or pathname).
func Split(command string) (words []string, err error) {
if command == "" {
return nil, fmt.Errorf("%w", ErrCommandEmpty)
}
const bufferSize = 1024
buffer := bytes.NewBuffer(make([]byte, bufferSize))
startIndex := 0
for startIndex < len(command) {
// skip any split characters at the start
character, runeSize := utf8.DecodeRuneInString(command[startIndex:])
switch {
case strings.ContainsRune(" \n\t", character):
startIndex += runeSize
case character == '\\':
// Look ahead to eventually skip an escaped newline
if command[startIndex+runeSize:] == "" {
return nil, fmt.Errorf("%w: %q", ErrEscapeUnterminated, command)
}
character, runeSize := utf8.DecodeRuneInString(command[startIndex+runeSize:])
if character == '\n' {
startIndex += runeSize + runeSize // backslash and newline
}
default:
var word string
buffer.Reset()
word, startIndex, err = splitWord(command, startIndex, buffer)
if err != nil {
return nil, fmt.Errorf("splitting word in %q: %w", command, err)
}
words = append(words, word)
}
}
return words, nil
}
// WARNING: buffer must be cleared before calling this function.
func splitWord(input string, startIndex int, buffer *bytes.Buffer) (
word string, newStartIndex int, err error,
) {
cursor := startIndex
for cursor < len(input) {
character, runeLength := utf8.DecodeRuneInString(input[cursor:])
cursor += runeLength
if character == '"' ||
character == '\'' ||
character == '\\' ||
character == ' ' ||
character == '\n' ||
character == '\t' {
buffer.WriteString(input[startIndex : cursor-runeLength])
}
switch {
case strings.ContainsRune(" \n\t", character): // spacing character
return buffer.String(), cursor, nil
case character == '"':
return handleDoubleQuoted(input, cursor, buffer)
case character == '\'':
return handleSingleQuoted(input, cursor, buffer)
case character == '\\':
return handleEscaped(input, cursor, buffer)
}
}
buffer.WriteString(input[startIndex:])
return buffer.String(), len(input), nil
}
func handleDoubleQuoted(input string, startIndex int, buffer *bytes.Buffer) (
word string, newStartIndex int, err error,
) {
cursor := startIndex
for cursor < len(input) {
nextCharacter, nextRuneLength := utf8.DecodeRuneInString(input[cursor:])
cursor += nextRuneLength
switch nextCharacter {
case '"': // end of the double quoted string
buffer.WriteString(input[startIndex : cursor-nextRuneLength])
return splitWord(input, cursor, buffer)
case '\\': // escaped character
escapedCharacter, escapedRuneLength := utf8.DecodeRuneInString(input[cursor:])
cursor += escapedRuneLength
if !strings.ContainsRune("$`\"\n\\", escapedCharacter) {
break
}
buffer.WriteString(input[startIndex : cursor-nextRuneLength-escapedRuneLength])
if escapedCharacter != '\n' {
// skip backslash entirely for the newline character
buffer.WriteRune(escapedCharacter)
}
startIndex = cursor
}
}
return "", 0, fmt.Errorf("%w", ErrDoubleQuoteUnterminated)
}
func handleSingleQuoted(input string, startIndex int, buffer *bytes.Buffer) (
word string, newStartIndex int, err error,
) {
closingQuoteIndex := strings.IndexRune(input[startIndex:], '\'')
if closingQuoteIndex == -1 {
return "", 0, fmt.Errorf("%w", ErrSingleQuoteUnterminated)
}
buffer.WriteString(input[startIndex : startIndex+closingQuoteIndex])
const singleQuoteRuneLength = 1
startIndex += closingQuoteIndex + singleQuoteRuneLength
return splitWord(input, startIndex, buffer)
}
func handleEscaped(input string, startIndex int, buffer *bytes.Buffer) (
word string, newStartIndex int, err error,
) {
if input[startIndex:] == "" {
return "", 0, fmt.Errorf("%w", ErrEscapeUnterminated)
}
character, runeLength := utf8.DecodeRuneInString(input[startIndex:])
if character != '\n' { // backslash-escaped newline is ignored
buffer.WriteString(input[startIndex : startIndex+runeLength])
}
startIndex += runeLength
return splitWord(input, startIndex, buffer)
}

View File

@@ -0,0 +1,110 @@
package command
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test_Split(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
command string
words []string
errWrapped error
errMessage string
}{
"empty": {
command: "",
errWrapped: ErrCommandEmpty,
errMessage: "command is empty",
},
"concrete_sh_command": {
command: `/bin/sh -c "echo 123"`,
words: []string{"/bin/sh", "-c", "echo 123"},
},
"single_word": {
command: "word1",
words: []string{"word1"},
},
"two_words_single_space": {
command: "word1 word2",
words: []string{"word1", "word2"},
},
"two_words_multiple_space": {
command: "word1 word2",
words: []string{"word1", "word2"},
},
"two_words_no_expansion": {
command: "word1* word2?",
words: []string{"word1*", "word2?"},
},
"escaped_single quote": {
command: "ain\\'t good",
words: []string{"ain't", "good"},
},
"escaped_single_quote_all_single_quoted": {
command: "'ain'\\''t good'",
words: []string{"ain't good"},
},
"empty_single_quoted": {
command: "word1 '' word2",
words: []string{"word1", "", "word2"},
},
"escaped_newline": {
command: "word1\\\nword2",
words: []string{"word1word2"},
},
"quoted_newline": {
command: "text \"with\na\" quoted newline",
words: []string{"text", "with\na", "quoted", "newline"},
},
"quoted_escaped_newline": {
command: "\"word1\\d\\\\\\\" word2\\\nword3 word4\"",
words: []string{"word1\\d\\\" word2word3 word4"},
},
"escaped_separated_newline": {
command: "word1 \\\n word2",
words: []string{"word1", "word2"},
},
"double_quotes_no_spacing": {
command: "word1\"word2\"word3",
words: []string{"word1word2word3"},
},
"unterminated_single_quote": {
command: "'abc'\\''def",
errWrapped: ErrSingleQuoteUnterminated,
errMessage: `splitting word in "'abc'\\''def": unterminated single-quoted string`,
},
"unterminated_double_quote": {
command: "\"abc'def",
errWrapped: ErrDoubleQuoteUnterminated,
errMessage: `splitting word in "\"abc'def": unterminated double-quoted string`,
},
"unterminated_escape": {
command: "abc\\",
errWrapped: ErrEscapeUnterminated,
errMessage: `splitting word in "abc\\": unterminated backslash-escape`,
},
"unterminated_escape_only": {
command: " \\",
errWrapped: ErrEscapeUnterminated,
errMessage: `unterminated backslash-escape: " \\"`,
},
}
for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
t.Parallel()
words, err := Split(testCase.command)
assert.Equal(t, testCase.words, words)
assert.ErrorIs(t, err, testCase.errWrapped)
if testCase.errWrapped != nil {
assert.EqualError(t, err, testCase.errMessage)
}
})
}
}

View File

@@ -29,6 +29,14 @@ type PortForwarding struct {
// to write to a file. It cannot be nil for the
// internal state
Filepath *string `json:"status_file_path"`
// UpCommand is the command to use when the port forwarding is up.
// It can be the empty string to indicate not to run a command.
// It cannot be nil in the internal state.
UpCommand *string `json:"up_command"`
// DownCommand is the command to use after the port forwarding goes down.
// It can be the empty string to indicate to NOT run a command.
// It cannot be nil in the internal state.
DownCommand *string `json:"down_command"`
// ListeningPort is the port traffic would be redirected to from the
// forwarded port. The redirection is disabled if it is set to 0, which
// is its default as well.
@@ -84,6 +92,8 @@ func (p *PortForwarding) Copy() (copied PortForwarding) {
Enabled: gosettings.CopyPointer(p.Enabled),
Provider: gosettings.CopyPointer(p.Provider),
Filepath: gosettings.CopyPointer(p.Filepath),
UpCommand: gosettings.CopyPointer(p.UpCommand),
DownCommand: gosettings.CopyPointer(p.DownCommand),
ListeningPort: gosettings.CopyPointer(p.ListeningPort),
Username: p.Username,
Password: p.Password,
@@ -94,6 +104,8 @@ func (p *PortForwarding) OverrideWith(other PortForwarding) {
p.Enabled = gosettings.OverrideWithPointer(p.Enabled, other.Enabled)
p.Provider = gosettings.OverrideWithPointer(p.Provider, other.Provider)
p.Filepath = gosettings.OverrideWithPointer(p.Filepath, other.Filepath)
p.UpCommand = gosettings.OverrideWithPointer(p.UpCommand, other.UpCommand)
p.DownCommand = gosettings.OverrideWithPointer(p.DownCommand, other.DownCommand)
p.ListeningPort = gosettings.OverrideWithPointer(p.ListeningPort, other.ListeningPort)
p.Username = gosettings.OverrideWithComparable(p.Username, other.Username)
p.Password = gosettings.OverrideWithComparable(p.Password, other.Password)
@@ -103,6 +115,8 @@ func (p *PortForwarding) setDefaults() {
p.Enabled = gosettings.DefaultPointer(p.Enabled, false)
p.Provider = gosettings.DefaultPointer(p.Provider, "")
p.Filepath = gosettings.DefaultPointer(p.Filepath, "/tmp/gluetun/forwarded_port")
p.UpCommand = gosettings.DefaultPointer(p.UpCommand, "")
p.DownCommand = gosettings.DefaultPointer(p.DownCommand, "")
p.ListeningPort = gosettings.DefaultPointer(p.ListeningPort, 0)
}
@@ -135,6 +149,13 @@ func (p PortForwarding) toLinesNode() (node *gotree.Node) {
}
node.Appendf("Forwarded port file path: %s", filepath)
if *p.UpCommand != "" {
node.Appendf("Forwarded port up command: %s", *p.UpCommand)
}
if *p.DownCommand != "" {
node.Appendf("Forwarded port down command: %s", *p.DownCommand)
}
if p.Username != "" {
credentialsNode := node.Appendf("Credentials:")
credentialsNode.Appendf("Username: %s", p.Username)
@@ -163,6 +184,12 @@ func (p *PortForwarding) read(r *reader.Reader) (err error) {
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE",
))
p.UpCommand = r.Get("VPN_PORT_FORWARDING_UP_COMMAND",
reader.ForceLowercase(false))
p.DownCommand = r.Get("VPN_PORT_FORWARDING_DOWN_COMMAND",
reader.ForceLowercase(false))
p.ListeningPort, err = r.Uint16Ptr("VPN_PORT_FORWARDING_LISTENING_PORT")
if err != nil {
return err

View File

@@ -22,7 +22,7 @@ type chainRule struct {
packets uint64
bytes uint64
target string // "ACCEPT", "DROP", "REJECT" or "REDIRECT"
protocol string // "tcp", "udp" or "" for all protocols.
protocol string // "icmp", "tcp", "udp" or "" for all protocols.
inputInterface string // input interface, for example "tun0" or "*""
outputInterface string // output interface, for example "eth0" or "*""
source netip.Prefix // source IP CIDR, for example 0.0.0.0/0. Must be valid.
@@ -324,6 +324,8 @@ var ErrProtocolUnknown = errors.New("unknown protocol")
func parseProtocol(s string) (protocol string, err error) {
switch s {
case "0":
case "1":
protocol = "icmp"
case "6":
protocol = "tcp"
case "17":

View File

@@ -56,7 +56,8 @@ num pkts bytes target prot opt in out source destinati
num pkts bytes target prot opt in out source destination
1 0 0 ACCEPT 17 -- tun0 * 0.0.0.0/0 0.0.0.0/0 udp dpt:55405
2 0 0 ACCEPT 6 -- tun0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:55405
3 0 0 DROP 0 -- tun0 * 1.2.3.4 0.0.0.0/0
3 0 0 ACCEPT 1 -- tun0 * 0.0.0.0/0 0.0.0.0/0
4 0 0 DROP 0 -- tun0 * 1.2.3.4 0.0.0.0/0
`,
table: chain{
name: "INPUT",
@@ -92,6 +93,17 @@ num pkts bytes target prot opt in out source destinati
lineNumber: 3,
packets: 0,
bytes: 0,
target: "ACCEPT",
protocol: "icmp",
inputInterface: "tun0",
outputInterface: "*",
source: netip.MustParsePrefix("0.0.0.0/0"),
destination: netip.MustParsePrefix("0.0.0.0/0"),
},
{
lineNumber: 4,
packets: 0,
bytes: 0,
target: "DROP",
protocol: "",
inputInterface: "tun0",

View File

@@ -92,7 +92,7 @@ func testIptablesPath(ctx context.Context, path string,
// Set policy as the existing policy so no mutation is done.
// This is an extra check for some buggy kernels where setting the policy
// does not work.
cmd = exec.CommandContext(ctx, path, "-L", "INPUT")
cmd = exec.CommandContext(ctx, path, "-nL", "INPUT")
output, err = runner.Run(cmd)
if err != nil {
unsupportedMessage = fmt.Sprintf("%s (%s)", output, err)

View File

@@ -24,7 +24,7 @@ func newDeleteTestRuleMatcher(path string) *cmdMatcher {
func newListInputRulesMatcher(path string) *cmdMatcher {
return newCmdMatcher(path,
"^-L$", "^INPUT$")
"^-nL$", "^INPUT$")
}
func newSetPolicyMatcher(path, inputPolicy string) *cmdMatcher { //nolint:unparam

View File

@@ -9,16 +9,15 @@ import (
)
type Server struct {
logger Logger
handler *handler
dialer *net.Dialer
config settings.Health
vpn vpnHealth
exitOnceConnected bool
logger Logger
handler *handler
dialer *net.Dialer
config settings.Health
vpn vpnHealth
}
func NewServer(config settings.Health,
logger Logger, vpnLoop StatusApplier, exitOnceConnected bool,
logger Logger, vpnLoop StatusApplier,
) *Server {
return &Server{
logger: logger,

View File

@@ -2,6 +2,7 @@ package natpmp
import (
"context"
"net"
"net/netip"
"testing"
"time"
@@ -23,14 +24,15 @@ func Test_Client_ExternalAddress(t *testing.T) {
durationSinceStartOfEpoch time.Duration
externalIPv4Address netip.Addr
err error
errMessage string
errMessageRegex string
}{
"failure": {
ctx: canceledCtx,
gateway: netip.AddrFrom4([4]byte{127, 0, 0, 1}),
initialConnDuration: initialConnectionDuration,
err: context.Canceled,
errMessage: "executing remote procedure call: reading from udp connection: context canceled",
err: net.ErrClosed,
errMessageRegex: "executing remote procedure call: setting connection deadline: " +
"set udp 127.0.0.1:[1-9][0-9]{1,4}: use of closed network connection",
},
"success": {
ctx: context.Background(),
@@ -60,7 +62,7 @@ func Test_Client_ExternalAddress(t *testing.T) {
durationSinceStartOfEpoch, externalIPv4Address, err := client.ExternalAddress(testCase.ctx, testCase.gateway)
assert.ErrorIs(t, err, testCase.err)
if testCase.err != nil {
assert.EqualError(t, err, testCase.errMessage)
assert.Regexp(t, testCase.errMessageRegex, err.Error())
}
assert.Equal(t, testCase.durationSinceStartOfEpoch, durationSinceStartOfEpoch)
assert.Equal(t, testCase.externalIPv4Address, externalIPv4Address)

View File

@@ -45,8 +45,10 @@ func (c *Client) rpc(ctx context.Context, gateway netip.Addr,
cancel()
<-endGoroutineDone
}()
ctxListeningReady := make(chan struct{})
go func() {
defer close(endGoroutineDone)
close(ctxListeningReady)
// Context is canceled either by the parent context or
// when this function returns.
<-ctx.Done()
@@ -60,6 +62,7 @@ func (c *Client) rpc(ctx context.Context, gateway netip.Addr,
}
err = fmt.Errorf("%w; closing connection: %w", err, closeErr)
}()
<-ctxListeningReady // really to make unit testing reliable
const maxResponseSize = 16
response = make([]byte, maxResponseSize)

View File

@@ -1,13 +0,0 @@
package netlink
import (
"testing"
"github.com/qdm12/log"
)
func Test_IsIPv6Supported(t *testing.T) {
n := New(log.New(log.SetLevel(log.LevelDebug)))
supported, err := n.IsIPv6Supported()
t.Log(supported, err)
}

View File

@@ -3,6 +3,7 @@ package portforward
import (
"context"
"net/netip"
"os/exec"
)
type Service interface {
@@ -29,3 +30,8 @@ type Logger interface {
Warn(s string)
Error(s string)
}
type Cmder interface {
Start(cmd *exec.Cmd) (stdoutLines, stderrLines <-chan string,
waitError <-chan error, startErr error)
}

View File

@@ -20,6 +20,7 @@ type Loop struct {
client *http.Client
portAllower PortAllower
logger Logger
cmder Cmder
// Fixed parameters
uid, gid int
// Internal channels and locks
@@ -34,7 +35,7 @@ type Loop struct {
func NewLoop(settings settings.PortForwarding, routing Routing,
client *http.Client, portAllower PortAllower,
logger Logger, uid, gid int,
logger Logger, cmder Cmder, uid, gid int,
) *Loop {
return &Loop{
settings: Settings{
@@ -42,6 +43,8 @@ func NewLoop(settings settings.PortForwarding, routing Routing,
Service: service.Settings{
Enabled: settings.Enabled,
Filepath: *settings.Filepath,
UpCommand: *settings.UpCommand,
DownCommand: *settings.DownCommand,
ListeningPort: *settings.ListeningPort,
},
},
@@ -49,6 +52,7 @@ func NewLoop(settings settings.PortForwarding, routing Routing,
client: client,
portAllower: portAllower,
logger: logger,
cmder: cmder,
uid: uid,
gid: gid,
}
@@ -115,7 +119,7 @@ func (l *Loop) run(runCtx context.Context, runDone chan<- struct{},
*serviceSettings.Enabled = *serviceSettings.Enabled && *l.settings.VPNIsUp
l.service = service.New(serviceSettings, l.routing, l.client,
l.portAllower, l.logger, l.uid, l.gid)
l.portAllower, l.logger, l.cmder, l.uid, l.gid)
var err error
serviceRunError, err = l.service.Start(runCtx)

View File

@@ -0,0 +1,59 @@
package service
import (
"context"
"fmt"
"os/exec"
"strings"
"github.com/qdm12/gluetun/internal/command"
)
func runCommand(ctx context.Context, cmder Cmder, logger Logger,
commandTemplate string, ports []uint16,
) (err error) {
portStrings := make([]string, len(ports))
for i, port := range ports {
portStrings[i] = fmt.Sprint(int(port))
}
portsString := strings.Join(portStrings, ",")
commandString := strings.ReplaceAll(commandTemplate, "{{PORTS}}", portsString)
args, err := command.Split(commandString)
if err != nil {
return fmt.Errorf("parsing command: %w", err)
}
cmd := exec.CommandContext(ctx, args[0], args[1:]...) // #nosec G204
stdout, stderr, waitError, err := cmder.Start(cmd)
if err != nil {
return err
}
streamCtx, streamCancel := context.WithCancel(context.Background())
streamDone := make(chan struct{})
go streamLines(streamCtx, streamDone, logger, stdout, stderr)
err = <-waitError
streamCancel()
<-streamDone
return err
}
func streamLines(ctx context.Context, done chan<- struct{},
logger Logger, stdout, stderr <-chan string,
) {
defer close(done)
var line string
for {
select {
case <-ctx.Done():
return
case line = <-stdout:
logger.Info(line)
case line = <-stderr:
logger.Error(line)
}
}
}

View File

@@ -0,0 +1,28 @@
//go:build linux
package service
import (
"context"
"testing"
gomock "github.com/golang/mock/gomock"
"github.com/qdm12/gluetun/internal/command"
"github.com/stretchr/testify/require"
)
func Test_Service_runCommand(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)
ctx := context.Background()
cmder := command.New()
const commandTemplate = `/bin/sh -c "echo {{PORTS}}"`
ports := []uint16{1234, 5678}
logger := NewMockLogger(ctrl)
logger.EXPECT().Info("1234,5678")
err := runCommand(ctx, cmder, logger, commandTemplate, ports)
require.NoError(t, err)
}

View File

@@ -3,6 +3,7 @@ package service
import (
"context"
"net/netip"
"os/exec"
"github.com/qdm12/gluetun/internal/provider/utils"
)
@@ -32,3 +33,8 @@ type PortForwarder interface {
ports []uint16, err error)
KeepPortForward(ctx context.Context, objects utils.PortForwardObjects) (err error)
}
type Cmder interface {
Start(cmd *exec.Cmd) (stdoutLines, stderrLines <-chan string,
waitError <-chan error, startErr error)
}

View File

@@ -0,0 +1,3 @@
package service
//go:generate mockgen -destination=mocks_test.go -package=$GOPACKAGE . Logger

View File

@@ -0,0 +1,82 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/qdm12/gluetun/internal/portforward/service (interfaces: Logger)
// Package service is a generated GoMock package.
package service
import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
)
// MockLogger is a mock of Logger interface.
type MockLogger struct {
ctrl *gomock.Controller
recorder *MockLoggerMockRecorder
}
// MockLoggerMockRecorder is the mock recorder for MockLogger.
type MockLoggerMockRecorder struct {
mock *MockLogger
}
// NewMockLogger creates a new mock instance.
func NewMockLogger(ctrl *gomock.Controller) *MockLogger {
mock := &MockLogger{ctrl: ctrl}
mock.recorder = &MockLoggerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockLogger) EXPECT() *MockLoggerMockRecorder {
return m.recorder
}
// Debug mocks base method.
func (m *MockLogger) Debug(arg0 string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Debug", arg0)
}
// Debug indicates an expected call of Debug.
func (mr *MockLoggerMockRecorder) Debug(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Debug", reflect.TypeOf((*MockLogger)(nil).Debug), arg0)
}
// Error mocks base method.
func (m *MockLogger) Error(arg0 string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Error", arg0)
}
// Error indicates an expected call of Error.
func (mr *MockLoggerMockRecorder) Error(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Error", reflect.TypeOf((*MockLogger)(nil).Error), arg0)
}
// Info mocks base method.
func (m *MockLogger) Info(arg0 string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Info", arg0)
}
// Info indicates an expected call of Info.
func (mr *MockLoggerMockRecorder) Info(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Info", reflect.TypeOf((*MockLogger)(nil).Info), arg0)
}
// Warn mocks base method.
func (m *MockLogger) Warn(arg0 string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Warn", arg0)
}
// Warn indicates an expected call of Warn.
func (mr *MockLoggerMockRecorder) Warn(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warn", reflect.TypeOf((*MockLogger)(nil).Warn), arg0)
}

View File

@@ -19,6 +19,7 @@ type Service struct {
client *http.Client
portAllower PortAllower
logger Logger
cmder Cmder
// Internal channels and locks
startStopMutex sync.Mutex
keepPortCancel context.CancelFunc
@@ -26,7 +27,7 @@ type Service struct {
}
func New(settings Settings, routing Routing, client *http.Client,
portAllower PortAllower, logger Logger, puid, pgid int,
portAllower PortAllower, logger Logger, cmder Cmder, puid, pgid int,
) *Service {
return &Service{
// Fixed parameters
@@ -38,6 +39,7 @@ func New(settings Settings, routing Routing, client *http.Client,
client: client,
portAllower: portAllower,
logger: logger,
cmder: cmder,
}
}

View File

@@ -12,6 +12,8 @@ type Settings struct {
Enabled *bool
PortForwarder PortForwarder
Filepath string
UpCommand string
DownCommand string
Interface string // needed for PIA, PrivateVPN and ProtonVPN, tun0 for example
ServerName string // needed for PIA
CanPortForward bool // needed for PIA
@@ -24,6 +26,8 @@ func (s Settings) Copy() (copied Settings) {
copied.Enabled = gosettings.CopyPointer(s.Enabled)
copied.PortForwarder = s.PortForwarder
copied.Filepath = s.Filepath
copied.UpCommand = s.UpCommand
copied.DownCommand = s.DownCommand
copied.Interface = s.Interface
copied.ServerName = s.ServerName
copied.CanPortForward = s.CanPortForward
@@ -37,6 +41,8 @@ func (s *Settings) OverrideWith(update Settings) {
s.Enabled = gosettings.OverrideWithPointer(s.Enabled, update.Enabled)
s.PortForwarder = gosettings.OverrideWithComparable(s.PortForwarder, update.PortForwarder)
s.Filepath = gosettings.OverrideWithComparable(s.Filepath, update.Filepath)
s.UpCommand = gosettings.OverrideWithComparable(s.UpCommand, update.UpCommand)
s.DownCommand = gosettings.OverrideWithComparable(s.DownCommand, update.DownCommand)
s.Interface = gosettings.OverrideWithComparable(s.Interface, update.Interface)
s.ServerName = gosettings.OverrideWithComparable(s.ServerName, update.ServerName)
s.CanPortForward = gosettings.OverrideWithComparable(s.CanPortForward, update.CanPortForward)

View File

@@ -73,6 +73,14 @@ func (s *Service) Start(ctx context.Context) (runError <-chan error, err error)
s.ports = ports
s.portMutex.Unlock()
if s.settings.UpCommand != "" {
err = runCommand(ctx, s.cmder, s.logger, s.settings.UpCommand, ports)
if err != nil {
err = fmt.Errorf("running up command: %w", err)
s.logger.Error(err.Error())
}
}
keepPortCtx, keepPortCancel := context.WithCancel(context.Background())
s.keepPortCancel = keepPortCancel
runErrorCh := make(chan error)

View File

@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"os"
"time"
)
func (s *Service) Stop() (err error) {
@@ -30,6 +31,17 @@ func (s *Service) cleanup() (err error) {
s.portMutex.Lock()
defer s.portMutex.Unlock()
if s.settings.DownCommand != "" {
const downTimeout = 60 * time.Second
ctx, cancel := context.WithTimeout(context.Background(), downTimeout)
defer cancel()
err = runCommand(ctx, s.cmder, s.logger, s.settings.DownCommand, s.ports)
if err != nil {
err = fmt.Errorf("running down command: %w", err)
s.logger.Error(err.Error())
}
}
for _, port := range s.ports {
err = s.portAllower.RemoveAllowedPort(context.Background(), port)
if err != nil {

View File

@@ -14,13 +14,17 @@ func (p *Provider) OpenVPNConfig(connection models.Connection,
providerSettings := utils.OpenVPNProviderSettings{
AuthUserPass: true,
Ciphers: []string{
openvpn.AES256gcm,
openvpn.AES256cbc,
},
Auth: openvpn.SHA256,
VerifyX509Type: "name",
TLSCipher: "TLS-DHE-RSA-WITH-AES-256-CBC-SHA:TLS-DHE-DSS-WITH-AES-256-CBC-SHA:TLS-RSA-WITH-AES-256-CBC-SHA",
CAs: []string{"MIIErTCCA5WgAwIBAgIJAMYKzSS8uPKDMA0GCSqGSIb3DQEBDQUAMIGVMQswCQYDVQQGEwJVUzELMAkGA1UECBMCRkwxFDASBgNVBAcTC1dpbnRlciBQYXJrMREwDwYDVQQKEwhJUFZhbmlzaDEVMBMGA1UECxMMSVBWYW5pc2ggVlBOMRQwEgYDVQQDEwtJUFZhbmlzaCBDQTEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEBpcHZhbmlzaC5jb20wHhcNMTIwMTExMTkzMjIwWhcNMjgxMTAyMTkzMjIwWjCBlTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkZMMRQwEgYDVQQHEwtXaW50ZXIgUGFyazERMA8GA1UEChMISVBWYW5pc2gxFTATBgNVBAsTDElQVmFuaXNoIFZQTjEUMBIGA1UEAxMLSVBWYW5pc2ggQ0ExIzAhBgkqhkiG9w0BCQEWFHN1cHBvcnRAaXB2YW5pc2guY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt9DBWNr/IKOuY3TmDP5x7vYZR0DGxLbXU8TyAzBbjUtFFMbhxlHiXVQrZHmgzih94x7BgXM7tWpmMKYVb+gNaqMdWE680Qm3nOwmhy/dulXDkEHAwD05i/iTx4ZaUdtV2vsKBxRg1vdC4AEiwD7bqV4HOi13xcG971aQ55Mj1KeCdA0aNvpat1LWx2jjWxsfI8s2Lv5Fkoi1HO1+vTnnaEsJZrBgAkLXpItqP29Lik3/OBIvkBIxlKrhiVPixE5qNiD+eSPirsmROvsyIonoJtuY4Dw5K6pcNlKyYiwo1IOFYU3YxffwFJk+bSW4WVBhsdf5dGxq/uOHmuz5gdwxCwIDAQABo4H9MIH6MAwGA1UdEwQFMAMBAf8wHQYDVR0OBBYEFEv9FCWJHefBcIPX9p8RHCVOGe6uMIHKBgNVHSMEgcIwgb+AFEv9FCWJHefBcIPX9p8RHCVOGe6uoYGbpIGYMIGVMQswCQYDVQQGEwJVUzELMAkGA1UECBMCRkwxFDASBgNVBAcTC1dpbnRlciBQYXJrMREwDwYDVQQKEwhJUFZhbmlzaDEVMBMGA1UECxMMSVBWYW5pc2ggVlBOMRQwEgYDVQQDEwtJUFZhbmlzaCBDQTEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEBpcHZhbmlzaC5jb22CCQDGCs0kvLjygzANBgkqhkiG9w0BAQ0FAAOCAQEAI2dkh/43ksV2fdYpVGhYaFZPVqCJoToCez0IvOmLeLGzow+EOSrY508oyjYeNP4VJEjApqo0NrMbKl8g/8bpLBcotOCF1c1HZ+y9v7648uumh01SMjsbBeHOuQcLb+7gX6c0pEmxWv8qj5JiW3/1L1bktnjW5Yp5oFkFSMXjOnIoYKHyKLjN2jtwH6XowUNYpg4qVtKU0CXPdOznWcd9/zSfa393HwJPeeVLbKYaFMC4IEbIUmKYtWyoJ9pJ58smU3pWsHZUg9Zc0LZZNjkNlBdQSLmUHAJ33Bd7pJS0JQeiWviC+4UTmzEWRKa7pDGnYRYNu2cUo0/voStphv8EVA=="}, //nolint:lll
CAs: []string{"MIIErzCCA5egAwIBAgIJAMYKzSS8uPKDMA0GCSqGSIb3DQEBDQUAMIGVMQswCQYDVQQGEwJVUzELMAkGA1UECBMCRkwxFDASBgNVBAcTC1dpbnRlciBQYXJrMREwDwYDVQQKEwhJUFZhbmlzaDEVMBMGA1UECxMMSVBWYW5pc2ggVlBOMRQwEgYDVQQDEwtJUFZhbmlzaCBDQTEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEBpcHZhbmlzaC5jb20wIBcNMjIwNTA5MjAyMDQ1WhgPMjA4MjA0MjQyMDIwNDVaMIGVMQswCQYDVQQGEwJVUzELMAkGA1UECBMCRkwxFDASBgNVBAcTC1dpbnRlciBQYXJrMREwDwYDVQQKEwhJUFZhbmlzaDEVMBMGA1UECxMMSVBWYW5pc2ggVlBOMRQwEgYDVQQDEwtJUFZhbmlzaCBDQTEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEBpcHZhbmlzaC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC30MFY2v8go65jdOYM/nHu9hlHQMbEttdTxPIDMFuNS0UUxuHGUeJdVCtkeaDOKH3jHsGBczu1amYwphVv6A1qox1YTrzRCbec7CaHL926VcOQQcDAPTmL+JPHhlpR21Xa+woHFGDW90LgASLAPtupXgc6LXfFwb3vVpDnkyPUp4J0DRo2+lq3UtbHaONbGx8jyzYu/kWSiLUc7X69OedoSwlmsGACQteki2o/b0uKTf84Ei+QEjGUquGJU+LETmo2IP55I+KuyZE6+zIiiegm25jgPDkrqlw2UrJiLCjUg4VhTdjF9/AUmT5tJbhZUGGx1/l0bGr+44ea7PmB3DELAgMBAAGjgf0wgfowDAYDVR0TBAUwAwEB/zAdBgNVHQ4EFgQUS/0UJYkd58Fwg9f2nxEcJU4Z7q4wgcoGA1UdIwSBwjCBv4AUS/0UJYkd58Fwg9f2nxEcJU4Z7q6hgZukgZgwgZUxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJGTDEUMBIGA1UEBxMLV2ludGVyIFBhcmsxETAPBgNVBAoTCElQVmFuaXNoMRUwEwYDVQQLEwxJUFZhbmlzaCBWUE4xFDASBgNVBAMTC0lQVmFuaXNoIENBMSMwIQYJKoZIhvcNAQkBFhRzdXBwb3J0QGlwdmFuaXNoLmNvbYIJAMYKzSS8uPKDMA0GCSqGSIb3DQEBDQUAA4IBAQCc9JV7IR8BfBrF/BQTXg0SZMZyyMAxR2jfW9qMHKSeJuZVVjfHiqoynEgBCNbn71wZWv3OF/Thu9BJ4GiYJ2Bc9nIa90D1NGYgiOVYLGXfUUqy5FgfrsWh0Go5oYm9l7W9pWfIifwsaZynkY0rTIHn32FF0H3+wZrGrEUzVL6qi+KD8iR3cBbLT+xUzulMTBp4JYaQnxpV4fZNS0ZsNrWKFWz4Iz1SSBcsnvUhfWs1aKx4yOJQx33Pc+KwpUI+meTlMjoh+AoTriooKU2MbOqLQl32y3pR0MP3fX4HDVFRylxdckEc+VryGNHQLUJiIBKBCORih/YiRhtEhpoBxmkw"}, //nolint:lll
MssFix: 1320,
ExtraLines: []string{
"comp-lzo", // Explicitly disable compression
},
}
return utils.OpenVPNConfig(providerSettings, connection, settings, ipv6Supported)
}

View File

@@ -2,6 +2,7 @@ package wireguard
import (
"fmt"
"strings"
"github.com/qdm12/gluetun/internal/netlink"
)
@@ -16,6 +17,10 @@ func (w *Wireguard) addRule(rulePriority int, firewallMark uint32,
rule.Table = int(firewallMark)
rule.Family = family
if err := w.netlink.RuleAdd(rule); err != nil {
if strings.HasSuffix(err.Error(), "file exists") {
w.logger.Info("if you are using Kubernetes, this may fix the error below: " +
"https://github.com/qdm12/gluetun-wiki/blob/main/setup/advanced/kubernetes.md#adding-ipv6-rule--file-exists")
}
return nil, fmt.Errorf("adding %s: %w", rule, err)
}