Full Code of qdm12/gluetun for AI

master 5e6c11b04587 cached
796 files
9.3 MB
2.5M tokens
2510 symbols
1 requests
Download .txt
Showing preview only (9,936K chars total). Download the full file or copy to clipboard to get everything.
Repository: qdm12/gluetun
Branch: master
Commit: 5e6c11b04587
Files: 796
Total size: 9.3 MB

Directory structure:
gitextract_eckrvmuz/

├── .devcontainer/
│   ├── .dockerignore
│   ├── Dockerfile
│   ├── README.md
│   └── devcontainer.json
├── .dockerignore
├── .github/
│   ├── CODEOWNERS
│   ├── CONTRIBUTING.md
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug.yml
│   │   ├── config.yml
│   │   ├── feature_request.yml
│   │   └── provider.md
│   ├── dependabot.yml
│   ├── labels.yml
│   ├── pull_request_template.md
│   └── workflows/
│       ├── ci-skip.yml
│       ├── ci.yml
│       ├── closed-issue.yml
│       ├── configs/
│       │   └── mlc-config.json
│       ├── labels.yml
│       ├── markdown-skip.yml
│       ├── markdown.yml
│       ├── opened-issue.yml
│       └── update-servers-list.yml
├── .gitignore
├── .golangci.yml
├── .markdownlint-cli2.jsonc
├── .vscode/
│   ├── extensions.json
│   ├── settings.json
│   └── tasks.json
├── Dockerfile
├── LICENSE
├── README.md
├── ci/
│   ├── cmd/
│   │   └── main.go
│   ├── go.mod
│   ├── go.sum
│   └── internal/
│       ├── mullvad.go
│       ├── protonvpn.go
│       ├── secrets.go
│       └── simple.go
├── cmd/
│   └── gluetun/
│       └── main.go
├── go.mod
├── go.sum
├── internal/
│   ├── alpine/
│   │   ├── alpine.go
│   │   ├── users.go
│   │   └── version.go
│   ├── amneziawg/
│   │   ├── constructor.go
│   │   ├── constructor_test.go
│   │   ├── helpers_test.go
│   │   ├── log.go
│   │   ├── log_mock_test.go
│   │   ├── netlinker.go
│   │   ├── netlinker_mock_test.go
│   │   ├── run.go
│   │   └── settings.go
│   ├── boringpoll/
│   │   ├── boringpoll.go
│   │   └── interfaces.go
│   ├── cleanup/
│   │   ├── cleanup.go
│   │   ├── cleanup_test.go
│   │   ├── interfaces.go
│   │   ├── mocks_generate_test.go
│   │   └── mocks_test.go
│   ├── cli/
│   │   ├── ci.go
│   │   ├── cli.go
│   │   ├── clientkey.go
│   │   ├── formatservers.go
│   │   ├── genkey.go
│   │   ├── healthcheck.go
│   │   ├── interfaces.go
│   │   ├── nooplogger.go
│   │   ├── openvpnconfig.go
│   │   ├── update.go
│   │   └── warner.go
│   ├── command/
│   │   ├── cmder.go
│   │   ├── interfaces.go
│   │   ├── interfaces_local.go
│   │   ├── mocks_generate_test.go
│   │   ├── mocks_local_test.go
│   │   ├── run.go
│   │   ├── run_test.go
│   │   ├── split.go
│   │   ├── split_test.go
│   │   ├── start.go
│   │   ├── start_test.go
│   │   └── startnlog.go
│   ├── configuration/
│   │   ├── settings/
│   │   │   ├── amneziawg.go
│   │   │   ├── boringpoll.go
│   │   │   ├── deprecated.go
│   │   │   ├── dns.go
│   │   │   ├── dns_test.go
│   │   │   ├── dnsblacklist.go
│   │   │   ├── errors.go
│   │   │   ├── firewall.go
│   │   │   ├── firewall_test.go
│   │   │   ├── health.go
│   │   │   ├── helpers/
│   │   │   │   └── belong.go
│   │   │   ├── helpers.go
│   │   │   ├── helpers_test.go
│   │   │   ├── httpproxy.go
│   │   │   ├── interfaces.go
│   │   │   ├── iptables.go
│   │   │   ├── log.go
│   │   │   ├── mocks_generate_test.go
│   │   │   ├── mocks_reader_test.go
│   │   │   ├── mocks_test.go
│   │   │   ├── nordvpn_retro.go
│   │   │   ├── openvpn.go
│   │   │   ├── openvpn_test.go
│   │   │   ├── openvpnselection.go
│   │   │   ├── pmtud.go
│   │   │   ├── portforward.go
│   │   │   ├── portforward_test.go
│   │   │   ├── provider.go
│   │   │   ├── publicip.go
│   │   │   ├── publicip_test.go
│   │   │   ├── server.go
│   │   │   ├── serverselection.go
│   │   │   ├── settings.go
│   │   │   ├── settings_test.go
│   │   │   ├── shadowsocks.go
│   │   │   ├── storage.go
│   │   │   ├── surfshark_retro.go
│   │   │   ├── system.go
│   │   │   ├── updater.go
│   │   │   ├── validation/
│   │   │   │   ├── servers.go
│   │   │   │   └── surfshark.go
│   │   │   ├── version.go
│   │   │   ├── vpn.go
│   │   │   ├── wireguard.go
│   │   │   └── wireguardselection.go
│   │   └── sources/
│   │       ├── files/
│   │       │   ├── amneziawg.go
│   │       │   ├── amneziawg_test.go
│   │       │   ├── helpers.go
│   │       │   ├── interfaces.go
│   │       │   ├── reader.go
│   │       │   ├── wireguard.go
│   │       │   └── wireguard_test.go
│   │       └── secrets/
│   │           ├── amneziawg.go
│   │           ├── helpers.go
│   │           ├── interfaces.go
│   │           ├── reader.go
│   │           ├── reader_test.go
│   │           └── wireguard.go
│   ├── constants/
│   │   ├── colors.go
│   │   ├── countries.go
│   │   ├── openvpn/
│   │   │   ├── auth.go
│   │   │   ├── ciphers.go
│   │   │   ├── paths.go
│   │   │   └── versions.go
│   │   ├── paths.go
│   │   ├── protocol.go
│   │   ├── providers/
│   │   │   ├── providers.go
│   │   │   └── providers_test.go
│   │   ├── status.go
│   │   └── vpn/
│   │       └── protocol.go
│   ├── dns/
│   │   ├── leak.go
│   │   ├── leak_test.go
│   │   ├── logger.go
│   │   ├── loop.go
│   │   ├── plaintext.go
│   │   ├── run.go
│   │   ├── settings.go
│   │   ├── setup.go
│   │   ├── state/
│   │   │   ├── settings.go
│   │   │   └── state.go
│   │   ├── status.go
│   │   ├── ticker.go
│   │   └── update.go
│   ├── firewall/
│   │   ├── enable.go
│   │   ├── firewall.go
│   │   ├── interfaces.go
│   │   ├── iptables/
│   │   │   ├── atomic.go
│   │   │   ├── cmd_matcher_test.go
│   │   │   ├── delete.go
│   │   │   ├── delete_test.go
│   │   │   ├── firewall.go
│   │   │   ├── interfaces.go
│   │   │   ├── ip6tables.go
│   │   │   ├── iptables.go
│   │   │   ├── iptablesmix.go
│   │   │   ├── list.go
│   │   │   ├── list_test.go
│   │   │   ├── mocks_generate_test.go
│   │   │   ├── mocks_test.go
│   │   │   ├── parse.go
│   │   │   ├── parse_test.go
│   │   │   ├── support.go
│   │   │   ├── support_test.go
│   │   │   └── tcp.go
│   │   ├── logger.go
│   │   ├── outboundsubnets.go
│   │   ├── ports.go
│   │   ├── redirect.go
│   │   ├── vpn.go
│   │   └── wrappers.go
│   ├── format/
│   │   ├── duration.go
│   │   └── duration_test.go
│   ├── healthcheck/
│   │   ├── checker.go
│   │   ├── checker_test.go
│   │   ├── client.go
│   │   ├── dns/
│   │   │   └── dns.go
│   │   ├── handler.go
│   │   ├── icmp/
│   │   │   ├── apple_ipv4.go
│   │   │   ├── echo.go
│   │   │   ├── interfaces.go
│   │   │   └── listen.go
│   │   ├── interfaces.go
│   │   ├── run.go
│   │   └── server.go
│   ├── httpproxy/
│   │   ├── accept.go
│   │   ├── auth.go
│   │   ├── handler.go
│   │   ├── handler_test.go
│   │   ├── http.go
│   │   ├── https.go
│   │   ├── logger.go
│   │   ├── loop.go
│   │   ├── run.go
│   │   ├── server.go
│   │   ├── settings.go
│   │   ├── state/
│   │   │   ├── settings.go
│   │   │   └── state.go
│   │   └── status.go
│   ├── httpserver/
│   │   ├── address.go
│   │   ├── helpers_test.go
│   │   ├── logger.go
│   │   ├── logger_mock_test.go
│   │   ├── run.go
│   │   ├── run_test.go
│   │   ├── server.go
│   │   ├── server_test.go
│   │   ├── settings.go
│   │   └── settings_test.go
│   ├── loopstate/
│   │   ├── apply.go
│   │   ├── get.go
│   │   ├── lock.go
│   │   ├── set.go
│   │   └── state.go
│   ├── mod/
│   │   ├── configgz_linux.go
│   │   ├── info_linux.go
│   │   ├── load_linux.go
│   │   ├── probe_linux.go
│   │   └── probe_unspecified.go
│   ├── models/
│   │   ├── alias.go
│   │   ├── build.go
│   │   ├── connection.go
│   │   ├── filters.go
│   │   ├── markdown.go
│   │   ├── markdown_test.go
│   │   ├── publicip.go
│   │   ├── server.go
│   │   ├── server_test.go
│   │   ├── servers.go
│   │   ├── servers_test.go
│   │   └── sort.go
│   ├── natpmp/
│   │   ├── checks.go
│   │   ├── checks_test.go
│   │   ├── externaladdress.go
│   │   ├── externaladdress_test.go
│   │   ├── helpers_test.go
│   │   ├── natpmp.go
│   │   ├── natpmp_test.go
│   │   ├── portmapping.go
│   │   ├── portmapping_test.go
│   │   ├── rpc.go
│   │   └── rpc_test.go
│   ├── netlink/
│   │   ├── address.go
│   │   ├── conntrack_linux.go
│   │   ├── conntrack_unspecified.go
│   │   ├── conversion.go
│   │   ├── conversion_test.go
│   │   ├── family.go
│   │   ├── family_linux.go
│   │   ├── helpers_test.go
│   │   ├── interfaces.go
│   │   ├── ipv6.go
│   │   ├── link.go
│   │   ├── link_linux.go
│   │   ├── link_test.go
│   │   ├── netlink.go
│   │   ├── netlink_unspecified.go
│   │   ├── route.go
│   │   ├── route_linux.go
│   │   ├── rule.go
│   │   ├── rule_linux.go
│   │   ├── rule_test.go
│   │   ├── wireguard_linux.go
│   │   └── wireguard_test.go
│   ├── openvpn/
│   │   ├── auth.go
│   │   ├── config.go
│   │   ├── extract/
│   │   │   ├── data.go
│   │   │   ├── extract.go
│   │   │   ├── extract_test.go
│   │   │   ├── extractor.go
│   │   │   ├── helpers_test.go
│   │   │   ├── pem.go
│   │   │   ├── pem_test.go
│   │   │   ├── read.go
│   │   │   └── read_test.go
│   │   ├── interfaces.go
│   │   ├── logger.go
│   │   ├── logs.go
│   │   ├── logs_test.go
│   │   ├── openvpn.go
│   │   ├── paths.go
│   │   ├── pkcs8/
│   │   │   ├── algorithms.go
│   │   │   ├── algorithms_test.go
│   │   │   ├── descbc.go
│   │   │   ├── testdata/
│   │   │   │   ├── readme.txt
│   │   │   │   ├── rsa_pkcs8_aes128cbc_decrypted.pem
│   │   │   │   ├── rsa_pkcs8_aes128cbc_encrypted.pem
│   │   │   │   ├── rsa_pkcs8_descbc_decrypted.pem
│   │   │   │   └── rsa_pkcs8_descbc_encrypted.pem
│   │   │   ├── upgrade.go
│   │   │   └── upgrade_test.go
│   │   ├── run.go
│   │   ├── start.go
│   │   ├── start_linux.go
│   │   ├── start_unspecified.go
│   │   ├── stream.go
│   │   └── version.go
│   ├── pmtud/
│   │   ├── constants/
│   │   │   ├── lengths.go
│   │   │   ├── syscall_unix.go
│   │   │   └── syscall_windows.go
│   │   ├── icmp/
│   │   │   ├── apple_ipv4.go
│   │   │   ├── check.go
│   │   │   ├── df_linux.go
│   │   │   ├── df_unspecified.go
│   │   │   ├── df_windows.go
│   │   │   ├── errors.go
│   │   │   ├── icmp.go
│   │   │   ├── interfaces.go
│   │   │   ├── ipv4.go
│   │   │   ├── ipv6.go
│   │   │   ├── message.go
│   │   │   └── multi.go
│   │   ├── interfaces.go
│   │   ├── ip/
│   │   │   ├── family.go
│   │   │   ├── ipheader.go
│   │   │   ├── ipheader_darwin.go
│   │   │   ├── ipheader_unspecified.go
│   │   │   ├── ipv4_unix.go
│   │   │   ├── ipv4_unspecified.go
│   │   │   ├── ipv4_windows.go
│   │   │   ├── source.go
│   │   │   ├── source_unix.go
│   │   │   └── source_windows.go
│   │   ├── nooplogger.go
│   │   ├── pmtud.go
│   │   ├── pmtud_integration_test.go
│   │   ├── tcp/
│   │   │   ├── helpers_test.go
│   │   │   ├── interfaces.go
│   │   │   ├── mocks_generate_test.go
│   │   │   ├── mocks_test.go
│   │   │   ├── mss.go
│   │   │   ├── mss_test.go
│   │   │   ├── multi.go
│   │   │   ├── packet.go
│   │   │   ├── tcp.go
│   │   │   ├── tcp_darwin.go
│   │   │   ├── tcp_integration_test.go
│   │   │   ├── tcp_linux.go
│   │   │   ├── tcp_notdarwin.go
│   │   │   ├── tcp_test.go
│   │   │   ├── tcp_unix.go
│   │   │   ├── tcp_unspecified.go
│   │   │   ├── tcp_windows.go
│   │   │   ├── tcpheader.go
│   │   │   └── tracker.go
│   │   ├── test/
│   │   │   ├── mtu.go
│   │   │   └── mtu_test.go
│   │   └── vpn.go
│   ├── portforward/
│   │   ├── interfaces.go
│   │   ├── loop.go
│   │   ├── service/
│   │   │   ├── command.go
│   │   │   ├── command_test.go
│   │   │   ├── fs.go
│   │   │   ├── helpers.go
│   │   │   ├── helpers_test.go
│   │   │   ├── interfaces.go
│   │   │   ├── mocks_generate_test.go
│   │   │   ├── mocks_test.go
│   │   │   ├── service.go
│   │   │   ├── settings.go
│   │   │   ├── start.go
│   │   │   └── stop.go
│   │   └── settings.go
│   ├── pprof/
│   │   ├── helpers_test.go
│   │   ├── logger_mock_test.go
│   │   ├── server.go
│   │   ├── server_test.go
│   │   ├── settings.go
│   │   └── settings_test.go
│   ├── provider/
│   │   ├── airvpn/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── api.go
│   │   │       ├── servers.go
│   │   │       └── updater.go
│   │   ├── common/
│   │   │   ├── mocks.go
│   │   │   ├── mocks_generate_test.go
│   │   │   ├── portforward.go
│   │   │   ├── storage.go
│   │   │   └── updater.go
│   │   ├── custom/
│   │   │   ├── connection.go
│   │   │   ├── interfaces.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── openvpnconf_test.go
│   │   │   └── provider.go
│   │   ├── cyberghost/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── constants.go
│   │   │       ├── countries.go
│   │   │       ├── hosttoserver.go
│   │   │       ├── resolve.go
│   │   │       ├── servers.go
│   │   │       └── updater.go
│   │   ├── example/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── api.go
│   │   │       ├── resolve.go
│   │   │       ├── servers.go
│   │   │       └── updater.go
│   │   ├── expressvpn/
│   │   │   ├── connection.go
│   │   │   ├── connection_test.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── hardcoded.go
│   │   │       ├── resolve.go
│   │   │       ├── servers.go
│   │   │       └── updater.go
│   │   ├── fastestvpn/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── api.go
│   │   │       ├── api_test.go
│   │   │       ├── hosttoserver.go
│   │   │       ├── resolve.go
│   │   │       ├── servers.go
│   │   │       └── updater.go
│   │   ├── giganews/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── filename.go
│   │   │       ├── hosttoserver.go
│   │   │       ├── resolve.go
│   │   │       ├── servers.go
│   │   │       └── updater.go
│   │   ├── hidemyass/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── hosts.go
│   │   │       ├── hosttourl.go
│   │   │       ├── index.go
│   │   │       ├── resolve.go
│   │   │       ├── servers.go
│   │   │       ├── updater.go
│   │   │       └── url.go
│   │   ├── ipvanish/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── filename.go
│   │   │       ├── filename_test.go
│   │   │       ├── hosttoserver.go
│   │   │       ├── hosttoserver_test.go
│   │   │       ├── resolve.go
│   │   │       ├── servers.go
│   │   │       ├── servers_test.go
│   │   │       └── updater.go
│   │   ├── ivpn/
│   │   │   ├── connection.go
│   │   │   ├── connection_test.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── api.go
│   │   │       ├── api_test.go
│   │   │       ├── resolve.go
│   │   │       ├── roundtrip_test.go
│   │   │       ├── servers.go
│   │   │       ├── servers_test.go
│   │   │       └── updater.go
│   │   ├── mullvad/
│   │   │   ├── connection.go
│   │   │   ├── connection_test.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── api.go
│   │   │       ├── hosttoserver.go
│   │   │       ├── ips.go
│   │   │       ├── ips_test.go
│   │   │       ├── servers.go
│   │   │       └── updater.go
│   │   ├── nordvpn/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── api.go
│   │   │       ├── models.go
│   │   │       ├── name.go
│   │   │       ├── servers.go
│   │   │       └── updater.go
│   │   ├── perfectprivacy/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── portforward.go
│   │   │   ├── portforward_test.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── citytoserver.go
│   │   │       ├── filename.go
│   │   │       ├── servers.go
│   │   │       └── updater.go
│   │   ├── privado/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── servers.go
│   │   │       └── updater.go
│   │   ├── privateinternetaccess/
│   │   │   ├── connection.go
│   │   │   ├── httpclient.go
│   │   │   ├── httpclient_test.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── portforward.go
│   │   │   ├── portforward_test.go
│   │   │   ├── presets/
│   │   │   │   └── presets.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── api.go
│   │   │       ├── hosttoserver.go
│   │   │       ├── servers.go
│   │   │       └── updater.go
│   │   ├── privatevpn/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── portforward.go
│   │   │   ├── portforward_test.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── countries.go
│   │   │       ├── filename.go
│   │   │       ├── hosttoserver.go
│   │   │       ├── resolve.go
│   │   │       ├── servers.go
│   │   │       └── updater.go
│   │   ├── protonvpn/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── portforward.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── api.go
│   │   │       ├── countries.go
│   │   │       ├── iptoserver.go
│   │   │       ├── servers.go
│   │   │       ├── updater.go
│   │   │       └── version.go
│   │   ├── provider.go
│   │   ├── providers.go
│   │   ├── purevpn/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── compare.go
│   │   │       ├── compare_test.go
│   │   │       ├── hosttoserver.go
│   │   │       ├── parse.go
│   │   │       ├── resolve.go
│   │   │       ├── servers.go
│   │   │       └── updater.go
│   │   ├── slickvpn/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── resolve.go
│   │   │       ├── servers.go
│   │   │       └── updater.go
│   │   ├── surfshark/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   ├── servers/
│   │   │   │   └── locationdata.go
│   │   │   └── updater/
│   │   │       ├── api.go
│   │   │       ├── api_test.go
│   │   │       ├── hosttoserver.go
│   │   │       ├── location.go
│   │   │       ├── remaining.go
│   │   │       ├── resolve.go
│   │   │       ├── roundtrip_test.go
│   │   │       ├── servers.go
│   │   │       ├── updater.go
│   │   │       └── zip.go
│   │   ├── torguard/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── filename.go
│   │   │       ├── hosttoserver.go
│   │   │       ├── resolve.go
│   │   │       ├── servers.go
│   │   │       └── updater.go
│   │   ├── utils/
│   │   │   ├── cipher.go
│   │   │   ├── cipher_test.go
│   │   │   ├── connection.go
│   │   │   ├── connection_test.go
│   │   │   ├── filtering.go
│   │   │   ├── filtering_test.go
│   │   │   ├── logger.go
│   │   │   ├── nofetcher.go
│   │   │   ├── openvpn.go
│   │   │   ├── pick.go
│   │   │   ├── pick_test.go
│   │   │   ├── port.go
│   │   │   ├── port_test.go
│   │   │   ├── portforward.go
│   │   │   ├── protocol.go
│   │   │   └── protocol_test.go
│   │   ├── vpnsecure/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── helpers_test.go
│   │   │       ├── hosttoserver.go
│   │   │       ├── resolve.go
│   │   │       ├── servers.go
│   │   │       ├── testdata/
│   │   │       │   └── index.html
│   │   │       ├── updater.go
│   │   │       ├── website.go
│   │   │       └── website_test.go
│   │   ├── vpnunlimited/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── constants.go
│   │   │       ├── hosttoserver.go
│   │   │       ├── resolve.go
│   │   │       ├── servers.go
│   │   │       └── updater.go
│   │   ├── vyprvpn/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── filename.go
│   │   │       ├── hosttoserver.go
│   │   │       ├── resolve.go
│   │   │       ├── servers.go
│   │   │       └── updater.go
│   │   └── windscribe/
│   │       ├── connection.go
│   │       ├── connection_test.go
│   │       ├── openvpnconf.go
│   │       ├── provider.go
│   │       └── updater/
│   │           ├── api.go
│   │           ├── servers.go
│   │           └── updater.go
│   ├── publicip/
│   │   ├── api/
│   │   │   ├── api.go
│   │   │   ├── api_test.go
│   │   │   ├── cloudflare.go
│   │   │   ├── echoip.go
│   │   │   ├── errors.go
│   │   │   ├── interfaces.go
│   │   │   ├── ip2location.go
│   │   │   ├── ipinfo.go
│   │   │   ├── multi.go
│   │   │   ├── resilient.go
│   │   │   └── resilient_test.go
│   │   ├── data.go
│   │   ├── fs.go
│   │   ├── interfaces.go
│   │   ├── loop.go
│   │   └── update.go
│   ├── routing/
│   │   ├── conversion.go
│   │   ├── conversion_test.go
│   │   ├── default.go
│   │   ├── enable.go
│   │   ├── errors.go
│   │   ├── inbound.go
│   │   ├── ip.go
│   │   ├── ip_test.go
│   │   ├── local.go
│   │   ├── logger.go
│   │   ├── mocks_generate_test.go
│   │   ├── mocks_test.go
│   │   ├── outbound.go
│   │   ├── routes.go
│   │   ├── routing.go
│   │   ├── rules.go
│   │   ├── rules_test.go
│   │   ├── tables_linux.go
│   │   ├── tables_unspecified.go
│   │   └── vpn.go
│   ├── server/
│   │   ├── dns.go
│   │   ├── handler.go
│   │   ├── handlerv0.go
│   │   ├── handlerv1.go
│   │   ├── helpers.go
│   │   ├── interfaces.go
│   │   ├── logger.go
│   │   ├── middlewares/
│   │   │   ├── auth/
│   │   │   │   ├── apikey.go
│   │   │   │   ├── basic.go
│   │   │   │   ├── configfile.go
│   │   │   │   ├── configfile_test.go
│   │   │   │   ├── format.go
│   │   │   │   ├── interfaces.go
│   │   │   │   ├── interfaces_local.go
│   │   │   │   ├── lookup.go
│   │   │   │   ├── lookup_test.go
│   │   │   │   ├── middleware.go
│   │   │   │   ├── middleware_test.go
│   │   │   │   ├── mocks_generate_test.go
│   │   │   │   ├── mocks_test.go
│   │   │   │   ├── none.go
│   │   │   │   └── settings.go
│   │   │   └── log/
│   │   │       ├── interfaces.go
│   │   │       └── middleware.go
│   │   ├── openvpn.go
│   │   ├── portforward.go
│   │   ├── publicip.go
│   │   ├── server.go
│   │   ├── updater.go
│   │   ├── vpn.go
│   │   └── wrappers.go
│   ├── shadowsocks/
│   │   ├── logger.go
│   │   ├── loop.go
│   │   └── state.go
│   ├── storage/
│   │   ├── choices.go
│   │   ├── copy.go
│   │   ├── copy_test.go
│   │   ├── filter.go
│   │   ├── flush.go
│   │   ├── formatting.go
│   │   ├── hardcoded.go
│   │   ├── hardcoded_test.go
│   │   ├── helpers.go
│   │   ├── merge.go
│   │   ├── mocks_generate_test.go
│   │   ├── mocks_test.go
│   │   ├── read.go
│   │   ├── read_test.go
│   │   ├── servers.go
│   │   ├── servers.json
│   │   ├── storage.go
│   │   └── sync.go
│   ├── subnet/
│   │   └── subsets.go
│   ├── tun/
│   │   ├── check.go
│   │   ├── check_unspecified.go
│   │   ├── create.go
│   │   ├── create_unspecified.go
│   │   ├── tun.go
│   │   └── tun_test.go
│   ├── updater/
│   │   ├── html/
│   │   │   ├── attribute.go
│   │   │   ├── bfs.go
│   │   │   ├── css.go
│   │   │   ├── errors.go
│   │   │   ├── fetch.go
│   │   │   ├── fetch_test.go
│   │   │   └── match.go
│   │   ├── interfaces.go
│   │   ├── loop/
│   │   │   ├── loop.go
│   │   │   └── state.go
│   │   ├── openvpn/
│   │   │   ├── extract.go
│   │   │   ├── fetch.go
│   │   │   └── multifetch.go
│   │   ├── providers.go
│   │   ├── resolver/
│   │   │   ├── interfaces.go
│   │   │   ├── ips.go
│   │   │   ├── ips_test.go
│   │   │   ├── net.go
│   │   │   ├── parallel.go
│   │   │   └── repeat.go
│   │   ├── unzip/
│   │   │   ├── extract.go
│   │   │   ├── fetch.go
│   │   │   └── unzip.go
│   │   └── updater.go
│   ├── version/
│   │   ├── github.go
│   │   └── version.go
│   ├── vpn/
│   │   ├── amneziawg.go
│   │   ├── cleanup.go
│   │   ├── helpers.go
│   │   ├── interfaces.go
│   │   ├── loop.go
│   │   ├── openvpn.go
│   │   ├── portforward.go
│   │   ├── run.go
│   │   ├── settings.go
│   │   ├── state/
│   │   │   ├── state.go
│   │   │   └── vpn.go
│   │   ├── status.go
│   │   ├── tunnelup.go
│   │   ├── wireguard.go
│   │   └── wireguard_test.go
│   └── wireguard/
│       ├── address.go
│       ├── address_test.go
│       ├── config.go
│       ├── config_test.go
│       ├── constructor.go
│       ├── constructor_test.go
│       ├── helpers_test.go
│       ├── log.go
│       ├── log_mock_test.go
│       ├── log_test.go
│       ├── netlink_integration_test.go
│       ├── netlinker.go
│       ├── netlinker_mock_test.go
│       ├── route.go
│       ├── route_test.go
│       ├── rule.go
│       ├── rule_test.go
│       ├── run.go
│       ├── settings.go
│       ├── settings_test.go
│       ├── wireguard_linux.go
│       └── wireguard_unspecified.go
└── maintenance.md

================================================
FILE CONTENTS
================================================

================================================
FILE: .devcontainer/.dockerignore
================================================
.dockerignore
devcontainer.json
Dockerfile
README.md


================================================
FILE: .devcontainer/Dockerfile
================================================
FROM ghcr.io/qdm12/godevcontainer:v0.21-alpine
RUN apk add wireguard-tools htop openssl tcpdump iptables


================================================
FILE: .devcontainer/README.md
================================================
# Development container

Development container that can be used with VSCode.

It works on Linux, Windows (WSL2) and OSX.

## Requirements

- [VS code](https://code.visualstudio.com/download) installed
- [VS code dev containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) installed
- [Docker](https://www.docker.com/products/docker-desktop) installed and running

## Setup

1. Create the following files and directory on your host if you don't have them:

    ```sh
    touch ~/.gitconfig ~/.zsh_history
    mkdir -p ~/.ssh
    ```

1. **For OSX hosts**: ensure the project directory and your home directory `~` are accessible by Docker.
1. Open the command palette in Visual Studio Code (CTRL+SHIFT+P).
1. Select `Dev-Containers: Open Folder in Container...` and choose the project directory.

## Customization

For any customization to take effect, you should "rebuild and reopen":

1. Open the command palette in Visual Studio Code (CTRL+SHIFT+P)
2. Select `Dev-Containers: Rebuild Container`

Changes you can make are notably:

- Changes to the Docker image in [Dockerfile](Dockerfile)
- Changes to VSCode **settings** and **extensions** in [devcontainer.json](devcontainer.json).
- Change the entrypoint script by adding a bind mount in [devcontainer.json](devcontainer.json) of a shell script to `/root/.welcome.sh` to replace the [current welcome script](https://github.com/qdm12/godevcontainer/blob/master/shell/.welcome.sh). For example:

    ```json
    // Welcome script
    {
        "source": "/yourpath/.welcome.sh",
        "target": "/root/.welcome.sh",
        "type": "bind"
    },
    ```

- More options are documented in the [devcontainer.json reference](https://containers.dev/implementors/json_reference/).


================================================
FILE: .devcontainer/devcontainer.json
================================================
{
    "name": "gluetun-dev",
    // User defined settings
    "containerEnv": {
        "TZ": ""
    },
    // Fixed settings
    "build": {
        "dockerfile": "./Dockerfile"
    },
    "postCreateCommand": "~/.windows.sh && go mod download",
    "capAdd": [
        "NET_ADMIN", // Gluetun specific
        "SYS_PTRACE" // for dlv Go debugging
    ],
    "securityOpt": [
        "seccomp=unconfined" // for dlv Go debugging
    ],
    "mounts": [
        // Zsh commands history persistence
        {
            "source": "${localEnv:HOME}/.zsh_history",
            "target": "/root/.zsh_history",
            "type": "bind"
        },
        // Git configuration file
        {
            "source": "${localEnv:HOME}/.gitconfig",
            "target": "/root/.gitconfig",
            "type": "bind"
        },
        // SSH directory for Linux, OSX and WSL
        // On Linux and OSX, a symlink /mnt/ssh <-> ~/.ssh is
        // created in the container. On Windows, files are copied
        // from /mnt/ssh to ~/.ssh to fix permissions.
        {
            "source": "${localEnv:HOME}/.ssh",
            "target": "/mnt/ssh",
            "type": "bind"
        },
        // Docker socket to access the host Docker server
        {
            "source": "/var/run/docker.sock",
            "target": "/var/run/docker.sock",
            "type": "bind"
        }
    ],
    "customizations": {
        "vscode": {
            "extensions": [
                "golang.go",
                "eamodio.gitlens", // IDE Git information
                "davidanson.vscode-markdownlint",
                "ms-azuretools.vscode-docker", // Docker integration and linting
                "shardulm94.trailing-spaces", // Show trailing spaces
                "Gruntfuggly.todo-tree", // Highlights TODO comments
                "bierner.emojisense", // Emoji sense for markdown
                "stkb.rewrap", // rewrap comments after n characters on one line
                "vscode-icons-team.vscode-icons", // Better file extension icons
                "github.vscode-pull-request-github", // Github interaction
                "redhat.vscode-yaml", // Kubernetes, Drone syntax highlighting
                "bajdzis.vscode-database", // Supports connections to mysql or postgres, over SSL, socked
                "IBM.output-colorizer", // Colorize your output/test logs
                "github.copilot" // AI code completion
            ],
            "settings": {
                "files.eol": "\n",
                "remote.extensionKind": {
                    "ms-azuretools.vscode-docker": "workspace"
                },
                "go.useLanguageServer": true,
                "[go]": {
                    "editor.codeActionsOnSave": {
                        "source.organizeImports": "explicit"
                    }
                },
                "[go.mod]": {
                    "editor.codeActionsOnSave": {
                        "source.organizeImports": "explicit"
                    }
                },
                "gopls": {
                    "usePlaceholders": false,
                    "staticcheck": true,
                    "ui.diagnostic.analyses": {
                        "ST1000": false
                    },
                    "formatting.gofumpt": true,
                },
                "go.lintTool": "golangci-lint",
                "go.lintOnSave": "package",
                "editor.formatOnSave": true,
                "go.buildTags": "linux",
                "go.toolsEnvVars": {
                    "CGO_ENABLED": "0"
                },
                "go.testEnvVars": {
                    "CGO_ENABLED": "1"
                },
                "go.testFlags": [
                    "-v",
                    "-race"
                ],
                "go.testTimeout": "10s",
                "go.coverOnSingleTest": true,
                "go.coverOnSingleTestFile": true,
                "go.coverOnTestPackage": true
            }
        }
    }
}

================================================
FILE: .dockerignore
================================================
.devcontainer
.git
.github
doc
docker-compose.yml
Dockerfile
LICENSE
README.md
title.svg


================================================
FILE: .github/CODEOWNERS
================================================
@qdm12

================================================
FILE: .github/CONTRIBUTING.md
================================================
# Contributing

Contributions are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [open source license of this project](../LICENSE).

## Submitting a pull request

1. [Fork](https://github.com/qdm12/gluetun/fork) and clone the repository
1. Create a new branch `git checkout -b my-branch-name`
1. Modify the code
1. Ensure the docker build succeeds `docker build .` (you might need `export DOCKER_BUILDKIT=1`)
1. Commit your modifications
1. Push to your fork and [submit a pull request](https://github.com/qdm12/gluetun/compare)

## Resources

- [Gluetun guide on development](https://github.com/qdm12/gluetun-wiki/blob/main/contributing/development.md)
- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/)
- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)


================================================
FILE: .github/FUNDING.yml
================================================
github: [qdm12]


================================================
FILE: .github/ISSUE_TEMPLATE/bug.yml
================================================
name: Bug
description: Report a bug
title: "Bug: "
labels: [":bug: bug"]
body:
  - type: markdown
    attributes:
      value: |
        Thanks for taking the time to fill out this bug report!

        ⚠️ Your issue will be instantly closed as not planned WITHOUT explanation if:
        - you do not fill out **the title of the issue** ☝️
        - you do not provide the **Gluetun version** as requested below
        - you provide **less than 10 lines of logs** as requested below
  - type: dropdown
    id: urgent
    attributes:
      label: Is this urgent?
      description: |
        Is this a critical bug, or do you need this fixed urgently?
        If this is urgent, note you can use one of the [image tags available](https://github.com/qdm12/gluetun-wiki/blob/main/setup/docker-image-tags.md) if that can help.
      options:
        - "No"
        - "Yes"
  - type: input
    id: host-os
    attributes:
      label: Host OS
      description: What is your host OS?
      placeholder: "Debian Buster"
  - type: dropdown
    id: cpu-arch
    attributes:
      label: CPU arch
      description: You can find it on Linux with `uname -m`.
      options:
        - x86_64
        - aarch64
        - armv7l
        - "386"
        - s390x
        - ppc64le
  - type: dropdown
    id: vpn-service-provider
    attributes:
      label: VPN service provider
      options:
        - AirVPN
        - Custom
        - Cyberghost
        - ExpressVPN
        - FastestVPN
        - Giganews
        - HideMyAss
        - IPVanish
        - IVPN
        - Mullvad
        - NordVPN
        - Privado
        - Private Internet Access
        - PrivateVPN
        - ProtonVPN
        - PureVPN
        - SlickVPN
        - Surfshark
        - TorGuard
        - VPNSecure.me
        - VPNUnlimited
        - VyprVPN
        - Windscribe
    validations:
      required: true
  - type: dropdown
    id: docker
    attributes:
      label: What are you using to run the container
      options:
        - docker run
        - docker-compose
        - Portainer
        - Kubernetes
        - Podman
        - Unraid
        - Other
    validations:
      required: true
  - type: input
    id: version
    attributes:
      label: What is the version of Gluetun
      description: |
        Copy paste the version line at the top of your logs.
        It MUST be in the form `Running version latest built on 2020-03-13T01:30:06Z (commit d0f678c)`.
    validations:
      required: true
  - type: textarea
    id: problem
    attributes:
      label: "What's the problem 🤔"
      placeholder: "That feature does not work..."
    validations:
      required: true
  - type: textarea
    id: logs
    attributes:
      label: Share your logs (at least 10 lines)
      description: No sensitive information is logged out except when running with `LOG_LEVEL=debug`.
      render: plain text
    validations:
      required: true
  - type: textarea
    id: config
    attributes:
      label: Share your configuration
      description: Share your configuration such as `docker-compose.yml`. Ensure to remove credentials.
      render: yml


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
  - name: Report a Wiki issue
    url: https://github.com/qdm12/gluetun-wiki/issues/new/choose
    about: Please create an issue on the gluetun-wiki repository.
  - name: Configuration help?
    url: https://github.com/qdm12/gluetun/discussions/new/choose
    about: Please create a Github discussion.
  - name: Unraid template issue
    url: https://github.com/qdm12/gluetun/discussions/550
    about: Please read the relevant Github discussion.


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: Feature request
description: Suggest a feature to add to Gluetun
title: "Feature request: "
labels: [":bulb: feature request"]
body:
  - type: textarea
    id: description
    attributes:
      label: "What's the feature 🧐"
      placeholder: "Make the tunnel resistant to earth quakes"
    validations:
      required: true
  - type: textarea
    id: extra
    attributes:
      label: "Extra information and references"
      placeholder: |
        - I tried `docker run something` and it doesn't work
        - That [url](https://github.com/qdm12/gluetun) is interesting


================================================
FILE: .github/ISSUE_TEMPLATE/provider.md
================================================
---
name: Support a VPN provider
about: Suggest a VPN provider to be supported
title: 'VPN provider support: NAME OF THE PROVIDER'
labels: ":bulb: New provider"

---

Important notes:

- There is no need to support both OpenVPN and Wireguard for a provider, but it's better to support both if possible
- We do **not** implement authentication to access servers information behind a login. This is way too time consuming unfortunately
- If it's not possible to support a provider natively, you can still use the [the custom provider](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/custom.md)

## For Wireguard

Wireguard can be natively supported ONLY if:

- the `PrivateKey` field value is the same across all servers for one user account
- the `Address` field value is:
  - can be found in a structured (JSON etc.) list of servers publicly available; OR
  - the same across all servers for one user account
- the `PublicKey` field value is:
  - can be found in a structured (JSON etc.) list of servers publicly available; OR
  - the same across all servers for one user account
- the `Endpoint` field value:
  - can be found in a structured (JSON etc.) list of servers publicly available
  - can be determined using a pattern, for example using country codes in hostnames

If any of these conditions are not met, Wireguard cannot be natively supported or there is no advantage compared to using a custom Wireguard configuration file.

If **all** of these conditions are met, please provide an answer for each of them.

## For OpenVPN

OpenVPN can be natively supported ONLY if one of the following can be provided, by preference in this order:

- Publicly accessible URL to a structured (JSON etc.) list of servers **and attach** an example Openvpn configuration file for both TCP and UDP; OR
- Publicly accessible URL to a zip file containing the Openvpn configuration files; OR
- Publicly accessible URL to the list of servers **and attach** an example Openvpn configuration file for both TCP and UDP


================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
  # Maintain dependencies for GitHub Actions
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "daily"
  - package-ecosystem: docker
    directory: /
    schedule:
      interval: "daily"
  - package-ecosystem: gomod
    directory: /
    schedule:
      interval: "daily"


================================================
FILE: .github/labels.yml
================================================
- name: "Status: 🗯️ Waiting for feedback"
  color: "f7d692"
- name: "Status: 🔴 Blocked"
  color: "f7d692"
  description: "Blocked by another issue or pull request"
- name: "Status: 📌 Before next release"
  color: "f7d692"
  description: "Has to be done before the next release"
- name: "Status: 🔒 After next release"
  color: "f7d692"
  description: "Will be done after the next release"
- name: "Status: 🟡 Nearly resolved"
  color: "f7d692"
  description: "This might be resolved or is about to be resolved"

- name: "Closed: ⚰️ Inactive"
  color: "959a9c"
  description: "No answer was received for weeks"
- name: "Closed: 👥 Duplicate"
  color: "959a9c"
  description: "Issue duplicates an existing issue"
- name: "Closed: 🗑️ Bad issue"
  color: "959a9c"
- name: "Closed: ☠️ cannot be done"
  color: "959a9c"

- name: "Priority: 🚨 Urgent"
  color: "03adfc"
- name: "Priority: 💤 Low priority"
  color: "03adfc"

- name: "Complexity: ☣️ Hard to do"
  color: "ff9efc"
- name: "Complexity: 🟩 Easy to do"
  color: "ff9efc"

- name: "Popularity: ❤️‍🔥 extreme"
  color: "ffc7ea"
- name: "Popularity: ❤️ high"
  color: "ffc7ea"

# VPN providers
- name: "☁️ AirVPN"
  color: "cfe8d4"
- name: "☁️ Custom"
  color: "cfe8d4"
- name: "☁️ Cyberghost"
  color: "cfe8d4"
- name: "☁️ Giganews"
  color: "cfe8d4"
- name: "☁️ HideMyAss"
  color: "cfe8d4"
- name: "☁️ IPVanish"
  color: "cfe8d4"
- name: "☁️ IVPN"
  color: "cfe8d4"
- name: "☁️ ExpressVPN"
  color: "cfe8d4"
- name: "☁️ FastestVPN"
  color: "cfe8d4"
- name: "☁️ Mullvad"
  color: "cfe8d4"
- name: "☁️ NordVPN"
  color: "cfe8d4"
- name: "☁️ Perfect Privacy"
  color: "cfe8d4"
- name: "☁️ PIA"
  color: "cfe8d4"
- name: "☁️ Privado"
  color: "cfe8d4"
- name: "☁️ PrivateVPN"
  color: "cfe8d4"
- name: "☁️ ProtonVPN"
  color: "cfe8d4"
- name: "☁️ PureVPN"
  color: "cfe8d4"
- name: "☁️ SlickVPN"
  color: "cfe8d4"
- name: "☁️ Surfshark"
  color: "cfe8d4"
- name: "☁️ Torguard"
  color: "cfe8d4"
- name: "☁️ VPNSecure.me"
  color: "cfe8d4"
- name: "☁️ VPNUnlimited"
  color: "cfe8d4"
- name: "☁️ Vyprvpn"
  color: "cfe8d4"
- name: "☁️ Windscribe"
  color: "cfe8d4"

- name: "Category: User error 🤦"
  from_name: "Category: Config problem 📝"
  color: "ffc7ea"
- name: "Category: Healthcheck 🩺"
  color: "ffc7ea"
- name: "Category: Documentation ✒️"
  description: "A problem with the readme or a code comment."
  color: "ffc7ea"
- name: "Category: Maintenance ⛓️"
  description: "Anything related to code or other maintenance"
  color: "ffc7ea"
- name: "Category: Logs 📚"
  description: "Something to change in logs"
  color: "ffc7ea"
- name: "Category: Good idea 🎯"
  description: "This is a good idea, judged by the maintainers"
  color: "ffc7ea"
- name: "Category: Motivated! 🙌"
  description: "Your pumpness makes me pumped! The issue or PR shows great motivation!"
  color: "ffc7ea"
- name: "Category: Foolproof settings 👼"
  color: "ffc7ea"
- name: "Category: Label missing ❗"
  color: "ffc7ea"
- name: "Category: updater ♻️"
  color: "ffc7ea"
  description: "Concerns the code to update servers data"
- name: "Category: New provider 🆕"
  color: "ffc7ea"
- name: "Category: OpenVPN 🔐"
  color: "ffc7ea"
- name: "Category: Wireguard 🔐"
  color: "ffc7ea"
- name: "Category: DNS 📠"
  color: "ffc7ea"
- name: "Category: Firewall ⛓️"
  color: "ffc7ea"
- name: "Category: MTU discovery 🔦"
  color: "ffc7ea"
- name: "Category: Routing 🛤️"
  color: "ffc7ea"
- name: "Category: IPv6 🛰️"
  color: "ffc7ea"
- name: "Category: VPN port forwarding 📥"
  color: "ffc7ea"
- name: "Category: HTTP proxy 🔁"
  color: "ffc7ea"
- name: "Category: Shadowsocks 🔁"
  color: "ffc7ea"
- name: "Category: control server ⚙️"
  color: "ffc7ea"
- name: "Category: kernel 🧠"
  color: "ffc7ea"
- name: "Category: public IP service 💬"
  color: "ffc7ea"
- name: "Category: servers storage 📦"
  color: "ffc7ea"
- name: "Category: Performance 🚀"
  color: "ffc7ea"
- name: "Category: Investigation 🔍"
  color: "ffc7ea"


================================================
FILE: .github/pull_request_template.md
================================================
# Description

<!-- Please describe the reason for the changes being proposed. -->

# Issue

<!-- Please link to the issue(s) this change relates to. -->

# Assertions

* [ ] I am aware that we do not accept manual changes to the servers.json file <!-- If this is your goal, please consult https://github.com/qdm12/gluetun-wiki/blob/main/setup/servers.md#update-using-the-command-line -->
* [ ] I am aware that any changes to settings should be reflected in the [wiki](https://github.com/qdm12/gluetun-wiki/)


================================================
FILE: .github/workflows/ci-skip.yml
================================================
name: No trigger file paths
on:
  push:
    branches:
      - master
    paths-ignore:
      - .github/workflows/ci.yml
      - cmd/**
      - internal/**
      - pkg/**
      - .dockerignore
      - .golangci.yml
      - Dockerfile
      - go.mod
      - go.sum
  pull_request:
    paths-ignore:
      - .github/workflows/ci.yml
      - cmd/**
      - internal/**
      - pkg/**
      - .dockerignore
      - .golangci.yml
      - Dockerfile
      - go.mod
      - go.sum

jobs:
  verify:
    runs-on: ubuntu-latest
    permissions:
      actions: read
    steps:
      - name: No trigger path triggered for required verify workflow.
        run: exit 0


================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
  release:
    types:
      - published
  push:
    branches:
      - master
    paths:
      - .github/workflows/ci.yml
      - cmd/**
      - internal/**
      - pkg/**
      - .dockerignore
      - .golangci.yml
      - Dockerfile
      - go.mod
      - go.sum
  pull_request:
    paths:
      - .github/workflows/ci.yml
      - cmd/**
      - internal/**
      - pkg/**
      - .dockerignore
      - .golangci.yml
      - Dockerfile
      - go.mod
      - go.sum

jobs:
  verify:
    runs-on: ubuntu-latest
    permissions:
      actions: read
      contents: read
    env:
      DOCKER_BUILDKIT: "1"
    steps:
      - uses: actions/checkout@v6

      - uses: reviewdog/action-misspell@v1
        with:
          locale: "US"
          level: error
          exclude: |
            ./internal/storage/servers.json
            ./.golangci.yml
            *.md

      - name: Linting
        run: docker build --target lint .

      - name: Mocks check
        run: docker build --target mocks .

      - name: Build test image
        run: docker build --target test -t test-container .

      - name: Run tests in test container
        run: |
          touch coverage.txt
          docker run --rm --cap-add=NET_ADMIN --device /dev/net/tun \
          -v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
          test-container

      - name: Verify dev cross platform compatibility
        run: docker build --target xcompile .

      - name: Build final image
        run: docker build -t final-image .

  verify-private:
    if: |
      github.repository == 'qdm12/gluetun' &&
      (
        github.event_name == 'push' ||
        github.event_name == 'release' ||
        (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]')
      )
    needs: [verify]
    runs-on: ubuntu-latest
    environment: secrets
    steps:
      - uses: actions/checkout@v6

      - run: docker build -t qmcgaw/gluetun .

      - name: Setup Go for CI utility
        uses: actions/setup-go@v6
        with:
          go-version-file: ci/go.mod

      - name: Build utility
        run: go build -C ./ci -o runner ./cmd/main.go

      - name: Run Gluetun container with Mullvad configuration
        run: echo -e "${{ secrets.MULLVAD_WIREGUARD_PRIVATE_KEY }}\n${{ secrets.MULLVAD_WIREGUARD_ADDRESS }}" | ./ci/runner mullvad

      - name: Run Gluetun container with ProtonVPN configuration
        run: echo -e "${{ secrets.PROTONVPN_WIREGUARD_PRIVATE_KEY }}" | ./ci/runner protonvpn

  codeql:
    runs-on: ubuntu-latest
    permissions:
      actions: read
      contents: read
      security-events: write
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-go@v6
        with:
          go-version-file: go.mod
      - uses: github/codeql-action/init@v4
        with:
          languages: go
      - uses: github/codeql-action/autobuild@v4
      - uses: github/codeql-action/analyze@v4

  publish:
    if: |
      github.repository == 'qdm12/gluetun' &&
      (
        github.event_name == 'push' ||
        github.event_name == 'release' ||
        (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]')
      )
    needs: [verify, verify-private, codeql]
    permissions:
      actions: read
      contents: read
      packages: write
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6

      # extract metadata (tags, labels) for Docker
      # https://github.com/docker/metadata-action
      - name: Extract Docker metadata
        id: meta
        uses: docker/metadata-action@v6
        with:
          flavor: |
            latest=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
          images: |
            ghcr.io/qdm12/gluetun
            qmcgaw/gluetun
            qmcgaw/private-internet-access
          tags: |
            type=ref,event=pr
            type=semver,pattern=v{{major}}.{{minor}}.{{patch}}
            type=semver,pattern=v{{major}}.{{minor}}
            type=semver,pattern=v{{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }}
            type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}

      - uses: docker/setup-qemu-action@v4
      - uses: docker/setup-buildx-action@v4

      - uses: docker/login-action@v4
        with:
          username: qmcgaw
          password: ${{ secrets.DOCKERHUB_PASSWORD }}

      - uses: docker/login-action@v4
        with:
          registry: ghcr.io
          username: qdm12
          password: ${{ github.token }}

      - name: Short commit
        id: shortcommit
        run: |
          # Use the PR head SHA if it exists, otherwise fallback to GITHUB_SHA
          FULL_SHA="${{ github.event.pull_request.head.sha || github.sha }}"
          SHORT_SHA=
          echo "value=$(echo $FULL_SHA | cut -c1-7)" >> $GITHUB_OUTPUT

      - name: Build and push final image
        uses: docker/build-push-action@v7
        with:
          platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
          labels: ${{ steps.meta.outputs.labels }}
          build-args: |
            CREATED=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }}
            COMMIT=${{ steps.shortcommit.outputs.value }}
            VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}
          tags: ${{ steps.meta.outputs.tags }}
          push: true


================================================
FILE: .github/workflows/closed-issue.yml
================================================
name: Closed issue
on:
  issues:
    types: [closed]

jobs:
  comment:
    permissions:
      issues: write
    runs-on: ubuntu-latest
    steps:
      - uses: peter-evans/create-or-update-comment@v5
        with:
          token: ${{ github.token }}
          issue-number: ${{ github.event.issue.number }}
          body: |
            Closed issues are **NOT** monitored, so commenting here will likely NOT be seen.
            If you think this is *still unresolved* and have **more information** to bring, please either
            re-open this issue or create another issue.

            ❤️😠 temporarily help the Gluetun community and fight the AI slop scam website `gluetun[dot]com` by setting `BORINGPOLL_GLUETUNCOM=on` on the latest image.
            See [the option in the wiki for more information](https://github.com/qdm12/gluetun-wiki/blob/main/setup/options/others.md)

            This is an automated comment setup because @qdm12 is the sole maintainer of this project
            which became too popular to monitor closed issues.


================================================
FILE: .github/workflows/configs/mlc-config.json
================================================
{
  "ignorePatterns": [
    {
      "pattern": "^https://console.substack.com/p/console-72$"
    }
  ],
  "timeout": "20s",
  "retryOn429": false,
  "fallbackRetryDelay": "30s",
  "aliveStatusCodes": [
    200,
    429
  ]
}

================================================
FILE: .github/workflows/labels.yml
================================================
name: labels
on:
  push:
    branches: [master]
    paths:
      - .github/labels.yml
      - .github/workflows/labels.yml
jobs:
  labeler:
    permissions:
      issues: write
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: crazy-max/ghaction-github-labeler@v6
        with:
          yaml-file: .github/labels.yml


================================================
FILE: .github/workflows/markdown-skip.yml
================================================
name: Markdown
on:
  push:
    branches:
      - master
    paths-ignore:
      - "**.md"
      - .github/workflows/markdown.yml
  pull_request:
    paths-ignore:
      - "**.md"
      - .github/workflows/markdown.yml

jobs:
  markdown:
    runs-on: ubuntu-latest
    permissions:
      actions: read
    steps:
      - name: No trigger path triggered for required markdown workflow.
        run: exit 0


================================================
FILE: .github/workflows/markdown.yml
================================================
name: Markdown
on:
  push:
    branches:
      - master
    paths:
      - "**.md"
      - .github/workflows/markdown.yml
  pull_request:
    paths:
      - "**.md"
      - .github/workflows/markdown.yml

jobs:
  markdown:
    runs-on: ubuntu-latest
    permissions:
      actions: read
      contents: read
    steps:
      - uses: actions/checkout@v6

      - uses: DavidAnson/markdownlint-cli2-action@v22
        with:
          globs: "**.md"
          config: .markdownlint-cli2.jsonc

      - uses: reviewdog/action-misspell@v1
        with:
          locale: "US"
          level: error
          pattern: |
            *.md

      - uses: gaurav-nelson/github-action-markdown-link-check@v1
        with:
          use-quiet-mode: yes
          config-file: .github/workflows/configs/mlc-config.json

      - uses: peter-evans/dockerhub-description@v5
        if: github.repository == 'qdm12/gluetun' && github.event_name == 'push'
        with:
          username: qmcgaw
          password: ${{ secrets.DOCKERHUB_PASSWORD }}
          repository: qmcgaw/gluetun
          short-description: Lightweight Swiss-knife VPN client to connect to several VPN providers
          readme-filepath: README.md


================================================
FILE: .github/workflows/opened-issue.yml
================================================
name: Opened issue
on:
  issues:
    types: [opened]

jobs:
  comment:
    permissions:
      issues: write
    runs-on: ubuntu-latest
    steps:
      - uses: peter-evans/create-or-update-comment@v5
        with:
          token: ${{ github.token }}
          issue-number: ${{ github.event.issue.number }}
          body: |
            @qdm12 is more or less the only maintainer of this project and works on it in his free time.
            Please:
            - **do not** ask for updates, be patient
            - :+1: the issue to show your support instead of commenting
            @qdm12 usually checks issues at least once a week, if this is a new urgent bug,
            [revert to an older tagged container image](https://github.com/qdm12/gluetun-wiki/blob/main/setup/docker-image-tags.md)


================================================
FILE: .github/workflows/update-servers-list.yml
================================================
name: Update servers list
on:
  workflow_dispatch:
    inputs:
      provider:
        description: "VPN Provider to update"
        required: true
        default: "all"
        type: choice
        options:
          - all
          - airvpn
          - cyberghost
          - expressvpn
          - fastestvpn
          - giganews
          - hidemyass
          - ipvanish
          - ivpn
          - mullvad
          - nordvpn
          - perfect privacy
          - privado
          - private internet access
          - privatevpn
          - protonvpn
          - purevpn
          - slickvpn
          - surfshark
          - torguard
          - vpnsecure
          - vpn unlimited
          - vyprvpn
          - windscribe
  schedule:
    - cron: "11 3 1 */2 *" # Run at 03:11 on the 1st of every 2nd month
jobs:
  update-servers-list:
    if: github.repository == 'qdm12/gluetun'
    runs-on: ubuntu-latest
    permissions:
      actions: read
      contents: write
      pull-requests: write
    steps:
      - uses: actions/checkout@v6

      - uses: actions/setup-go@v6
        with:
          go-version-file: go.mod

      - name: Update servers list
        run: |
          SELECTED_PROVIDER="${{ github.event.inputs.provider || 'all' }}"

          if [ "$SELECTED_PROVIDER" = "all" ]; then
            FLAGS="-all"
          else
            FLAGS="-providers $SELECTED_PROVIDER"
          fi

          go run ./cmd/gluetun/main.go update $FLAGS \
            -maintainer \
            -proton-email "${{ secrets.PROTON_EMAIL }}" \
            -proton-password "${{ secrets.PROTON_PASSWORD }}"

      - name: Check for changes
        run: |
          if git diff --exit-code internal/storage/servers.json >/dev/null; then
            echo "Error: internal/storage/servers.json was not modified."
            exit 1
          fi

      - name: Check no other file changes
        run: |
          if ! git diff --exit-code --quiet ':!internal/storage/servers.json'; then
            echo "Error: Unexpected changes detected in files other than servers.json"
            git status --short
            exit 1
          fi

      - name: Create Pull Request
        id: createpr
        uses: peter-evans/create-pull-request@v8
        with:
          branch-suffix: timestamp
          branch: bot/update-servers-list
          base: master
          delete-branch: true
          title: "feat(providers/${{ github.event.inputs.provider || 'all' }}): servers data update"
          body: |
            This PR was automatically generated by the [Update servers list](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) workflow run.

      # - name: Merge Pull Request
      #   env:
      #     GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      #   run: |
      #     gh pr merge ${{ steps.createpr.outputs.pull-request-number }} --auto -m -d


================================================
FILE: .gitignore
================================================
scratch.txt
.DS_Store


================================================
FILE: .golangci.yml
================================================
version: "2"

formatters:
  enable:
    - gci
    - gofumpt
    - goimports
  exclusions:
    generated: lax
    paths:
      - third_party$
      - builtin$
      - examples$

linters:
  settings:
    misspell:
      locale: US
    goconst:
      ignore-string-values:
        # commonly used settings strings
        - "^disabled$"
        # Firewall and routing strings
        - "^(ACCEPT|DROP)$"
        - "^--append$"
        - "^--delete$"
        - "^all$"
        - "^(tcp|udp)$"
        # Server route strings
        - "^/status$"

  exclusions:
    generated: lax
    presets:
      - comments
      - common-false-positives
      - legacy
      - std-error-handling
    rules:
      - linters:
          - containedctx
          - dupl
          - err113
          - maintidx
        path: _test\.go
      - linters:
          - dupl
        path: internal\/server\/.+\.go
      - linters:
          - ireturn
        text: returns interface \(golang\.org\/x\/sys\/unix\.Sockaddr\)
      - linters:
          - ireturn
        path: internal\/openvpn\/pkcs8\/descbc\.go
        text: newCipherDESCBCBlock returns interface \(github\.com\/youmark\/pkcs8\.Cipher\)
      - linters:
          - revive
        path: internal\/provider\/(common|utils)\/.+\.go
        text: "var-naming: avoid (bad|meaningless) package names"
      - linters:
          - lll
        source: "^// https://.+$"
      - linters:
          - mnd
        source: "^	cleanups\\.Add.+$"
        path: internal\/(wireguard|amneziawg)\/run\.go
      - linters:
          - err113
          - mnd
        path: ci\/.+\.go

    paths:
      - third_party$
      - builtin$
      - examples$
  enable:
    # - cyclop
    # - errorlint
    - asasalint
    - asciicheck
    - bidichk
    - bodyclose
    - containedctx
    - copyloopvar
    - decorder
    - dogsled
    - dupl
    - dupword
    - durationcheck
    - err113
    - errchkjson
    - errname
    - exhaustive
    - fatcontext
    - forcetypeassert
    - gocheckcompilerdirectives
    - gochecknoglobals
    - gochecknoinits
    - gocognit
    - goconst
    - gocritic
    - gocyclo
    - godot
    - goheader
    - gomoddirectives
    - goprintffuncname
    - gosec
    - gosmopolitan
    - grouper
    - importas
    - interfacebloat
    - intrange
    - ireturn
    - lll
    - maintidx
    - makezero
    - mirror
    - misspell
    - mnd
    - musttag
    - nakedret
    - nestif
    - nilerr
    - nilnil
    - noctx
    - nolintlint
    - nosprintfhostport
    - paralleltest
    - prealloc
    - predeclared
    - promlinter
    - reassign
    - revive
    - rowserrcheck
    - sqlclosecheck
    - tagalign
    - thelper
    - tparallel
    - unconvert
    - unparam
    - usestdlibvars
    - wastedassign
    - whitespace
    - zerologlint


================================================
FILE: .markdownlint-cli2.jsonc
================================================
{
  "config": {
    "default": true,
    "MD013": false,
  },
  "ignores": [
    ".github/pull_request_template.md"
  ]
}

================================================
FILE: .vscode/extensions.json
================================================
{
  // This list should be kept to the strict minimum
  // to develop this project.
  "recommendations": [
    "golang.go",
    "davidanson.vscode-markdownlint",
  ],
}

================================================
FILE: .vscode/settings.json
================================================
{
  // The settings should be kept to the strict minimum
  // to develop this project.
  "files.eol": "\n",
  "editor.formatOnSave": true,
  "go.buildTags": "linux",
  "go.toolsEnvVars": {
    "CGO_ENABLED": "0"
  },
  "go.testEnvVars": {
    "CGO_ENABLED": "1"
  },
  "go.testFlags": [
    "-v",
    "-race"
  ],
  "go.testTimeout": "10s",
  "go.coverOnSingleTest": true,
  "go.coverOnSingleTestFile": true,
  "go.coverOnTestPackage": true,
  "go.useLanguageServer": true,
  "[go]": {
    "editor.codeActionsOnSave": {
      "source.organizeImports": "explicit"
    }
  },
  "go.lintTool": "golangci-lint",
  "go.lintOnSave": "package"
}

================================================
FILE: .vscode/tasks.json
================================================
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Update a VPN provider servers data",
            "type": "shell",
            "command": "go",
            "args": [
                "run",
                "./cmd/gluetun/main.go",
                "update",
                "${input:updateMode}",
                "-providers",
                "${input:provider}"
            ],
        },
        {
            "label": "Add a Gluetun Github Git remote",
            "type": "shell",
            "command": "git",
            "args": [
                "remote",
                "add",
                "${input:githubRemoteUsername}",
                "git@github.com:${input:githubRemoteUsername}/gluetun.git"
            ],
        }
    ],
    "inputs": [
        {
            "id": "provider",
            "type": "promptString",
            "description": "Please enter a provider (or comma separated list of providers)",
        },
        {
            "id": "updateMode",
            "type": "pickString",
            "description": "Update mode to use",
            "options": [
                "-maintainer",
                "-enduser"
            ],
            "default": "-maintainer"
        },
        {
            "id": "githubRemoteUsername",
            "type": "promptString",
            "description": "Please enter a Github username",
        },
    ]
}

================================================
FILE: Dockerfile
================================================
ARG ALPINE_VERSION=3.23
ARG GO_ALPINE_VERSION=3.23
ARG GO_VERSION=1.25
ARG XCPUTRANSLATE_VERSION=v0.9.0
ARG GOLANGCI_LINT_VERSION=v2.4.0
ARG MOCKGEN_VERSION=v1.6.0
ARG BUILDPLATFORM=linux/amd64

FROM --platform=${BUILDPLATFORM} ghcr.io/qdm12/xcputranslate:${XCPUTRANSLATE_VERSION} AS xcputranslate
FROM --platform=${BUILDPLATFORM} ghcr.io/qdm12/binpot:golangci-lint-${GOLANGCI_LINT_VERSION} AS golangci-lint
FROM --platform=${BUILDPLATFORM} ghcr.io/qdm12/binpot:mockgen-${MOCKGEN_VERSION} AS mockgen

FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION}-alpine${GO_ALPINE_VERSION} AS base
COPY --from=xcputranslate /xcputranslate /usr/local/bin/xcputranslate
# Note: findutils needed to have xargs support `-d` flag for mocks stage.
RUN apk --update add git g++ findutils iptables
ENV CGO_ENABLED=0
COPY --from=golangci-lint /bin /go/bin/golangci-lint
COPY --from=mockgen /bin /go/bin/mockgen
WORKDIR /tmp/gobuild
COPY go.mod go.sum ./
RUN go mod download
COPY cmd/ ./cmd/
COPY internal/ ./internal/

FROM --platform=${BUILDPLATFORM} base AS test
# Note on the go race detector:
# - we set CGO_ENABLED=1 to have it enabled
# - we installed g++ to support the race detector
ENV CGO_ENABLED=1
ENTRYPOINT go test -race -coverpkg=./... -coverprofile=coverage.txt -covermode=atomic ./...

FROM --platform=${BUILDPLATFORM} base AS lint
COPY .golangci.yml ./
RUN golangci-lint run

FROM --platform=${BUILDPLATFORM} base AS mocks
RUN git init && \
    git config user.email ci@localhost && \
    git config user.name ci && \
    git config core.fileMode false && \
    git add -A && \
    git commit -m "snapshot" && \
    grep -lr -E '^// Code generated by MockGen\. DO NOT EDIT\.$' . | xargs -r -d '\n' rm && \
    go generate -run "mockgen" ./... && \
    git diff --exit-code && \
    rm -rf .git/

FROM --platform=${BUILDPLATFORM} base AS xcompile
RUN GOOS=darwin go build -o /dev/null ./...
RUN GOOS=windows go build -o /dev/null ./...

FROM --platform=${BUILDPLATFORM} base AS build
ARG TARGETPLATFORM
ARG VERSION=unknown
ARG CREATED="an unknown date"
ARG COMMIT=unknown
RUN GOARCH="$(xcputranslate translate -field arch -targetplatform ${TARGETPLATFORM})" \
    GOARM="$(xcputranslate translate -field arm -targetplatform ${TARGETPLATFORM})" \
    go build -trimpath -ldflags="-s -w \
    -X 'main.version=$VERSION' \
    -X 'main.created=$CREATED' \
    -X 'main.commit=$COMMIT' \
    " -o entrypoint cmd/gluetun/main.go

FROM alpine:${ALPINE_VERSION}
ARG VERSION=unknown
ARG CREATED="an unknown date"
ARG COMMIT=unknown
LABEL \
    org.opencontainers.image.authors="quentin.mcgaw@gmail.com" \
    org.opencontainers.image.created=$CREATED \
    org.opencontainers.image.version=$VERSION \
    org.opencontainers.image.revision=$COMMIT \
    org.opencontainers.image.url="https://github.com/qdm12/gluetun" \
    org.opencontainers.image.documentation="https://github.com/qdm12/gluetun" \
    org.opencontainers.image.source="https://github.com/qdm12/gluetun" \
    org.opencontainers.image.title="VPN swiss-knife like client for multiple VPN providers" \
    org.opencontainers.image.description="VPN swiss-knife like client to tunnel to multiple VPN servers using OpenVPN, IPtables, DNS over TLS, Shadowsocks, an HTTP proxy and Alpine Linux"
ENV VPN_SERVICE_PROVIDER=pia \
    VPN_TYPE=openvpn \
    # Common VPN options
    VPN_INTERFACE=tun0 \
    # OpenVPN
    OPENVPN_ENDPOINT_IP= \
    OPENVPN_ENDPOINT_PORT= \
    OPENVPN_PROTOCOL=udp \
    OPENVPN_USER= \
    OPENVPN_PASSWORD= \
    OPENVPN_USER_SECRETFILE=/run/secrets/openvpn_user \
    OPENVPN_PASSWORD_SECRETFILE=/run/secrets/openvpn_password \
    OPENVPN_VERSION=2.6 \
    OPENVPN_VERBOSITY=1 \
    OPENVPN_FLAGS= \
    OPENVPN_CIPHERS= \
    OPENVPN_AUTH= \
    OPENVPN_PROCESS_USER=root \
    OPENVPN_MSSFIX= \
    OPENVPN_CUSTOM_CONFIG= \
    # Wireguard
    WIREGUARD_ENDPOINT_IP= \
    WIREGUARD_ENDPOINT_PORT= \
    WIREGUARD_CONF_SECRETFILE=/run/secrets/wg0.conf \
    WIREGUARD_PRIVATE_KEY= \
    WIREGUARD_PRIVATE_KEY_SECRETFILE=/run/secrets/wireguard_private_key \
    WIREGUARD_PRESHARED_KEY= \
    WIREGUARD_PRESHARED_KEY_SECRETFILE=/run/secrets/wireguard_preshared_key \
    WIREGUARD_PUBLIC_KEY= \
    WIREGUARD_ALLOWED_IPS= \
    WIREGUARD_PERSISTENT_KEEPALIVE_INTERVAL=0 \
    WIREGUARD_ADDRESSES= \
    WIREGUARD_ADDRESSES_SECRETFILE=/run/secrets/wireguard_addresses \
    WIREGUARD_MTU= \
    WIREGUARD_IMPLEMENTATION=auto \
    # Amnezia
    AMNEZIAWG_ENDPOINT_IP= \
    AMNEZIAWG_ENDPOINT_PORT= \
    AMNEZIAWG_CONF_SECRETFILE=/run/secrets/wg0.conf \
    AMNEZIAWG_PRIVATE_KEY= \
    AMNEZIAWG_PRIVATE_KEY_SECRETFILE=/run/secrets/wireguard_private_key \
    AMNEZIAWG_PRESHARED_KEY= \
    AMNEZIAWG_PRESHARED_KEY_SECRETFILE=/run/secrets/wireguard_preshared_key \
    AMNEZIAWG_PUBLIC_KEY= \
    AMNEZIAWG_ALLOWED_IPS= \
    AMNEZIAWG_PERSISTENT_KEEPALIVE_INTERVAL=0 \
    AMNEZIAWG_ADDRESSES= \
    AMNEZIAWG_ADDRESSES_SECRETFILE=/run/secrets/wireguard_addresses \
    AMNEZIAWG_MTU= \
    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= \
    # 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= \
    VPN_PORT_FORWARDING_UP_COMMAND= \
    VPN_PORT_FORWARDING_DOWN_COMMAND= \
    VPN_PORT_FORWARDING_LISTENING_PORT=0 \
    VPN_PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
    # PMTUD
    PMTUD_ICMP_ADDRESSES=1.1.1.1,8.8.8.8 \
    PMTUD_TCP_ADDRESSES=1.1.1.1:443,8.8.8.8:443,1.1.1.1:53,8.8.8.8:53,[2606:4700:4700::1111]:53,[2001:4860:4860::8888]:53,[2606:4700:4700::1111]:443,[2001:4860:4860::8888]:443 \
    # VPN server filtering
    SERVER_REGIONS= \
    SERVER_COUNTRIES= \
    SERVER_CITIES= \
    SERVER_HOSTNAMES= \
    SERVER_CATEGORIES= \
    # # Mullvad only:
    ISP= \
    OWNED_ONLY=no \
    # # Private Internet Access only:
    PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET= \
    VPN_PORT_FORWARDING_USERNAME= \
    VPN_PORT_FORWARDING_PASSWORD= \
    # # Cyberghost only:
    OPENVPN_CERT= \
    OPENVPN_KEY= \
    OPENVPN_CLIENTCRT_SECRETFILE=/run/secrets/openvpn_clientcrt \
    OPENVPN_CLIENTKEY_SECRETFILE=/run/secrets/openvpn_clientkey \
    # # VPNSecure only:
    OPENVPN_ENCRYPTED_KEY= \
    OPENVPN_ENCRYPTED_KEY_SECRETFILE=/run/secrets/openvpn_encrypted_key \
    OPENVPN_KEY_PASSPHRASE= \
    OPENVPN_KEY_PASSPHRASE_SECRETFILE=/run/secrets/openvpn_key_passphrase \
    # # Nordvpn only:
    SERVER_NUMBER= \
    # # PIA only:
    SERVER_NAMES= \
    # # VPNUnlimited and ProtonVPN only:
    STREAM_ONLY= \
    FREE_ONLY= \
    # # ProtonVPN only:
    SECURE_CORE_ONLY= \
    TOR_ONLY= \
    # # Surfshark only:
    MULTIHOP_ONLY= \
    # # VPN Secure only:
    PREMIUM_ONLY= \
    # # PIA and ProtonVPN only:
    PORT_FORWARD_ONLY= \
    # Firewall
    FIREWALL_ENABLED_DISABLING_IT_SHOOTS_YOU_IN_YOUR_FOOT=on \
    FIREWALL_VPN_INPUT_PORTS= \
    FIREWALL_INPUT_PORTS= \
    FIREWALL_OUTBOUND_SUBNETS= \
    FIREWALL_IPTABLES_LOG_LEVEL=info \
    # Logging
    LOG_LEVEL=info \
    # Health
    HEALTH_SERVER_ADDRESS=127.0.0.1:9999 \
    HEALTH_TARGET_ADDRESSES=cloudflare.com:443,github.com:443 \
    HEALTH_ICMP_TARGET_IPS=1.1.1.1,8.8.8.8 \
    HEALTH_SMALL_CHECK_TYPE=icmp \
    HEALTH_RESTART_VPN=on \
    # DNS
    DNS_UPSTREAM_RESOLVER_TYPE=DoT \
    # Note: DNS_UPSTREAM_RESOLVERS defaults to cloudflare in code if DNS_UPSTREAM_PLAIN_ADDRESSES is empty
    DNS_UPSTREAM_RESOLVERS= \
    DNS_BLOCK_IPS= \
    DNS_BLOCK_IP_PREFIXES= \
    DNS_CACHING=on \
    DNS_UPSTREAM_IPV6=off \
    BLOCK_MALICIOUS=on \
    BLOCK_SURVEILLANCE=off \
    BLOCK_ADS=off \
    DNS_UNBLOCK_HOSTNAMES= \
    DNS_REBINDING_PROTECTION_EXEMPT_HOSTNAMES= \
    DNS_UPDATE_PERIOD=24h \
    DNS_UPSTREAM_PLAIN_ADDRESSES= \
    # HTTP proxy
    HTTPPROXY= \
    HTTPPROXY_LOG=off \
    HTTPPROXY_LISTENING_ADDRESS=":8888" \
    HTTPPROXY_STEALTH=off \
    HTTPPROXY_USER= \
    HTTPPROXY_PASSWORD= \
    HTTPPROXY_USER_SECRETFILE=/run/secrets/httpproxy_user \
    HTTPPROXY_PASSWORD_SECRETFILE=/run/secrets/httpproxy_password \
    # Shadowsocks
    SHADOWSOCKS=off \
    SHADOWSOCKS_LOG=off \
    SHADOWSOCKS_LISTENING_ADDRESS=":8388" \
    SHADOWSOCKS_PASSWORD= \
    SHADOWSOCKS_PASSWORD_SECRETFILE=/run/secrets/shadowsocks_password \
    SHADOWSOCKS_CIPHER=chacha20-ietf-poly1305 \
    # Control server
    HTTP_CONTROL_SERVER_LOG=on \
    HTTP_CONTROL_SERVER_ADDRESS=":8000" \
    HTTP_CONTROL_SERVER_AUTH_CONFIG_FILEPATH=/gluetun/auth/config.toml \
    HTTP_CONTROL_SERVER_AUTH_DEFAULT_ROLE="{}" \
    # Server data updater
    UPDATER_PERIOD=0 \
    UPDATER_MIN_RATIO=0.8 \
    UPDATER_VPN_SERVICE_PROVIDERS= \
    UPDATER_PROTONVPN_EMAIL= \
    UPDATER_PROTONVPN_PASSWORD= \
    # Public IP
    PUBLICIP_FILE="/tmp/gluetun/ip" \
    PUBLICIP_ENABLED=on \
    PUBLICIP_API=ipinfo,ifconfigco,ip2location,cloudflare \
    PUBLICIP_API_TOKEN= \
    # Storage
    STORAGE_FILEPATH=/gluetun/servers.json \
    # Pprof
    PPROF_ENABLED=no \
    PPROF_BLOCK_PROFILE_RATE=0 \
    PPROF_MUTEX_PROFILE_RATE=0 \
    PPROF_HTTP_SERVER_ADDRESS=":6060" \
    # Extras
    VERSION_INFORMATION=on \
    BORINGPOLL_GLUETUNCOM=off \
    TZ= \
    PUID=1000 \
    PGID=1000
ENTRYPOINT ["/gluetun-entrypoint"]
EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=3 CMD /gluetun-entrypoint healthcheck
ARG TARGETPLATFORM
RUN apk add --no-cache --update -l wget && \
    apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.17/main" openvpn\~2.5 && \
    mv /usr/sbin/openvpn /usr/sbin/openvpn2.5 && \
    apk del openvpn && \
    apk add --no-cache --update openvpn ca-certificates iptables iptables-legacy tzdata && \
    mv /usr/sbin/openvpn /usr/sbin/openvpn2.6 && \
    rm -rf /var/cache/apk/* /etc/openvpn/*.sh /usr/lib/openvpn/plugins/openvpn-plugin-down-root.so && \
    deluser openvpn && \
    mkdir /gluetun
COPY --from=build /tmp/gobuild/entrypoint /gluetun-entrypoint


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2018 Quentin McGaw

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

================================================
FILE: README.md
================================================
# Gluetun VPN client

⚠️ This and [gluetun-wiki](https://github.com/qdm12/gluetun-wiki) are the only websites for Gluetun, other websites claiming to be official are scams ⚠️

💁 You can optionally set `BORINGPOLL_GLUETUNCOM=on` to... [poll](./internal/boringpoll/boringpoll.go) that **scammy AI slop** website every few minutes so it costs them too much to keep it up. My gentle email reminders to take it down are being grossly ignored 🤷 This would make me very happy and serve this community.

Lightweight swiss-army-knife-like VPN client to multiple VPN service providers

![Title image](https://raw.githubusercontent.com/qdm12/gluetun/master/title.svg)

[![Build status](https://github.com/qdm12/gluetun/actions/workflows/ci.yml/badge.svg)](https://github.com/qdm12/gluetun/actions/workflows/ci.yml)

[![Docker pulls qmcgaw/gluetun](https://img.shields.io/docker/pulls/qmcgaw/gluetun.svg)](https://hub.docker.com/r/qmcgaw/gluetun)
[![Docker pulls qmcgaw/private-internet-access](https://img.shields.io/docker/pulls/qmcgaw/private-internet-access.svg)](https://hub.docker.com/r/qmcgaw/gluetun)

[![Docker stars qmcgaw/gluetun](https://img.shields.io/docker/stars/qmcgaw/gluetun.svg)](https://hub.docker.com/r/qmcgaw/gluetun)
[![Docker stars qmcgaw/private-internet-access](https://img.shields.io/docker/stars/qmcgaw/private-internet-access.svg)](https://hub.docker.com/r/qmcgaw/gluetun)

![Last release](https://img.shields.io/github/release/qdm12/gluetun?label=Last%20release)
![Last Docker tag](https://img.shields.io/docker/v/qmcgaw/gluetun?sort=semver&label=Last%20Docker%20tag)
[![Last release size](https://img.shields.io/docker/image-size/qmcgaw/gluetun?sort=semver&label=Last%20released%20image)](https://hub.docker.com/r/qmcgaw/gluetun/tags?page=1&ordering=last_updated)
![GitHub last release date](https://img.shields.io/github/release-date/qdm12/gluetun?label=Last%20release%20date)
![Commits since release](https://img.shields.io/github/commits-since/qdm12/gluetun/latest?sort=semver)

[![Latest size](https://img.shields.io/docker/image-size/qmcgaw/gluetun/latest?label=Latest%20image)](https://hub.docker.com/r/qmcgaw/gluetun/tags)

[![GitHub last commit](https://img.shields.io/github/last-commit/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/commits/master)
[![GitHub commit activity](https://img.shields.io/github/commit-activity/y/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/graphs/contributors)
[![GitHub closed PRs](https://img.shields.io/github/issues-pr-closed/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/pulls?q=is%3Apr+is%3Aclosed)
[![GitHub issues](https://img.shields.io/github/issues/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/issues)
[![GitHub closed issues](https://img.shields.io/github/issues-closed/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/issues?q=is%3Aissue+is%3Aclosed)

![Code size](https://img.shields.io/github/languages/code-size/qdm12/gluetun)
![GitHub repo size](https://img.shields.io/github/repo-size/qdm12/gluetun)
![Go version](https://img.shields.io/github/go-mod/go-version/qdm12/gluetun)

![Visitors count](https://visitor-badge.laobi.icu/badge?page_id=gluetun.readme)

## Quick links

- [Setup](#setup)
- [Features](#features)
- Problem?
  - Check the Wiki [common errors](https://github.com/qdm12/gluetun-wiki/tree/main/errors) and [faq](https://github.com/qdm12/gluetun-wiki/tree/main/faq)
  - [Start a discussion](https://github.com/qdm12/gluetun/discussions)
  - [Fix the Unraid template](https://github.com/qdm12/gluetun/discussions/550)
- Suggestion?
  - [Create an issue](https://github.com/qdm12/gluetun/issues)
- Happy?
  - Sponsor me on [github.com/sponsors/qdm12](https://github.com/sponsors/qdm12)
  - Donate to [paypal.me/qmcgaw](https://www.paypal.me/qmcgaw)
  - Drop me [an email](mailto:quentin.mcgaw@gmail.com)
- **Want to add a VPN provider?** check [the development page](https://github.com/qdm12/gluetun-wiki/blob/main/contributing/development.md) and [add a provider page](https://github.com/qdm12/gluetun-wiki/blob/main/contributing/add-a-provider.md)
- Video:

  [![Video Gif](https://i.imgur.com/CetWunc.gif)](https://youtu.be/0F6I03LQcI4)

- [Substack Console interview](https://console.substack.com/p/console-72)

## Features

- Based on Alpine 3.23 for a small Docker image of 43.1MB
- Supports: **AirVPN**, **Cyberghost**, **ExpressVPN**, **FastestVPN**, **Giganews**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad** (Wireguard only), **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**,  **SlickVPN**, **Surfshark**, **TorGuard**, **VPNSecure.me**, **VPNUnlimited**, **Vyprvpn**, **Windscribe** servers
- Supports OpenVPN for all providers listed
- Supports Wireguard both kernelspace and userspace
  - For **AirVPN**, **FastestVPN**, **Ivpn**, **Mullvad**, **NordVPN**, **Perfect privacy**, **ProtonVPN**, **Surfshark** and **Windscribe**
  - For **Cyberghost**, **Private Internet Access**, **PrivateVPN**, **PureVPN**, **Torguard**, **VPN Unlimited** and **VyprVPN** using [the custom provider](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/custom.md)
  - For custom Wireguard configurations using [the custom provider](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/custom.md)
  - More in progress, see [#134](https://github.com/qdm12/gluetun/issues/134)
- Supports AmneziaWG only with the custom provider for now
- DNS over TLS baked in with service provider(s) of your choice
- DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours
- Choose the vpn network protocol, `udp` or `tcp`
- Built in firewall kill switch to allow traffic only with needed the VPN servers and LAN devices
- Built in Shadowsocks proxy server (protocol based on SOCKS5 with an encryption layer, tunnels TCP+UDP)
- Built in HTTP proxy (tunnels HTTP and HTTPS through TCP)
- [Connect other containers to it](https://github.com/qdm12/gluetun-wiki/blob/main/setup/connect-a-container-to-gluetun.md)
- [Connect LAN devices to it](https://github.com/qdm12/gluetun-wiki/blob/main/setup/connect-a-lan-device-to-gluetun.md)
- Compatible with amd64, i686 (32 bit), **ARM** 64 bit, ARM 32 bit v6 and v7, and even ppc64le 🎆
- Custom VPN server side port forwarding for [Perfect Privacy](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/perfect-privacy.md#vpn-server-port-forwarding), [Private Internet Access](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/private-internet-access.md#vpn-server-port-forwarding), [PrivateVPN](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/privatevpn.md#vpn-server-port-forwarding) and [ProtonVPN](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/protonvpn.md#vpn-server-port-forwarding)
- Possibility of split horizon DNS by selecting multiple DNS over TLS providers
- Can work as a Kubernetes sidecar container, thanks @rorph

## Setup

🎉 There are now instructions specific to each VPN provider with examples to help you get started as quickly as possible!

Go to the [Wiki](https://github.com/qdm12/gluetun-wiki)!

[🐛 Found a bug in the Wiki?!](https://github.com/qdm12/gluetun-wiki/issues/new/choose)

Here's a docker-compose.yml for the laziest:

```yml
---
services:
  gluetun:
    image: qmcgaw/gluetun
    # container_name: gluetun
    # line above must be uncommented to allow external containers to connect.
    # See https://github.com/qdm12/gluetun-wiki/blob/main/setup/connect-a-container-to-gluetun.md#external-container-to-gluetun
    cap_add:
      - NET_ADMIN
    devices:
      - /dev/net/tun:/dev/net/tun
    ports:
      - 8888:8888/tcp # HTTP proxy
      - 8388:8388/tcp # Shadowsocks
      - 8388:8388/udp # Shadowsocks
    volumes:
      - /yourpath:/gluetun
    environment:
      # See https://github.com/qdm12/gluetun-wiki/tree/main/setup#setup
      - VPN_SERVICE_PROVIDER=ivpn
      - VPN_TYPE=openvpn
      # OpenVPN:
      - OPENVPN_USER=
      - OPENVPN_PASSWORD=
      # Wireguard:
      # - WIREGUARD_PRIVATE_KEY=wOEI9rqqbDwnN8/Bpp22sVz48T71vJ4fYmFWujulwUU=
      # - WIREGUARD_ADDRESSES=10.64.222.21/32
      # Timezone for accurate log times
      - TZ=
      # Server list updater
      # See https://github.com/qdm12/gluetun-wiki/blob/main/setup/servers.md#update-the-vpn-servers-list
      - UPDATER_PERIOD=
```

🆕 Image also available as `ghcr.io/qdm12/gluetun`

## Fun graphs

[![Star History Chart](https://api.star-history.com/svg?repos=qdm12/gluetun&type=date&legend=top-left)](https://www.star-history.com/#qdm12/gluetun&type=date&legend=top-left)

## License

[![MIT](https://img.shields.io/github/license/qdm12/gluetun)](https://github.com/qdm12/gluetun/blob/master/LICENSE)


================================================
FILE: ci/cmd/main.go
================================================
package main

import (
	"context"
	"fmt"
	"os"
	"os/signal"

	"github.com/qdm12/gluetun/ci/internal"
	"github.com/qdm12/log"
)

func main() {
	logger := log.New()
	if len(os.Args) < 2 {
		logger.Error("Usage: " + os.Args[0] + " <command>")
		os.Exit(1)
	}

	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)

	var err error
	switch os.Args[1] {
	case "mullvad":
		err = internal.MullvadTest(ctx, logger)
	case "protonvpn":
		err = internal.ProtonVPNTest(ctx, logger)
	default:
		err = fmt.Errorf("unknown command: %s", os.Args[1])
	}
	stop()
	if err != nil {
		logger.Error(err.Error())
		os.Exit(1)
	}
	logger.Info("test completed successfully")
}


================================================
FILE: ci/go.mod
================================================
module github.com/qdm12/gluetun/ci

go 1.25.0

require (
	github.com/docker/docker v28.5.1+incompatible
	github.com/opencontainers/image-spec v1.1.1
)

require (
	github.com/Microsoft/go-winio v0.6.2 // indirect
	github.com/containerd/errdefs v1.0.0 // indirect
	github.com/containerd/errdefs/pkg v0.3.0 // indirect
	github.com/containerd/log v0.1.0 // indirect
	github.com/distribution/reference v0.6.0 // indirect
	github.com/docker/go-connections v0.6.0 // indirect
	github.com/docker/go-units v0.5.0 // indirect
	github.com/fatih/color v1.13.0 // indirect
	github.com/felixge/httpsnoop v1.0.4 // indirect
	github.com/go-logr/logr v1.4.3 // indirect
	github.com/go-logr/stdr v1.2.2 // indirect
	github.com/mattn/go-colorable v0.1.9 // indirect
	github.com/mattn/go-isatty v0.0.14 // indirect
	github.com/moby/docker-image-spec v1.3.1 // indirect
	github.com/moby/sys/atomicwriter v0.1.0 // indirect
	github.com/moby/term v0.5.2 // indirect
	github.com/morikuni/aec v1.0.0 // indirect
	github.com/opencontainers/go-digest v1.0.0 // indirect
	github.com/pkg/errors v0.9.1 // indirect
	github.com/qdm12/log v0.1.0 // indirect
	go.opentelemetry.io/auto/sdk v1.1.0 // indirect
	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
	go.opentelemetry.io/otel v1.38.0 // indirect
	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect
	go.opentelemetry.io/otel/metric v1.38.0 // indirect
	go.opentelemetry.io/otel/trace v1.38.0 // indirect
	golang.org/x/sys v0.35.0 // indirect
	golang.org/x/time v0.14.0 // indirect
	gotest.tools/v3 v3.5.2 // indirect
)


================================================
FILE: ci/go.sum
================================================
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM=
github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
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/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/qdm12/log v0.1.0 h1:jYBd/xscHYpblzZAd2kjZp2YmuYHjAAfbTViJWxoPTw=
github.com/qdm12/log v0.1.0/go.mod h1:Vchi5M8uBvHfPNIblN4mjXn/oSbiWguQIbsgF1zdQPI=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY=
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc=
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=


================================================
FILE: ci/internal/mullvad.go
================================================
package internal

import (
	"context"
	"fmt"
)

func MullvadTest(ctx context.Context, logger Logger) error {
	expectedSecrets := []string{
		"Wireguard private key",
		"Wireguard address",
	}
	secrets, err := readSecrets(ctx, expectedSecrets, logger)
	if err != nil {
		return fmt.Errorf("reading secrets: %w", err)
	}

	env := []string{
		"VPN_SERVICE_PROVIDER=mullvad",
		"VPN_TYPE=wireguard",
		"LOG_LEVEL=debug",
		"SERVER_COUNTRIES=USA",
		"WIREGUARD_PRIVATE_KEY=" + secrets[0],
		"WIREGUARD_ADDRESSES=" + secrets[1],
	}
	return simpleTest(ctx, env, logger)
}


================================================
FILE: ci/internal/protonvpn.go
================================================
package internal

import (
	"context"
	"fmt"
)

func ProtonVPNTest(ctx context.Context, logger Logger) error {
	expectedSecrets := []string{
		"Wireguard private key",
	}
	secrets, err := readSecrets(ctx, expectedSecrets, logger)
	if err != nil {
		return fmt.Errorf("reading secrets: %w", err)
	}

	env := []string{
		"VPN_SERVICE_PROVIDER=protonvpn",
		"VPN_TYPE=wireguard",
		"LOG_LEVEL=debug",
		"SERVER_COUNTRIES=United States",
		"WIREGUARD_PRIVATE_KEY=" + secrets[0],
	}
	return simpleTest(ctx, env, logger)
}


================================================
FILE: ci/internal/secrets.go
================================================
package internal

import (
	"bufio"
	"context"
	"fmt"
	"os"
	"strings"
)

type Logger interface {
	Info(msg string)
	Infof(format string, args ...any)
}

func readSecrets(ctx context.Context, expectedSecrets []string,
	logger Logger,
) (lines []string, err error) {
	scanner := bufio.NewScanner(os.Stdin)
	lines = make([]string, 0, len(expectedSecrets))

	for i := range expectedSecrets {
		logger.Infof("🤫 reading %s from Stdin...", expectedSecrets[i])
		if !scanner.Scan() {
			break
		}
		lines = append(lines, strings.TrimSpace(scanner.Text()))
		logger.Infof("🤫 %s secret read successfully", expectedSecrets[i])
		if ctx.Err() != nil {
			return nil, ctx.Err()
		}
	}

	if err := scanner.Err(); err != nil {
		return nil, fmt.Errorf("reading secrets from stdin: %w", err)
	}

	if len(lines) < len(expectedSecrets) {
		return nil, fmt.Errorf("expected %d secrets via Stdin, but only received %d",
			len(expectedSecrets), len(lines))
	}
	for i, line := range lines {
		if line == "" {
			return nil, fmt.Errorf("secret on line %d/%d was empty", i+1, len(lines))
		}
	}

	return lines, nil
}


================================================
FILE: ci/internal/simple.go
================================================
package internal

import (
	"bufio"
	"context"
	"fmt"
	"io"
	"regexp"
	"time"

	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/network"
	"github.com/docker/docker/client"
	v1 "github.com/opencontainers/image-spec/specs-go/v1"
)

func ptrTo[T any](v T) *T { return &v }

func simpleTest(ctx context.Context, env []string, logger Logger) error {
	const timeout = 60 * time.Second
	ctx, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()

	client, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
	if err != nil {
		return fmt.Errorf("creating Docker client: %w", err)
	}
	defer client.Close()

	config := &container.Config{
		Image:       "qmcgaw/gluetun",
		StopTimeout: ptrTo(3),
		Env:         env,
	}
	hostConfig := &container.HostConfig{
		AutoRemove: true,
		CapAdd:     []string{"NET_ADMIN", "NET_RAW"},
	}
	networkConfig := (*network.NetworkingConfig)(nil)
	platform := (*v1.Platform)(nil)
	const containerName = "" // auto-generated name

	response, err := client.ContainerCreate(ctx, config, hostConfig, networkConfig, platform, containerName)
	if err != nil {
		return fmt.Errorf("creating container: %w", err)
	}
	for _, warning := range response.Warnings {
		fmt.Println("Warning during container creation:", warning)
	}
	containerID := response.ID
	defer stopContainer(client, containerID)

	beforeStartTime := time.Now()

	err = client.ContainerStart(ctx, containerID, container.StartOptions{})
	if err != nil {
		return fmt.Errorf("starting container: %w", err)
	}

	return waitForLogLine(ctx, client, containerID, beforeStartTime, logger)
}

func stopContainer(client *client.Client, containerID string) {
	const stopTimeout = 5 * time.Second // must be higher than 3s, see above [container.Config]'s StopTimeout field
	stopCtx, stopCancel := context.WithTimeout(context.Background(), stopTimeout)
	defer stopCancel()

	err := client.ContainerStop(stopCtx, containerID, container.StopOptions{})
	if err != nil {
		fmt.Println("failed to stop container:", err)
	}
}

var successRegexp = regexp.MustCompile(`^.+Public IP address is .+$`)

func waitForLogLine(ctx context.Context, client *client.Client, containerID string,
	beforeStartTime time.Time, logger Logger,
) error {
	logOptions := container.LogsOptions{
		ShowStdout: true,
		Follow:     true,
		Since:      beforeStartTime.Format(time.RFC3339Nano),
	}

	reader, err := client.ContainerLogs(ctx, containerID, logOptions)
	if err != nil {
		return fmt.Errorf("error getting container logs: %w", err)
	}
	defer reader.Close()

	var linesSeen []string
	scanner := bufio.NewScanner(reader)
	for ctx.Err() == nil {
		if scanner.Scan() {
			line := scanner.Text()
			if len(line) > 8 { // remove Docker log prefix
				line = line[8:]
			}
			linesSeen = append(linesSeen, line)
			if successRegexp.MatchString(line) {
				fmt.Println("✅ Success line logged")
				return nil
			}
			continue
		}
		err := scanner.Err()
		if err != nil && err != io.EOF {
			logSeenLines(logger, linesSeen)
			return fmt.Errorf("reading log stream: %w", err)
		}

		// The scanner is either done or cannot read because of EOF
		logger.Info("the log scanner stopped")
		logSeenLines(logger, linesSeen)

		// Check if the container is still running
		inspect, err := client.ContainerInspect(ctx, containerID)
		if err != nil {
			return fmt.Errorf("inspecting container: %w", err)
		}
		if !inspect.State.Running {
			return fmt.Errorf("container stopped unexpectedly while waiting for log line. Exit code: %d", inspect.State.ExitCode)
		}
	}

	return ctx.Err()
}

func logSeenLines(logger Logger, lines []string) {
	fmt.Println("Logs seen so far:")
	for _, line := range lines {
		fmt.Println("  " + line)
	}
}


================================================
FILE: cmd/gluetun/main.go
================================================
package main

import (
	"context"
	"errors"
	"fmt"
	"io/fs"
	"net/http"
	"net/netip"
	"os"
	"os/exec"
	"os/signal"
	"strings"
	"syscall"
	"time"
	_ "time/tzdata"

	_ "github.com/breml/rootcerts"
	"github.com/qdm12/dns/v2/pkg/doh"
	dnsprovider "github.com/qdm12/dns/v2/pkg/provider"
	"github.com/qdm12/gluetun/internal/alpine"
	"github.com/qdm12/gluetun/internal/boringpoll"
	"github.com/qdm12/gluetun/internal/cli"
	"github.com/qdm12/gluetun/internal/command"
	"github.com/qdm12/gluetun/internal/configuration/settings"
	"github.com/qdm12/gluetun/internal/configuration/sources/files"
	"github.com/qdm12/gluetun/internal/configuration/sources/secrets"
	"github.com/qdm12/gluetun/internal/constants"
	copenvpn "github.com/qdm12/gluetun/internal/constants/openvpn"
	"github.com/qdm12/gluetun/internal/dns"
	"github.com/qdm12/gluetun/internal/firewall"
	"github.com/qdm12/gluetun/internal/healthcheck"
	"github.com/qdm12/gluetun/internal/httpproxy"
	"github.com/qdm12/gluetun/internal/models"
	"github.com/qdm12/gluetun/internal/netlink"
	"github.com/qdm12/gluetun/internal/openvpn"
	"github.com/qdm12/gluetun/internal/openvpn/extract"
	"github.com/qdm12/gluetun/internal/portforward"
	"github.com/qdm12/gluetun/internal/pprof"
	"github.com/qdm12/gluetun/internal/provider"
	"github.com/qdm12/gluetun/internal/publicip"
	"github.com/qdm12/gluetun/internal/routing"
	"github.com/qdm12/gluetun/internal/server"
	"github.com/qdm12/gluetun/internal/shadowsocks"
	"github.com/qdm12/gluetun/internal/storage"
	"github.com/qdm12/gluetun/internal/tun"
	updater "github.com/qdm12/gluetun/internal/updater/loop"
	"github.com/qdm12/gluetun/internal/updater/resolver"
	"github.com/qdm12/gluetun/internal/updater/unzip"
	"github.com/qdm12/gluetun/internal/vpn"
	"github.com/qdm12/gosettings/reader"
	"github.com/qdm12/gosettings/reader/sources/env"
	"github.com/qdm12/goshutdown"
	"github.com/qdm12/goshutdown/goroutine"
	"github.com/qdm12/goshutdown/group"
	"github.com/qdm12/goshutdown/order"
	"github.com/qdm12/gosplash"
	"github.com/qdm12/log"
)

//nolint:gochecknoglobals
var (
	version = "unknown"
	commit  = "unknown"
	created = "an unknown date"
)

func main() {
	buildInfo := models.BuildInformation{
		Version: version,
		Commit:  commit,
		Created: created,
	}

	background := context.Background()
	signalCh := make(chan os.Signal, 1)
	signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM)
	ctx, cancel := context.WithCancel(background)

	logger := log.New(log.SetLevel(log.LevelInfo))

	args := os.Args
	tun := tun.New()
	netLinkDebugLogger := logger.New(log.SetComponent("netlink"))
	netLinker := netlink.New(netLinkDebugLogger)
	cli := cli.New()
	cmder := command.New()

	reader := reader.New(reader.Settings{
		Sources: []reader.Source{
			secrets.New(logger),
			files.New(logger),
			env.New(env.Settings{}),
		},
		HandleDeprecatedKey: func(source, deprecatedKey, currentKey string) {
			logger.Warn("You are using the old " + source + " " + deprecatedKey +
				", please consider changing it to " + currentKey)
		},
	})

	errorCh := make(chan error)
	go func() {
		errorCh <- _main(ctx, buildInfo, args, logger, reader, tun, netLinker, cmder, cli)
	}()

	// Wait for OS signal or run error
	var err error
	select {
	case receivedSignal := <-signalCh:
		signal.Stop(signalCh)
		fmt.Println("")
		logger.Warn("Caught OS signal " + receivedSignal.String() + ", shutting down")
		cancel()
	case err = <-errorCh:
		close(errorCh)
		if err == nil { // expected exit such as healthcheck
			os.Exit(0)
		}
		logger.Error(err.Error())
		cancel()
	}

	// Shutdown timed sequence, and force exit on second OS signal
	const shutdownGracePeriod = 5 * time.Second
	timer := time.NewTimer(shutdownGracePeriod)
	select {
	case shutdownErr := <-errorCh:
		timer.Stop()
		if shutdownErr != nil {
			logger.Warnf("Shutdown failed: %s", shutdownErr)
			os.Exit(1)
		}

		logger.Info("Shutdown successful")
		if err != nil {
			os.Exit(1)
		}
		os.Exit(0)
	case <-timer.C:
		logger.Warn("Shutdown timed out")
		os.Exit(1)
	}
}

var errCommandUnknown = errors.New("command is unknown")

//nolint:gocognit,gocyclo,maintidx
func _main(ctx context.Context, buildInfo models.BuildInformation,
	args []string, logger log.LoggerInterface, reader *reader.Reader,
	tun Tun, netLinker netLinker, cmder RunStarter,
	cli clier,
) error {
	if len(args) > 1 { // cli operation
		switch args[1] {
		case "healthcheck":
			return cli.HealthCheck(ctx, reader, logger)
		case "clientkey":
			return cli.ClientKey(args[2:])
		case "openvpnconfig":
			return cli.OpenvpnConfig(logger, reader, netLinker)
		case "update":
			return cli.Update(ctx, args[2:], logger)
		case "format-servers":
			return cli.FormatServers(args[2:])
		case "genkey":
			return cli.GenKey(args[2:])
		default:
			return fmt.Errorf("%w: %s", errCommandUnknown, args[1])
		}
	}

	defer fmt.Println(gluetunLogo)

	announcementExp, err := time.Parse(time.RFC3339, "2026-04-30T00:00:00Z")
	if err != nil {
		return err
	}
	splashSettings := gosplash.Settings{
		User:         "qdm12",
		Repository:   "gluetun",
		Emails:       []string{"quentin.mcgaw@gmail.com"},
		Version:      buildInfo.Version,
		Commit:       buildInfo.Commit,
		Created:      buildInfo.Created,
		Announcement: "Set BORINGPOLL_GLUETUNCOM=on to help combat AI slop and shutdown that scam website",
		AnnounceExp:  announcementExp,
		// Sponsor information
		PaypalUser:    "qmcgaw",
		GithubSponsor: "qdm12",
	}
	for _, line := range gosplash.MakeLines(splashSettings) {
		fmt.Println(line)
	}

	var allSettings settings.Settings
	err = allSettings.Read(reader, logger)
	if err != nil {
		return err
	}
	allSettings.SetDefaults()

	// Note: no need to validate minimal settings for the firewall:
	// - global log level is parsed below
	// - firewall Debug and Enabled are booleans parsed from source
	logLevel, err := log.ParseLevel(allSettings.Log.Level)
	if err != nil {
		return fmt.Errorf("log level: %w", err)
	}
	logger.Patch(log.SetLevel(logLevel))
	netLinker.PatchLoggerLevel(logLevel)

	routingLogger := logger.New(log.SetComponent("routing"))
	routingConf := routing.New(netLinker, routingLogger)

	defaultRoutes, err := routingConf.DefaultRoutes()
	if err != nil {
		return err
	}

	localNetworks, err := routingConf.LocalNetworks()
	if err != nil {
		return err
	}

	iptablesLogLevel, _ := log.ParseLevel(allSettings.Firewall.Iptables.LogLevel)
	iptablesLogger := logger.New(log.SetComponent("iptables"), log.SetLevel(iptablesLogLevel))

	firewallLogger := logger.New(log.SetComponent("firewall"))
	firewallConf, err := firewall.NewConfig(ctx, firewallLogger, iptablesLogger, cmder,
		defaultRoutes, localNetworks)
	if err != nil {
		return err
	}

	if *allSettings.Firewall.Enabled {
		err = firewallConf.SetEnabled(ctx, true)
		if err != nil {
			return err
		}
		err = netLinker.FlushConntrack()
		if err != nil {
			logger.Warnf("flushing conntrack failed: %s", err)
		}
	}

	// TODO run this in a loop or in openvpn to reload from file without restarting
	storageLogger := logger.New(log.SetComponent("storage"))
	storage, err := storage.New(storageLogger, *allSettings.Storage.Filepath)
	if err != nil {
		return err
	}

	ipv6Supported, err := netLinker.IsIPv6Supported()
	if err != nil {
		return fmt.Errorf("checking for IPv6 support: %w", err)
	}

	err = allSettings.Validate(storage, ipv6Supported, logger)
	if err != nil {
		return err
	}

	allSettings.Pprof.HTTPServer.Logger = logger.New(log.SetComponent("pprof"))
	pprofServer, err := pprof.New(allSettings.Pprof)
	if err != nil {
		return fmt.Errorf("creating Pprof server: %w", err)
	}

	puid, pgid := int(*allSettings.System.PUID), int(*allSettings.System.PGID)

	const clientTimeout = 35 * time.Second
	httpClient := &http.Client{Timeout: clientTimeout}
	// Create configurators
	alpineConf := alpine.New()
	ovpnConf := openvpn.New(
		logger.New(log.SetComponent("openvpn configurator")),
		cmder, puid, pgid)
	ovpnVersion := ovpnConf.Version26
	if allSettings.VPN.OpenVPN.Version == copenvpn.Openvpn25 {
		ovpnVersion = ovpnConf.Version25
	}

	err = printVersions(ctx, logger, []printVersionElement{
		{name: "Alpine", getVersion: alpineConf.Version},
		{name: "OpenVPN", getVersion: ovpnVersion},
		{name: "Firewall", getVersion: firewallConf.Version},
	})
	if err != nil {
		return err
	}

	logger.Info(allSettings.String())

	for _, warning := range allSettings.Warnings() {
		logger.Warn(warning)
	}

	const permission = fs.FileMode(0o644)
	err = os.MkdirAll("/tmp/gluetun", permission)
	if err != nil {
		return err
	}
	err = os.MkdirAll("/gluetun", permission)
	if err != nil {
		return err
	}

	const defaultUsername = "nonrootuser"
	nonRootUsername, err := alpineConf.CreateUser(defaultUsername, puid)
	if err != nil {
		return fmt.Errorf("creating user: %w", err)
	}
	if nonRootUsername != defaultUsername {
		logger.Info("using existing username " + nonRootUsername + " corresponding to user id " + fmt.Sprint(puid))
	}
	allSettings.VPN.OpenVPN.ProcessUser = nonRootUsername

	if err := routingConf.Setup(); err != nil {
		if strings.Contains(err.Error(), "operation not permitted") {
			logger.Warn("💡 Tip: Are you passing NET_ADMIN capability to gluetun?")
		}
		return fmt.Errorf("setting up routing: %w", err)
	}
	defer func() {
		routingLogger.Info("routing cleanup...")
		if err := routingConf.TearDown(); err != nil {
			routingLogger.Error("cannot teardown routing: " + err.Error())
		}
	}()

	if err := firewallConf.SetOutboundSubnets(ctx, allSettings.Firewall.OutboundSubnets); err != nil {
		return err
	}
	if err := routingConf.SetOutboundRoutes(allSettings.Firewall.OutboundSubnets); err != nil {
		return err
	}

	err = routingConf.AddLocalRules(localNetworks)
	if err != nil {
		return fmt.Errorf("adding local rules: %w", err)
	}

	const tunDevice = "/dev/net/tun"
	err = tun.Check(tunDevice)
	if err != nil {
		if !errors.Is(err, os.ErrNotExist) {
			return fmt.Errorf("checking TUN device: %w (see the Wiki errors/tun page)", err)
		}
		logger.Info(err.Error() + "; creating it...")
		err = tun.Create(tunDevice)
		if err != nil {
			return fmt.Errorf("creating tun device: %w", err)
		}
	}

	for _, port := range allSettings.Firewall.InputPorts {
		for _, defaultRoute := range defaultRoutes {
			err = firewallConf.SetAllowedPort(ctx, port, defaultRoute.NetInterface)
			if err != nil {
				return err
			}
		}
	} // TODO move inside firewall?

	// Shutdown settings
	const totalShutdownTimeout = 3 * time.Second
	const defaultShutdownTimeout = 400 * time.Millisecond
	defaultShutdownOnSuccess := func(goRoutineName string) {
		logger.Info(goRoutineName + ": terminated ✔️")
	}
	defaultShutdownOnFailure := func(goRoutineName string, err error) {
		logger.Warn(goRoutineName + ": " + err.Error() + " ⚠️")
	}
	defaultGroupOptions := []group.Option{
		group.OptionTimeout(defaultShutdownTimeout),
		group.OptionOnSuccess(defaultShutdownOnSuccess),
	}

	controlGroupHandler := goshutdown.NewGroupHandler("control", defaultGroupOptions...)
	tickersGroupHandler := goshutdown.NewGroupHandler("tickers", defaultGroupOptions...)
	otherGroupHandler := goshutdown.NewGroupHandler("other", defaultGroupOptions...)

	if *allSettings.Pprof.Enabled {
		// TODO run in run loop so this can be patched at runtime
		pprofReady := make(chan struct{})
		pprofHandler, pprofCtx, pprofDone := goshutdown.NewGoRoutineHandler("pprof server")
		go pprofServer.Run(pprofCtx, pprofReady, pprofDone)
		otherGroupHandler.Add(pprofHandler)
		<-pprofReady
	}

	portForwardLogger := logger.New(log.SetComponent("port forwarding"))
	portForwardLooper := portforward.NewLoop(allSettings.VPN.Provider.PortForwarding,
		routingConf, httpClient, firewallConf, portForwardLogger, cmder, puid, pgid)
	portForwardRunError, err := portForwardLooper.Start(ctx)
	if err != nil {
		return fmt.Errorf("starting port forwarding loop: %w", err)
	}

	dnsLogger := logger.New(log.SetComponent("dns"))
	dnsLooper, err := dns.NewLoop(allSettings.DNS, httpClient,
		dnsLogger, localNetworksToPrefixes(localNetworks))
	if err != nil {
		return fmt.Errorf("creating DNS loop: %w", err)
	}

	dnsHandler, dnsCtx, dnsDone := goshutdown.NewGoRoutineHandler(
		"dns", goroutine.OptionTimeout(defaultShutdownTimeout))
	// wait for dnsLooper.Restart or its ticker launched with RunRestartTicker
	go dnsLooper.Run(dnsCtx, dnsDone)
	otherGroupHandler.Add(dnsHandler)

	dnsTickerHandler, dnsTickerCtx, dnsTickerDone := goshutdown.NewGoRoutineHandler(
		"dns ticker", goroutine.OptionTimeout(defaultShutdownTimeout))
	go dnsLooper.RunRestartTicker(dnsTickerCtx, dnsTickerDone)
	controlGroupHandler.Add(dnsTickerHandler)

	publicIPLooper, err := publicip.NewLoop(allSettings.PublicIP, puid, pgid, httpClient,
		logger.New(log.SetComponent("ip getter")))
	if err != nil {
		return fmt.Errorf("creating public ip loop: %w", err)
	}
	publicIPRunError, err := publicIPLooper.Start(ctx)
	if err != nil {
		return fmt.Errorf("starting public ip loop: %w", err)
	}

	healthLogger := logger.New(log.SetComponent("healthcheck"))
	healthcheckServer := healthcheck.NewServer(allSettings.Health, healthLogger)
	healthServerHandler, healthServerCtx, healthServerDone := goshutdown.NewGoRoutineHandler(
		"HTTP health server", goroutine.OptionTimeout(defaultShutdownTimeout))
	go healthcheckServer.Run(healthServerCtx, healthServerDone)
	healthChecker := healthcheck.NewChecker(healthLogger)

	// Note: we use a separate DoH dialer for the VPN servers data updater, separate from the
	// main DNS local server to make sure no request is blocked by filters.
	dohDialer, err := doh.New(doh.Settings{
		UpstreamResolvers: []dnsprovider.Provider{dnsprovider.Cloudflare(), dnsprovider.Google()},
	})
	if err != nil {
		return fmt.Errorf("creating updater DoH dialer: %w", err)
	}
	updaterLogger := logger.New(log.SetComponent("updater"))

	unzipper := unzip.New(httpClient)
	parallelResolver := resolver.NewParallelResolver(dohDialer)
	openvpnFileExtractor := extract.New()
	providers := provider.NewProviders(storage, time.Now, updaterLogger,
		httpClient, unzipper, parallelResolver, publicIPLooper.Fetcher(),
		openvpnFileExtractor, allSettings.Updater)

	boringPollLogger := logger.New(log.SetComponent("boring poll"))
	boringPoll := boringpoll.New(httpClient, boringPollLogger, allSettings.BoringPoll)

	vpnLogger := logger.New(log.SetComponent("vpn"))
	vpnLooper := vpn.NewLoop(allSettings.VPN, ipv6Supported, allSettings.Firewall.VPNInputPorts,
		providers, storage, boringPoll, allSettings.Health, healthChecker, healthcheckServer,
		ovpnConf, netLinker, firewallConf, routingConf, portForwardLooper, cmder, publicIPLooper,
		dnsLooper, vpnLogger, httpClient, buildInfo, *allSettings.Version.Enabled)
	vpnHandler, vpnCtx, vpnDone := goshutdown.NewGoRoutineHandler(
		"vpn", goroutine.OptionTimeout(time.Second))
	go vpnLooper.Run(vpnCtx, vpnDone)

	updaterLooper := updater.NewLoop(allSettings.Updater,
		providers, storage, httpClient, updaterLogger)
	updaterHandler, updaterCtx, updaterDone := goshutdown.NewGoRoutineHandler(
		"updater", goroutine.OptionTimeout(defaultShutdownTimeout))
	// wait for updaterLooper.Restart() or its ticket launched with RunRestartTicker
	go updaterLooper.Run(updaterCtx, updaterDone)
	tickersGroupHandler.Add(updaterHandler)

	updaterTickerHandler, updaterTickerCtx, updaterTickerDone := goshutdown.NewGoRoutineHandler(
		"updater ticker", goroutine.OptionTimeout(defaultShutdownTimeout))
	go updaterLooper.RunRestartTicker(updaterTickerCtx, updaterTickerDone)
	controlGroupHandler.Add(updaterTickerHandler)

	httpProxyLooper := httpproxy.NewLoop(
		logger.New(log.SetComponent("http proxy")),
		allSettings.HTTPProxy)
	httpProxyHandler, httpProxyCtx, httpProxyDone := goshutdown.NewGoRoutineHandler(
		"http proxy", goroutine.OptionTimeout(defaultShutdownTimeout))
	go httpProxyLooper.Run(httpProxyCtx, httpProxyDone)
	otherGroupHandler.Add(httpProxyHandler)

	shadowsocksLooper := shadowsocks.NewLoop(allSettings.Shadowsocks,
		logger.New(log.SetComponent("shadowsocks")))
	shadowsocksHandler, shadowsocksCtx, shadowsocksDone := goshutdown.NewGoRoutineHandler(
		"shadowsocks proxy", goroutine.OptionTimeout(defaultShutdownTimeout))
	go shadowsocksLooper.Run(shadowsocksCtx, shadowsocksDone)
	otherGroupHandler.Add(shadowsocksHandler)

	httpServerHandler, httpServerCtx, httpServerDone := goshutdown.NewGoRoutineHandler(
		"http server", goroutine.OptionTimeout(defaultShutdownTimeout))
	httpServer, err := server.New(httpServerCtx, allSettings.ControlServer,
		logger.New(log.SetComponent("http server")),
		buildInfo, vpnLooper, portForwardLooper, dnsLooper, updaterLooper, publicIPLooper,
		storage, ipv6Supported)
	if err != nil {
		return fmt.Errorf("setting up control server: %w", err)
	}
	httpServerReady := make(chan struct{})
	go httpServer.Run(httpServerCtx, httpServerReady, httpServerDone)
	<-httpServerReady
	controlGroupHandler.Add(httpServerHandler)

	orderHandler := goshutdown.NewOrderHandler("gluetun",
		order.OptionTimeout(totalShutdownTimeout),
		order.OptionOnSuccess(defaultShutdownOnSuccess),
		order.OptionOnFailure(defaultShutdownOnFailure))
	orderHandler.Append(controlGroupHandler, tickersGroupHandler, healthServerHandler,
		vpnHandler, otherGroupHandler)

	// Start VPN for the first time in a blocking call
	// until the VPN is launched
	_, _ = vpnLooper.ApplyStatus(ctx, constants.Running) // TODO option to disable with variable

	select {
	case <-ctx.Done():
		stoppers := []interface {
			String() string
			Stop() error
		}{
			portForwardLooper, publicIPLooper,
		}
		for _, stopper := range stoppers {
			err := stopper.Stop()
			if err != nil {
				logger.Error(fmt.Sprintf("stopping %s: %s", stopper, err))
			}
		}
	case err := <-portForwardRunError:
		logger.Errorf("port forwarding loop crashed: %s", err)
	case err := <-publicIPRunError:
		logger.Errorf("public IP loop crashed: %s", err)
	}

	return orderHandler.Shutdown(context.Background())
}

type printVersionElement struct {
	name       string
	getVersion func(ctx context.Context) (version string, err error)
}

type infoer interface {
	Info(s string)
}

func printVersions(ctx context.Context, logger infoer,
	elements []printVersionElement,
) (err error) {
	const timeout = 5 * time.Second
	ctx, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()

	for _, element := range elements {
		version, err := element.getVersion(ctx)
		if err != nil {
			return fmt.Errorf("getting %s version: %w", element.name, err)
		}
		logger.Info(element.name + " version: " + version)
	}

	return nil
}

func localNetworksToPrefixes(localNetworks []routing.LocalNetwork) (prefixes []netip.Prefix) {
	prefixes = make([]netip.Prefix, len(localNetworks))
	for i, localNetwork := range localNetworks {
		prefixes[i] = localNetwork.IPNet
	}
	return prefixes
}

type netLinker interface {
	Addresser
	Router
	Ruler
	Linker
	IsWireguardSupported() (ok bool, err error)
	IsIPv6Supported() (ok bool, err error)
	FlushConntrack() error
	PatchLoggerLevel(level log.Level)
}

type Addresser interface {
	AddrList(linkIndex uint32, family uint8) (
		addresses []netip.Prefix, err error)
	AddrReplace(linkIndex uint32, addr netip.Prefix) error
}

type Router interface {
	RouteList(family uint8) (routes []netlink.Route, err error)
	RouteAdd(route netlink.Route) error
	RouteDel(route netlink.Route) error
	RouteReplace(route netlink.Route) error
}

type Ruler interface {
	RuleList(family uint8) (rules []netlink.Rule, err error)
	RuleAdd(rule netlink.Rule) error
	RuleDel(rule netlink.Rule) error
}

type Linker interface {
	LinkList() (links []netlink.Link, err error)
	LinkByName(name string) (link netlink.Link, err error)
	LinkByIndex(index uint32) (link netlink.Link, err error)
	LinkAdd(link netlink.Link) (linkIndex uint32, err error)
	LinkDel(linkIndex uint32) (err error)
	LinkSetUp(linkIndex uint32) (err error)
	LinkSetDown(linkIndex uint32) (err error)
	LinkSetMTU(linkIndex, mtu uint32) error
}

type clier interface {
	ClientKey(args []string) error
	FormatServers(args []string) error
	OpenvpnConfig(logger cli.OpenvpnConfigLogger, reader *reader.Reader, ipv6Checker cli.IPv6Checker) error
	HealthCheck(ctx context.Context, reader *reader.Reader, warner cli.Warner) error
	Update(ctx context.Context, args []string, logger cli.UpdaterLogger) error
	GenKey(args []string) error
}

type Tun interface {
	Check(tunDevice string) error
	Create(tunDevice string) error
}

type RunStarter interface {
	Run(cmd *exec.Cmd) (output string, err error)
	Start(cmd *exec.Cmd) (stdoutLines, stderrLines <-chan string,
		waitError <-chan error, err error)
	RunAndLog(ctx context.Context, commandString string,
		logger command.Logger) (err error)
}

const gluetunLogo = `                         @@@
                         @@@@
                        @@@@@@
                       @@@@.@@                       @@@@@@@@@@
                       @@@@.@@@                   @@@@@@@@==@@@@
                      @@@.@..@@                @@@@@@@=@..==@@@@
            @@@@      @@@.@@.@@              @@@@@@===@@@@.=@@@
           @...-@@   @@@@.@@.@@@  @@@     @@@@@@=======@@@=@@@@
           @@@@@@@@  @@@.-%@.+@@@@@@@@  @@@@@%============@@@@
                     @@@.--@..@@@@.-@@@@@@@==============@@@@
              @@@@  @@@-@--@@.@@.---@@@@@==============#@@@@@
              @@@   @@@.@@-@@.@@--@@@@@===============@@@@@@
                   @@@@.@--@@@@@@@@@@================@@@@@@@
                   @@@..--@@*@@@@@@================@@@@+*@@
                   @@@.---@@.@@@@=================@@@@--@@
                  @@@-.---@@@@@@================@@@@*--@@@
                  @@@.:-#@@@@@@===============*@@@@.---@@
                  @@@.-------.@@@============@@@@@@.--@@@
                 @@@..--------:@@@=========@@@@@@@@.--@@@
                 @@@.-@@@@@@@@@@@========@@@@@  @@@.--@@
                 @@.@@@@===============@@@@@ @@@@@@---@@@@@@
                @@@@@@@==============@@@@@@@@@@@@*@---@@@@@@@@
                @@@@@@=============@@@@@ @@@...------------.*@@@
                @@@@%===========@@@@@@ @@@..------@@@@.-----.-@@@
                @@@@@@.=======@@@@@@  @@@.-------@@@@@@-.------=@@
               @@@@@@@@@===@@@@@@     @@.------@@@@   @@@@.-----@@@
               @@@==@@@=@@@@@@@      @@@.-@@@@@@@       @@@@@@@--@@
               @@@@@@@@@@@@@         @@@@@@@@                @@@@@@@
                @@@@@@@@             @@@@                       @@@@
                                                                       `


================================================
FILE: go.mod
================================================
module github.com/qdm12/gluetun

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
	github.com/jsimonetti/rtnetlink v1.4.2
	github.com/klauspost/compress v1.18.4
	github.com/klauspost/pgzip v1.2.6
	github.com/mdlayher/genetlink v1.3.2
	github.com/mdlayher/netlink v1.9.0
	github.com/pelletier/go-toml/v2 v2.2.4
	github.com/qdm12/dns/v2 v2.0.0-rc9.0.20260310123525-76fabc2b3294
	github.com/qdm12/gosettings v0.4.4
	github.com/qdm12/goshutdown v0.3.0
	github.com/qdm12/gosplash v0.2.1-0.20260305164749-b713de4fee6c
	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.11.1
	github.com/ti-mo/netfilter v0.5.3
	github.com/ulikunitz/xz v0.5.15
	github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
	golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c
	golang.org/x/net v0.51.0
	golang.org/x/sys v0.42.0
	golang.org/x/text v0.35.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.1
)

require (
	github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf // indirect
	github.com/ProtonMail/go-crypto v1.3.0-proton // indirect
	github.com/beorn7/perks v1.0.1 // indirect
	github.com/cespare/xxhash/v2 v2.3.0 // indirect
	github.com/cloudflare/circl v1.6.1 // indirect
	github.com/cronokirby/saferith v0.33.0 // indirect
	github.com/davecgh/go-spew v1.1.1 // indirect
	github.com/google/go-cmp v0.7.0 // indirect
	github.com/mattn/go-colorable v0.1.13 // indirect
	github.com/mattn/go-isatty v0.0.20 // indirect
	github.com/mdlayher/socket v0.5.1 // indirect
	github.com/miekg/dns v1.1.62 // indirect
	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
	github.com/pkg/errors v0.9.1 // indirect
	github.com/pmezard/go-difflib v1.0.0 // indirect
	github.com/prometheus/client_golang v1.20.5 // indirect
	github.com/prometheus/client_model v0.6.1 // indirect
	github.com/prometheus/common v0.60.1 // indirect
	github.com/prometheus/procfs v0.15.1 // indirect
	github.com/qdm12/goservices v0.1.1-0.20251104135713-6bee97bd4978 // indirect
	github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
	golang.org/x/crypto v0.48.0 // indirect
	golang.org/x/mod v0.33.0 // indirect
	golang.org/x/sync v0.20.0 // indirect
	golang.org/x/tools v0.42.0 // indirect
	golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
	google.golang.org/protobuf v1.35.1 // indirect
	gopkg.in/yaml.v3 v3.0.1 // indirect
	kernel.org/pub/linux/libs/security/libcap/cap v1.2.70 // indirect
	kernel.org/pub/linux/libs/security/libcap/psx v1.2.70 // indirect
)


================================================
FILE: go.sum
================================================
github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57/go.mod h1:HecWFHognK8GfRDGnFQbW/LiV7A3MX3gZVs45vk5h8I=
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf h1:yc9daCCYUefEs69zUkSzubzjBbL+cmOXgnmt9Fyd9ug=
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo=
github.com/ProtonMail/go-crypto v0.0.0-20230321155629-9a39f2531310/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE=
github.com/ProtonMail/go-crypto v1.3.0-proton h1:tAQKQRZX/73VmzK6yHSCaRUOvS/3OYSQzhXQsrR7yUM=
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=
github.com/breml/rootcerts v0.3.4/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
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/cilium/ebpf v0.12.3 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4=
github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM=
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cronokirby/saferith v0.33.0 h1:TgoQlfsD4LIwx71+ChfRcIpjkw+RPOapDEVxa+LhwLo=
github.com/cronokirby/saferith v0.33.0/go.mod h1:QKJhjoqUtBsXCAVEjw38mFqoi7DebT7kthcD7UzbnoA=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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.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/jsimonetti/rtnetlink v1.4.2 h1:Df9w9TZ3npHTyDn0Ev9e1uzmN2odmXd0QX+J5GTEn90=
github.com/jsimonetti/rtnetlink v1.4.2/go.mod h1:92s6LJdE+1iOrw+F2/RO7LYI2Qd8pPpFNNUYW06gcoM=
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw=
github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o=
github.com/mdlayher/netlink v1.9.0 h1:G8+GLq2x3v4D4MVIqDdNUhTUC7TKiCy/6MDkmItfKco=
github.com/mdlayher/netlink v1.9.0/go.mod h1:YBnl5BXsCoRuwBjKKlZ+aYmEoq0r12FDA/3JC+94KDg=
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc=
github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/qdm12/dns/v2 v2.0.0-rc9.0.20260310123525-76fabc2b3294 h1:adkCP7N9mEHpsKSR/5LToF27qJo0yOufhT5zBdKpyrE=
github.com/qdm12/dns/v2 v2.0.0-rc9.0.20260310123525-76fabc2b3294/go.mod h1:98foWgXJZ+g8gJIuO+fdO+oWpFei5WShMFTeN4Im2lE=
github.com/qdm12/goservices v0.1.1-0.20251104135713-6bee97bd4978 h1:TRGpCU1l0lNwtogEUSs5U+RFceYxkAJUmrGabno7J5c=
github.com/qdm12/goservices v0.1.1-0.20251104135713-6bee97bd4978/go.mod h1:D1Po4CRQLYjccnAR2JsVlN1sBMgQrcNLONbvyuzcdTg=
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.1-0.20260305164749-b713de4fee6c h1:l8qz53IqEXRGK0X62gWwipG077Fz5eNM7qe4mUbAr/Q=
github.com/qdm12/gosplash v0.2.1-0.20260305164749-b713de4fee6c/go.mod h1:vgRg8Skq9+RNp1THecwMI7SGsnIwO/NPMfYenNTgpAc=
github.com/qdm12/gotree v0.3.0 h1:Q9f4C571EFK7ZEsPkEL2oGZX7I+ZhVxhh1ZSydW+5yI=
github.com/qdm12/gotree v0.3.0/go.mod h1:iz06uXmRR4Aq9v6tX7mosXStO/yGHxRA1hbyD0UVeYw=
github.com/qdm12/log v0.1.0 h1:jYBd/xscHYpblzZAd2kjZp2YmuYHjAAfbTViJWxoPTw=
github.com/qdm12/log v0.1.0/go.mod h1:Vchi5M8uBvHfPNIblN4mjXn/oSbiWguQIbsgF1zdQPI=
github.com/qdm12/ss-server v0.6.0 h1:OaOdCIBXx0z3DGHPT6Th0v88vGa3MtAS4oRgUsDHGZE=
github.com/qdm12/ss-server v0.6.0/go.mod h1:0BO/zEmtTiLDlmQEcjtoHTC+w+cWxwItjBuGP6TWM78=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
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/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/ti-mo/netfilter v0.5.3 h1:ikzduvnaUMwre5bhbNwWOd6bjqLMVb33vv0XXbK0xGQ=
github.com/ti-mo/netfilter v0.5.3/go.mod h1:08SyBCg6hu1qyQk4s3DjjJKNrm3RTb32nm6AzyT972E=
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk=
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=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
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=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
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=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
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.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
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=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80=
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/ini.v1 v1.67.1 h1:tVBILHy0R6e4wkYOn3XmiITt/hEVH4TFMYvAX2Ytz6k=
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-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=
kernel.org/pub/linux/libs/security/libcap/psx v1.2.70/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24=


================================================
FILE: internal/alpine/alpine.go
================================================
package alpine

import (
	"os/user"
)

type Alpine struct {
	alpineReleasePath string
	passwdPath        string
	lookupID          func(uid string) (*user.User, error)
	lookup            func(username string) (*user.User, error)
}

func New() *Alpine {
	return &Alpine{
		alpineReleasePath: "/etc/alpine-release",
		passwdPath:        "/etc/passwd",
		lookupID:          user.LookupId,
		lookup:            user.Lookup,
	}
}


================================================
FILE: internal/alpine/users.go
================================================
package alpine

import (
	"errors"
	"fmt"
	"io/fs"
	"os"
	"os/user"
	"strconv"
)

var ErrUserAlreadyExists = errors.New("user already exists")

// CreateUser creates a user in Alpine with the given UID.
func (a *Alpine) CreateUser(username string, uid int) (createdUsername string, err error) {
	UIDStr := strconv.Itoa(uid)
	u, err := a.lookupID(UIDStr)
	_, unknownUID := err.(user.UnknownUserIdError)
	if err != nil && !unknownUID {
		return "", err
	}

	if u != nil {
		if u.Username == username {
			return "", nil
		}
		return u.Username, nil
	}

	u, err = a.lookup(username)
	_, unknownUsername := err.(user.UnknownUserError)
	if err != nil && !unknownUsername {
		return "", err
	}

	if u != nil {
		return "", fmt.Errorf("%w: with name %s for ID %s instead of %d",
			ErrUserAlreadyExists, username, u.Uid, uid)
	}

	const permission = fs.FileMode(0o644)
	file, err := os.OpenFile(a.passwdPath, os.O_APPEND|os.O_WRONLY, permission)
	if err != nil {
		return "", err
	}
	s := fmt.Sprintf("%s:x:%d:::/dev/null:/sbin/nologin\n", username, uid)
	_, err = file.WriteString(s)
	if err != nil {
		_ = file.Close()
		return "", err
	}

	return username, file.Close()
}


================================================
FILE: internal/alpine/version.go
================================================
package alpine

import (
	"context"
	"io"
	"os"
	"strings"
)

func (a *Alpine) Version(context.Context) (version string, err error) {
	file, err := os.OpenFile(a.alpineReleasePath, os.O_RDONLY, 0)
	if err != nil {
		return "", err
	}

	b, err := io.ReadAll(file)
	if err != nil {
		return "", err
	}

	if err := file.Close(); err != nil {
		return "", err
	}

	version = strings.ReplaceAll(string(b), "\n", "")
	return version, nil
}


================================================
FILE: internal/amneziawg/constructor.go
================================================
package amneziawg

type Amneziawg struct {
	logger   Logger
	settings Settings
	netlink  NetLinker
}

func New(settings Settings, netlink NetLinker,
	logger Logger,
) (a *Amneziawg, err error) {
	settings.SetDefaults()
	if err := settings.Check(); err != nil {
		return nil, err
	}

	return &Amneziawg{
		logger:   logger,
		settings: settings,
		netlink:  netlink,
	}, nil
}


================================================
FILE: internal/amneziawg/constructor_test.go
================================================
package amneziawg

import (
	"net/netip"
	"testing"

	"github.com/qdm12/gluetun/internal/wireguard"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"golang.zx2c4.com/wireguard/device"
)

func Test_New(t *testing.T) {
	t.Parallel()

	const validKeyString = "oMNSf/zJ0pt1ciy+qIRk8Rlyfs9accwuRLnKd85Yl1Q="
	logger := NewMockLogger(nil)
	netLinker := NewMockNetLinker(nil)

	testCases := map[string]struct {
		settings  Settings
		amneziawg *Amneziawg
		err       error
	}{
		"bad_settings": {
			settings: Settings{
				Wireguard: wireguard.Settings{
					PrivateKey: "",
				},
			},
			err: wireguard.ErrPrivateKeyMissing,
		},
		"minimal valid settings": {
			settings: Settings{
				Wireguard: wireguard.Settings{
					PrivateKey: validKeyString,
					PublicKey:  validKeyString,
					Endpoint:   netip.AddrPortFrom(netip.AddrFrom4([4]byte{1, 2, 3, 4}), 0),
					Addresses: []netip.Prefix{
						netip.PrefixFrom(netip.AddrFrom4([4]byte{5, 6, 7, 8}), 32),
					},
					FirewallMark: 100,
				},
			},
			amneziawg: &Amneziawg{
				logger:  logger,
				netlink: netLinker,
				settings: Settings{
					Wireguard: wireguard.Settings{
						InterfaceName: "wg0",
						PrivateKey:    validKeyString,
						PublicKey:     validKeyString,
						Endpoint:      netip.AddrPortFrom(netip.AddrFrom4([4]byte{1, 2, 3, 4}), 51820),
						Addresses: []netip.Prefix{
							netip.PrefixFrom(netip.AddrFrom4([4]byte{5, 6, 7, 8}), 32),
						},
						AllowedIPs: []netip.Prefix{
							netip.MustParsePrefix("0.0.0.0/0"),
						},
						FirewallMark:   100,
						MTU:            device.DefaultMTU,
						IPv6:           ptrTo(false),
						Implementation: "auto",
					},
				},
			},
		},
	}

	for name, testCase := range testCases {
		t.Run(name, func(t *testing.T) {
			t.Parallel()

			wireguard, err := New(testCase.settings, netLinker, logger)

			if testCase.err != nil {
				require.Error(t, err)
				assert.Equal(t, testCase.err.Error(), err.Error())
			} else {
				assert.NoError(t, err)
			}

			assert.Equal(t, testCase.amneziawg, wireguard)
		})
	}
}


================================================
FILE: internal/amneziawg/helpers_test.go
================================================
package amneziawg

func ptrTo[T any](v T) *T {
	return &v
}


================================================
FILE: internal/amneziawg/log.go
================================================
package amneziawg

//go:generate mockgen -destination=log_mock_test.go -package amneziawg . Logger

type Logger interface {
	Debug(s string)
	Debugf(format string, args ...interface{})
	Info(s string)
	Error(s string)
	Errorf(format string, args ...interface{})
}


================================================
FILE: internal/amneziawg/log_mock_test.go
================================================
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/qdm12/gluetun/internal/amneziawg (interfaces: Logger)

// Package amneziawg is a generated GoMock package.
package amneziawg

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)
}

// Debugf mocks base method.
func (m *MockLogger) Debugf(arg0 string, arg1 ...interface{}) {
	m.ctrl.T.Helper()
	varargs := []interface{}{arg0}
	for _, a := range arg1 {
		varargs = append(varargs, a)
	}
	m.ctrl.Call(m, "Debugf", varargs...)
}

// Debugf indicates an expected call of Debugf.
func (mr *MockLoggerMockRecorder) Debugf(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	varargs := append([]interface{}{arg0}, arg1...)
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Debugf", reflect.TypeOf((*MockLogger)(nil).Debugf), varargs...)
}

// 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)
}

// Errorf mocks base method.
func (m *MockLogger) Errorf(arg0 string, arg1 ...interface{}) {
	m.ctrl.T.Helper()
	varargs := []interface{}{arg0}
	for _, a := range arg1 {
		varargs = append(varargs, a)
	}
	m.ctrl.Call(m, "Errorf", varargs...)
}

// Errorf indicates an expected call of Errorf.
func (mr *MockLoggerMockRecorder) Errorf(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	varargs := append([]interface{}{arg0}, arg1...)
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Errorf", reflect.TypeOf((*MockLogger)(nil).Errorf), varargs...)
}

// 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)
}


================================================
FILE: internal/amneziawg/netlinker.go
================================================
package amneziawg

import (
	"net/netip"

	"github.com/qdm12/gluetun/internal/netlink"
)

//go:generate mockgen -destination=netlinker_mock_test.go -package amneziawg . NetLinker

type NetLinker interface {
	AddrReplace(linkIndex uint32, addr netip.Prefix) error
	Router
	Ruler
	Linker
	IsWireguardSupported() (ok bool, err error)
}

type Router interface {
	RouteList(family uint8) (routes []netlink.Route, err error)
	RouteAdd(route netlink.Route) error
}

type Ruler interface {
	RuleAdd(rule netlink.Rule) error
	RuleDel(rule netlink.Rule) error
}

type Linker interface {
	LinkAdd(link netlink.Link) (linkIndex uint32, err error)
	LinkList() (links []netlink.Link, err error)
	LinkByName(name string) (link netlink.Link, err error)
	LinkSetUp(linkIndex uint32) error
	LinkSetDown(linkIndex uint32) error
	LinkDel(linkIndex uint32) error
}


================================================
FILE: internal/amneziawg/netlinker_mock_test.go
================================================
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/qdm12/gluetun/internal/amneziawg (interfaces: NetLinker)

// Package amneziawg is a generated GoMock package.
package amneziawg

import (
	netip "net/netip"
	reflect "reflect"

	gomock "github.com/golang/mock/gomock"
	netlink "github.com/qdm12/gluetun/internal/netlink"
)

// MockNetLinker is a mock of NetLinker interface.
type MockNetLinker struct {
	ctrl     *gomock.Controller
	recorder *MockNetLinkerMockRecorder
}

// MockNetLinkerMockRecorder is the mock recorder for MockNetLinker.
type MockNetLinkerMockRecorder struct {
	mock *MockNetLinker
}

// NewMockNetLinker creates a new mock instance.
func NewMockNetLinker(ctrl *gomock.Controller) *MockNetLinker {
	mock := &MockNetLinker{ctrl: ctrl}
	mock.recorder = &MockNetLinkerMockRecorder{mock}
	return mock
}

// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockNetLinker) EXPECT() *MockNetLinkerMockRecorder {
	return m.recorder
}

// AddrReplace mocks base method.
func (m *MockNetLinker) AddrReplace(arg0 uint32, arg1 netip.Prefix) error {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "AddrReplace", arg0, arg1)
	ret0, _ := ret[0].(error)
	return ret0
}

// AddrReplace indicates an expected call of AddrReplace.
func (mr *MockNetLinkerMockRecorder) AddrReplace(arg0, arg1 interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddrReplace", reflect.TypeOf((*MockNetLinker)(nil).AddrReplace), arg0, arg1)
}

// IsWireguardSupported mocks base method.
func (m *MockNetLinker) IsWireguardSupported() (bool, error) {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "IsWireguardSupported")
	ret0, _ := ret[0].(bool)
	ret1, _ := ret[1].(error)
	return ret0, ret1
}

// IsWireguardSupported indicates an expected call of IsWireguardSupported.
func (mr *MockNetLinkerMockRecorder) IsWireguardSupported() *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsWireguardSupported", reflect.TypeOf((*MockNetLinker)(nil).IsWireguardSupported))
}

// LinkAdd mocks base method.
func (m *MockNetLinker) LinkAdd(arg0 netlink.Link) (uint32, error) {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "LinkAdd", arg0)
	ret0, _ := ret[0].(uint32)
	ret1, _ := ret[1].(error)
	return ret0, ret1
}

// LinkAdd indicates an expected call of LinkAdd.
func (mr *MockNetLinkerMockRecorder) LinkAdd(arg0 interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LinkAdd", reflect.TypeOf((*MockNetLinker)(nil).LinkAdd), arg0)
}

// LinkByName mocks base method.
func (m *MockNetLinker) LinkByName(arg0 string) (netlink.Link, error) {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "LinkByName", arg0)
	ret0, _ := ret[0].(netlink.Link)
	ret1, _ := ret[1].(error)
	return ret0, ret1
}

// LinkByName indicates an expected call of LinkByName.
func (mr *MockNetLinkerMockRecorder) LinkByName(arg0 interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LinkByName", reflect.TypeOf((*MockNetLinker)(nil).LinkByName), arg0)
}

// LinkDel mocks base method.
func (m *MockNetLinker) LinkDel(arg0 uint32) error {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "LinkDel", arg0)
	ret0, _ := ret[0].(error)
	return ret0
}

// LinkDel indicates an expected call of LinkDel.
func (mr *MockNetLinkerMockRecorder) LinkDel(arg0 interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LinkDel", reflect.TypeOf((*MockNetLinker)(nil).LinkDel), arg0)
}

// LinkList mocks base method.
func (m *MockNetLinker) LinkList() ([]netlink.Link, error) {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "LinkList")
	ret0, _ := ret[0].([]netlink.Link)
	ret1, _ := ret[1].(error)
	return ret0, ret1
}

// LinkList indicates an expected call of LinkList.
func (mr *MockNetLinkerMockRecorder) LinkList() *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LinkList", reflect.TypeOf((*MockNetLinker)(nil).LinkList))
}

// LinkSetDown mocks base method.
func (m *MockNetLinker) LinkSetDown(arg0 uint32) error {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "LinkSetDown", arg0)
	ret0, _ := ret[0].(error)
	return ret0
}

// LinkSetDown indicates an expected call of LinkSetDown.
func (mr *MockNetLinkerMockRecorder) LinkSetDown(arg0 interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LinkSetDown", reflect.TypeOf((*MockNetLinker)(nil).LinkSetDown), arg0)
}

// LinkSetUp mocks base method.
func (m *MockNetLinker) LinkSetUp(arg0 uint32) error {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "LinkSetUp", arg0)
	ret0, _ := ret[0].(error)
	return ret0
}

// LinkSetUp indicates an expected call of LinkSetUp.
func (mr *MockNetLinkerMockRecorder) LinkSetUp(arg0 interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LinkSetUp", reflect.TypeOf((*MockNetLinker)(nil).LinkSetUp), arg0)
}

// RouteAdd mocks base method.
func (m *MockNetLinker) RouteAdd(arg0 netlink.Route) error {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "RouteAdd", arg0)
	ret0, _ := ret[0].(error)
	return ret0
}

// RouteAdd indicates an expected call of RouteAdd.
func (mr *MockNetLinkerMockRecorder) RouteAdd(arg0 interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RouteAdd", reflect.TypeOf((*MockNetLinker)(nil).RouteAdd), arg0)
}

// RouteList mocks base method.
func (m *MockNetLinker) RouteList(arg0 byte) ([]netlink.Route, error) {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "RouteList", arg0)
	ret0, _ := ret[0].([]netlink.Route)
	ret1, _ := ret[1].(error)
	return ret0, ret1
}

// RouteList indicates an expected call of RouteList.
func (mr *MockNetLinkerMockRecorder) RouteList(arg0 interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RouteList", reflect.TypeOf((*MockNetLinker)(nil).RouteList), arg0)
}

// RuleAdd mocks base method.
func (m *MockNetLinker) RuleAdd(arg0 netlink.Rule) error {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "RuleAdd", arg0)
	ret0, _ := ret[0].(error)
	return ret0
}

// RuleAdd indicates an expected call of RuleAdd.
func (mr *MockNetLinkerMockRecorder) RuleAdd(arg0 interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RuleAdd", reflect.TypeOf((*MockNetLinker)(nil).RuleAdd), arg0)
}

// RuleDel mocks base method.
func (m *MockNetLinker) RuleDel(arg0 netlink.Rule) error {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "RuleDel", arg0)
	ret0, _ := ret[0].(error)
	return ret0
}

// RuleDel indicates an expected call of RuleDel.
func (mr *MockNetLinkerMockRecorder) RuleDel(arg0 interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RuleDel", reflect.TypeOf((*MockNetLinker)(nil).RuleDel), arg0)
}


================================================
FILE: internal/amneziawg/run.go
================================================
package amneziawg

import (
	"context"
	"errors"
	"fmt"
	"net"

	amneziaconn "github.com/amnezia-vpn/amneziawg-go/conn"
	amneziadevice "github.com/amnezia-vpn/amneziawg-go/device"
	amneziatun "github.com/amnezia-vpn/amneziawg-go/tun"
	"github.com/qdm12/gluetun/internal/cleanup"
	"github.com/qdm12/gluetun/internal/wireguard"
)

var (
	errTunNameMismatch = errors.New("TUN device name is mismatching")
	errDeviceWaited    = errors.New("device waited for")
)

// Run runs the amneziawg interface and waits until the context is done, then it cleans up the
// interface and returns any error that occurred during setup or waiting. It sends an error to
// waitError if any error occurs during setup or waiting, otherwise it sends nil when the context
// is done. It sends a signal to ready when the setup is complete and the interface is ready to use.
// See https://github.com/amnezia-vpn/amneziawg-go/blob/master/main.go
func (a *Amneziawg) Run(ctx context.Context, waitError chan<- error, ready chan<- struct{}) {
	setup := func(ctx context.Context, cleanups *cleanup.Cleanups) (
		linkIndex uint32, waitAndCleanup func() error, err error,
	) {
		return setupUserspace(ctx, a.settings.Wireguard.InterfaceName,
			a.netlink, a.settings.Wireguard.MTU, cleanups, a.logger, a.settings)
	}

	wireguard.Run(ctx, waitError, ready, setup, a.settings.Wireguard, a.netlink, a.logger)
}

func setupUserspace(ctx context.Context,
	interfaceName string, netLinker NetLinker, mtu uint32,
	cleanups *cleanup.Cleanups, logger Logger,
	settings Settings,
) (
	linkIndex uint32, waitAndCleanup func() error, err error,
) {
	tun, err := amneziatun.CreateTUN(interfaceName, int(mtu))
	if err != nil {
		return 0, nil, fmt.Errorf("creating TUN device: %w", err)
	}

	cleanups.Add("closing TUN device", 7, tun.Close)

	tunName, err := tun.Name()
	if err != nil {
		return 0, nil, fmt.Errorf("getting created TUN device name: %w", err)
	} else if tunName != interfaceName {
		return 0, nil, fmt.Errorf("%w: expected %q and got %q",
			errTunNameMismatch, interfaceName, tunName)
	}

	link, err := netLinker.LinkByName(interfaceName)
	if err != nil {
		return 0, nil, fmt.Errorf("finding link %s: %w", interfaceName, err)
	}
	cleanups.Add("deleting link", 5, func() error {
		return netLinker.LinkDel(link.Index)
	})

	bind := amneziaconn.NewDefaultBind()
	cleanups.Add("closing bind", 7, bind.Close)

	deviceLogger := amneziadevice.Logger{
		Verbosef: logger.Debugf,
		Errorf:   logger.Errorf,
	}
	device := amneziadevice.NewDevice(tun, bind, &deviceLogger)

	cleanups.Add("closing Wireguard device", 6, func() error {
		device.Close()
		return nil
	})

	uapiFile, err := wireguard.UAPIOpen(interfaceName)
	if err != nil {
		return 0, nil, fmt.Errorf("opening UAPI socket: %w", err)
	}
	cleanups.Add("closing UAPI file", 3, uapiFile.Close)

	uapiListener, err := wireguard.UAPIListen(interfaceName, uapiFile)
	if err != nil {
		return 0, nil, fmt.Errorf("listening on UAPI socket: %w", err)
	}
	cleanups.Add("closing UAPI listener", 2, uapiListener.Close)

	uapiConfig := settings.uapiConfig()
	err = device.IpcSet(uapiConfig)
	if err != nil {
		return 0, nil, fmt.Errorf("setting amneziawg uapi config: %w", err)
	}

	// acceptAndHandle exits when uapiListener is closed
	uapiAcceptErrorCh := make(chan error)
	go acceptAndHandle(uapiListener, device, uapiAcceptErrorCh)
	waitAndCleanup = func() error {
		select {
		case <-ctx.Done():
			err = ctx.Err()
		case err = <-uapiAcceptErrorCh:
			close(uapiAcceptErrorCh)
		case <-device.Wait():
			err = errDeviceWaited
		}

		cleanups.Cleanup(logger)

		<-uapiAcceptErrorCh // wait for acceptAndHandle to exit

		return err
	}

	return link.Index, waitAndCleanup, nil
}

func acceptAndHandle(uapi net.Listener, device *amneziadevice.Device,
	uapiAcceptErrorCh chan<- error,
) {
	for { // stopped by uapiFile.Close()
		conn, err := uapi.Accept()
		if err != nil {
			uapiAcceptErrorCh <- err
			return
		}
		go device.IpcHandle(conn)
	}
}


================================================
FILE: internal/amneziawg/settings.go
================================================
package amneziawg

import (
	"fmt"
	"strings"

	"github.com/qdm12/gluetun/internal/wireguard"
)

type Settings struct {
	Wireguard       wireguard.Settings
	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 Settings) 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")
}

func (s *Settings) SetDefaults() {
	s.Wireguard.SetDefaults()
}

func (s *Settings) Check() error {
	return s.Wireguard.Check()
}


================================================
FILE: internal/boringpoll/boringpoll.go
================================================
package boringpoll

import (
	"context"
	"fmt"
	"io"
	"math/rand"
	"net/http"
	"sync"
	"time"

	"github.com/qdm12/gluetun/internal/configuration/settings"
)

type BoringPoll struct {
	// Injected dependencies
	client *http.Client
	logger Logger

	// Internal state
	urlToData map[string]*urlData

	// Internal signals and channels
	cancel context.CancelFunc
	done   <-chan struct{}
	mutex  sync.Mutex
}

type urlData struct{}

func New(client *http.Client, logger Logger, settings settings.BoringPoll) *BoringPoll {
	urlToData := make(map[string]*urlData)
	if *settings.GluetunCom {
		urlToData["https://gluetun.com/wp-json"] = &urlData{}
	}
	return &BoringPoll{
		client:    client,
		logger:    logger,
		urlToData: urlToData,
	}
}

func (b *BoringPoll) Start() (runError <-chan error, err error) {
	b.mutex.Lock()
	defer b.mutex.Unlock()

	if len(b.urlToData) == 0 {
		return nil, nil //nolint:nilnil
	}

	const minPeriod = time.Minute
	const maxPeriod = 5 * time.Minute
	const logEveryBytes = 100 * 1000 * 1000 // 100 IEC MB

	var ready, done sync.WaitGroup
	ready.Add(len(b.urlToData))
	done.Add(len(b.urlToData))
	ctx, cancel := context.WithCancel(context.Background())
	b.cancel = cancel
	for url := range b.urlToData {
		go func(url string) {
			defer done.Done()

			b.logger.Infof("running against %s periodically between %s and %s "+
				"and will log every %s downloaded",
				url, minPeriod, maxPeriod, byteCountSI(logEveryBytes))
			totalDownloaded := uint64(0)
			lastDownloaded := uint64(0)
			consecutiveFails := 0
			const maxConsecutiveErrs = 3
			const coolDownTimeout = time.Hour
			timer := time.NewTimer(time.Hour)
			var err error

			ready.Done()
			for {
				timeout := minPeriod + time.Duration(rand.Int63n(int64(maxPeriod-minPeriod))) //nolint:gosec
				if consecutiveFails >= maxConsecutiveErrs {
					b.logger.Debugf("pausing poll to %s for %s due to %d consecutive errors, last error: %s",
						url, coolDownTimeout, consecutiveFails, err)
					timeout = coolDownTimeout
				}
				timer.Reset(timeout)
				select {
				case <-ctx.Done():
					timer.Stop()
					totalDownloaded += lastDownloaded
					if totalDownloaded > 0 {
						b.logger.Infof("stopping poll to %s, downloaded %s!", url, byteCountSI(totalDownloaded))
					}
					return
				case <-timer.C:
				}
				var n int64
				n, err = fetchURL(ctx, b.client, url)
				if err != nil {
					consecutiveFails++
					continue
				}
				consecutiveFails = 0
				totalDownloaded += uint64(n) //nolint:gosec
				lastDownloaded += uint64(n)  //nolint:gosec
				if lastDownloaded >= logEveryBytes {
					b.logger.Infof("thanks for helping! You have downloaded %s from %s so far!",
						byteCountSI(totalDownloaded), url)
					lastDownloaded = 0
				}
			}
		}(url)
	}
	return nil, nil //nolint:nilnil
}

func fetchURL(ctx context.Context, client *http.Client, url string) (downloaded int64, err error) {
	const requestTimeout = 10 * time.Second
	ctx, cancel := context.WithTimeout(ctx, requestTimeout)
	defer cancel()
	request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		cancel()
		return 0, err
	}
	request.Header.Set("Cache-Control", "no-cache, no-store, must-revalidate")
	request.Header.Set("Pragma", "no-cache")
	request.Header.Set("Expires", "0")
	request.Header.Set("User-Agent", getRandomUserAgent())

	response, err := client.Do(request)
	if err != nil {
		return 0, err
	}
	downloaded, err = io.Copy(io.Discard, response.Body)
	_ = response.Body.Close()
	if err != nil {
		return 0, err
	}
	return downloaded, nil
}

func getRandomUserAgent() string {
	//nolint:lll
	userAgents := [...]string{
		"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
		"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
		"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
		"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0",
		"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2.1 Safari/605.1.15",
		"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edge/121.0.0.0 Safari/537.36",
		"Mozilla/5.0 (iPhone; CPU iPhone OS 17_2_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1",
		"Mozilla/5.0 (iPad; CPU OS 17_2_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1",
		"Mozilla/5.0 (Android 14; Mobile; rv:122.0) Gecko/122.0 Firefox/122.0",
		"Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Mobile Safari/537.36",
		"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 OPR/106.0.0.0",
		"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 OPR/106.0.0.0",
		"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)",
		"Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)",
	}
	return userAgents[rand.Intn(len(userAgents))] //nolint:gosec
}

func (b *BoringPoll) Stop() error {
	b.mutex.Lock()
	defer b.mutex.Unlock()

	if b.cancel == nil {
		return nil
	}
	b.cancel()
	<-b.done
	b.cancel = nil
	b.done = nil
	return nil
}

func byteCountSI(b uint64) string {
	const unit = 1000
	if b < unit {
		return fmt.Sprintf("%dB", b)
	}

	div, exp := uint64(unit), 0
	for n := b / unit; n >= unit; n /= unit {
		div *= unit
		exp++
	}

	return fmt.Sprintf("%.1f%cB", float64(b)/float64(div), "kMGTPE"[exp])
}


================================================
FILE: internal/boringpoll/interfaces.go
================================================
package boringpoll

type Logger interface {
	Infof(format string, args ...any)
	Debugf(format string, args ...any)
}


================================================
FILE: internal/cleanup/cleanup.go
================================================
package cleanup

import "sort"

type Cleanups []cleanup

type cleanup struct {
	operation  string
	orderIndex uint
	cleanup    func() error
	done       bool
}

// Add adds a cleanup function to the list of cleanups, with a description of the
// operation being cleaned up, and an order index that determines the order in which
// the cleanup functions are run. The lower the order index, the earlier the cleanup
// function is run.
func (c *Cleanups) Add(operation string, orderIndex uint,
	cleanupFunc func() error,
) {
	closer := cleanup{
		operation:  operation,
		orderIndex: orderIndex,
		cleanup:    cleanupFunc,
	}
	*c = append(*c, closer)
}

// Cleanup runs the cleanup functions in the order of their orderIndex,
// and logs any error that occurs during cleanup.
// It can also be re-called in case a cleanup fails, and already cleaned up
// functions will not be re-run.
func (c *Cleanups) Cleanup(logger Logger) {
	closers := *c

	sort.Slice(closers, func(i, j int) bool {
		return closers[i].orderIndex < closers[j].orderIndex
	})

	for i, closer := range closers {
		if closer.done {
			continue
		}
		closers[i].done = true
		logger.Debug(closer.operation + "...")
		err := closer.cleanup()
		if err != nil {
			logger.Error("failed " + closer.operation + ": " + err.Error())
		}
	}
}


================================================
FILE: internal/cleanup/cleanup_test.go
================================================
package cleanup

import (
	"errors"
	"testing"

	"github.com/golang/mock/gomock"
	"github.com/stretchr/testify/assert"
)

func Test_Cleanups(t *testing.T) {
	t.Parallel()

	ctrl := gomock.NewController(t)

	var ACloseCalled, BCloseCalled, CCloseCalled bool
	var (
		AErr error
		BErr = errors.New("B failed")
		CErr = errors.New("C failed")
	)

	var cleanups Cleanups
	cleanups.Add("cleaning up A", 5, func() error {
		ACloseCalled = true
		return AErr
	})

	cleanups.Add("cleaning up B", 3, func() error {
		BCloseCalled = true
		return BErr
	})

	cleanups.Add("cleaning up C", 2, func() error {
		CCloseCalled = true
		return CErr
	})

	logger := NewMockLogger(ctrl)
	prevCall := logger.EXPECT().Debug("cleaning up C...")
	prevCall = logger.EXPECT().Error("failed cleaning up C: C failed").After(prevCall)
	prevCall = logger.EXPECT().Debug("cleaning up B...").After(prevCall)
	prevCall = logger.EXPECT().Error("failed cleaning up B: B failed").After(prevCall)
	logger.EXPECT().Debug("cleaning up A...").After(prevCall)

	cleanups.Cleanup(logger)

	cleanups.Cleanup(logger) // run twice should not close already closed

	for _, cleanup := range cleanups {
		assert.True(t, cleanup.done)
	}

	assert.True(t, ACloseCalled)
	assert.True(t, BCloseCalled)
	assert.True(t, CCloseCalled)
}


================================================
FILE: internal/cleanup/interfaces.go
================================================
package cleanup

type Logger interface {
	Debug(string)
	Error(string)
}


================================================
FILE: internal/cleanup/mocks_generate_test.go
================================================
package cleanup

//go:generate mockgen -destination=mocks_test.go -package=$GOPACKAGE . Logger


================================================
FILE: internal/cleanup/mocks_test.go
================================================
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/qdm12/gluetun/internal/cleanup (interfaces: Logger)

// Package cleanup is a generated GoMock package.
package cleanup

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)
}


================================================
FILE: internal/cli/ci.go
================================================
package cli

import "context"

func (c *CLI) CI(context.Context) error {
	return nil
}


================================================
FILE: internal/cli/cli.go
================================================
package cli

type CLI struct {
	repoServersPath string
}

func New() *CLI {
	return &CLI{
		repoServersPath: "./internal/storage/servers.json",
	}
}


================================================
FILE: internal/cli/clientkey.go
================================================
package cli

import (
	"flag"
	"fmt"
	"io"
	"os"
	"strings"
)

func (c *CLI) ClientKey(args []string) error {
	flagSet := flag.NewFlagSet("clientkey", flag.ExitOnError)
	const openVPNClientKeyPath = "/gluetun/client.key" // TODO deduplicate?
	filepath := flagSet.String("path", openVPNClientKeyPath, "file path to the client.key file")
	if err := flagSet.Parse(args); err != nil {
		return err
	}
	file, err := os.OpenFile(*filepath, os.O_RDONLY, 0)
	if err != nil {
		return err
	}
	data, err := io.ReadAll(file)
	if err != nil {
		_ = file.Close()
		return err
	}
	if err := file.Close(); err != nil {
		return err
	}
	s := string(data)
	s = strings.ReplaceAll(s, "\n", "")
	s = strings.ReplaceAll(s, "\r", "")
	s = strings.TrimPrefix(s, "-----BEGIN PRIVATE KEY-----")
	s = strings.TrimSuffix(s, "-----END PRIVATE KEY-----")
	fmt.Println(s)
	return nil
}


================================================
FILE: internal/cli/formatservers.go
================================================
package cli

import (
	"errors"
	"flag"
	"fmt"
	"io/fs"
	"os"
	"path/filepath"
	"strings"

	"github.com/qdm12/gluetun/internal/constants"
	"github.com/qdm12/gluetun/internal/constants/providers"
	"github.com/qdm12/gluetun/internal/storage"
	"golang.org/x/text/cases"
	"golang.org/x/text/language"
)

var (
	ErrProviderUnspecified       = errors.New("VPN provider to format was not specified")
	ErrMultipleProvidersToFormat = errors.New("more than one VPN provider to format were specified")
)

func addProviderFlag(flagSet *flag.FlagSet, providerToFormat map[string]*bool,
	provider string, titleCaser cases.Caser,
) {
	boolPtr, ok := providerToFormat[provider]
	if !ok {
		panic(fmt.Sprintf("unknown provider in format map: %s", provider))
	}
	flagSet.BoolVar(boolPtr, provider, false, "Format "+titleCaser.String(provider)+" servers")
}

func (c *CLI) FormatServers(args []string) error {
	var format, output string
	allProviders := providers.All()
	allProviderFlags := make([]string, len(allProviders))
	for i, provider := range allProviders {
		allProviderFlags[i] = strings.ReplaceAll(provider, " ", "-")
	}

	providersToFormat := make(map[string]*bool, len(allProviders))
	for _, provider := range allProviderFlags {
		providersToFormat[provider] = new(bool)
	}
	flagSet := flag.NewFlagSet("format-servers", flag.ExitOnError)
	flagSet.StringVar(&format, "format", "markdown", "Format to use which can be: 'markdown' or 'json'")
	flagSet.StringVar(&output, "output", "/dev/stdout", "Output file to write the formatted data to")
	titleCaser := cases.Title(language.English)
	for _, provider := range allProviderFlags {
		addProviderFlag(flagSet, providersToFormat, provider, titleCaser)
	}
	if err := flagSet.Parse(args); err != nil {
		return err
	}

	// Note the format is validated by storage.Format

	// Verify only one provider is set to be formatted.
	var providers []string
	for provider, formatPtr := range providersToFormat {
		if *formatPtr {
			providers = append(providers, provider)
		}
	}
	switch len(providers) {
	case 0:
		return fmt.Errorf("%w", ErrProviderUnspecified)
	case 1:
	default:
		return fmt.Errorf("%w: %d specified: %s",
			ErrMultipleProvidersToFormat, len(providers),
			strings.Join(providers, ", "))
	}

	var providerToFormat string
	for _, providerToFormat = range allProviders {
		if strings.ReplaceAll(providerToFormat, " ", "-") == providers[0] {
			break
		}
	}

	logger := newNoopLogger()
	storage, err := storage.New(logger, constants.ServersData)
	if err != nil {
		return fmt.Errorf("creating servers storage: %w", err)
	}

	formatted, err := storage.Format(providerToFormat, format)
	if err != nil {
		return fmt.Errorf("formatting servers: %w", err)
	}

	output = filepath.Clean(output)
	const permission = fs.FileMode(0o644)
	file, err := os.OpenFile(output, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, permission)
	if err != nil {
		return fmt.Errorf("opening output file: %w", err)
	}

	_, err = fmt.Fprint(file, formatted)
	if err != nil {
		_ = file.Close()
		return fmt.Errorf("writing to output file: %w", err)
	}

	err = file.Close()
	if err != nil {
		return fmt.Errorf("closing output file: %w", err)
	}

	return nil
}


================================================
FILE: internal/cli/genkey.go
================================================
package cli

import (
	"crypto/rand"
	"flag"
	"fmt"
)

func (c *CLI) GenKey(args []string) (err error) {
	flagSet := flag.NewFlagSet("genkey", flag.ExitOnError)
	err = flagSet.Parse(args)
	if err != nil {
		return fmt.Errorf("parsing flags: %w", err)
	}

	const keyLength = 128 / 8
	keyBytes := make([]byte, keyLength)

	_, _ = rand.Read(keyBytes)

	key := base58Encode(keyBytes)
	fmt.Println(key)

	return nil
}

func base58Encode(data []byte) string {
	const alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
	const radix = 58

	zcount := 0
	for zcount < len(data) && data[zcount] == 0 {
		zcount++
	}

	// integer simplification of ceil(log(256)/log(58))
	ceilLog256Div58 := (len(data)-zcount)*555/406 + 1 //nolint:mnd
	size := zcount + ceilLog256Div58

	output := make([]byte, size)

	high := size - 1
	for _, b := range data {
		i := size - 1
		for carry := uint32(b); i > high || carry != 0; i-- {
			carry += 256 * uint32(output[i]) //nolint:mnd
			output[i] = byte(carry % radix)
			carry /= radix
		}
		high = i
	}

	// Determine the additional "zero-gap" in the output buffer
	additionalZeroGapEnd := zcount
	for additionalZeroGapEnd < size && output[additionalZeroGapEnd] == 0 {
		additionalZeroGapEnd++
	}

	val := output[additionalZeroGapEnd-zcount:]
	size = len(val)
	for i := range val {
		output[i] = alphabet[val[i]]
	}

	return string(output[:size])
}


================================================
FILE: internal/cli/healthcheck.go
================================================
package cli

import (
	"context"
	"net"
	"net/http"
	"time"

	"github.com/qdm12/gluetun/internal/configuration/settings"
	"github.com/qdm12/gluetun/internal/healthcheck"
	"github.com/qdm12/gosettings/reader"
)

func (c *CLI) HealthCheck(ctx context.Context, reader *reader.Reader, _ Warner) (err error) {
	// Extract the health server port from the configuration.
	var config settings.Health
	err = config.Read(reader)
	if err != nil {
		return err
	}

	config.SetDefaults()

	err = config.Validate()
	if err != nil {
		return err
	}

	_, port, err := net.SplitHostPort(config.ServerAddress)
	if err != nil {
		return err
	}

	const timeout = 10 * time.Second
	httpClient := &http.Client{Timeout: timeout}
	client := healthcheck.NewClient(httpClient)
	ctx, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()

	url := "http://127.0.0.1:" + port
	return client.Check(ctx, url)
}


================================================
FILE: internal/cli/interfaces.go
================================================
package cli

import "github.com/qdm12/gluetun/internal/configuration/settings"

type Source interface {
	Read() (settings settings.Settings, err error)
	ReadHealth() (health settings.Health, err error)
	String() string
}


================================================
FILE: internal/cli/nooplogger.go
================================================
package cli

type noopLogger struct{}

func newNoopLogger() *noopLogger {
	return new(noopLogger)
}

func (l *noopLogger) Info(string) {}
func (l *noopLogger) Warn(string) {}


================================================
FILE: internal/cli/openvpnconfig.go
================================================
package cli

import (
	"context"
	"fmt"
	"net/http"
	"net/netip"
	"strings"
	"time"

	"github.com/qdm12/gluetun/internal/configuration/settings"
	"github.com/qdm12/gluetun/internal/constants"
	"github.com/qdm12/gluetun/internal/models"
	"github.com/qdm12/gluetun/internal/openvpn/extract"
	"github.com/qdm12/gluetun/internal/provider"
	"github.com/qdm12/gluetun/internal/storage"
	"github.com/qdm12/gluetun/internal/updater/resolver"
	"github.com/qdm12/gosettings/reader"
)

type OpenvpnConfigLogger interface {
	Info(s string)
	Warn(s string)
}

type Unzipper interface {
	FetchAndExtract(ctx context.Context, url string) (
		contents map[string][]byte, err error)
}

type ParallelResolver interface {
	Resolve(ctx context.Context, settings resolver.ParallelSettings) (
		hostToIPs map[string][]netip.Addr, warnings []string, err error)
}

type IPFetcher interface {
	String() string
	CanFetchAnyIP() bool
	FetchInfo(ctx context.Context, ip netip.Addr) (data models.PublicIP, err error)
}

type IPv6Checker interface {
	IsIPv6Supported() (supported bool, err error)
}

func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, reader *reader.Reader,
	ipv6Checker IPv6Checker,
) error {
	storage, err := storage.New(logger, constants.ServersData)
	if err != nil {
		return err
	}

	var allSettings settings.Settings
	err = allSettings.Read(reader, logger)
	if err != nil {
		return err
	}
	allSettings.SetDefaults()

	ipv6Supported, err := ipv6Checker.IsIPv6Supported()
	if err != nil {
		return fmt.Errorf("checking for IPv6 support: %w", err)
	}

	if err = allSettings.Validate(storage, ipv6Supported, logger); err != nil {
		return fmt.Errorf("validating settings: %w", err)
	}

	// Unused by this CLI command
	unzipper := (Unzipper)(nil)
	client := (*http.Client)(nil)
	warner := (Warner)(nil)
	parallelResolver := (ParallelResolver)(nil)
	ipFetcher := (IPFetcher)(nil)
	openvpnFileExtractor := extract.New()

	providers := provider.NewProviders(storage, time.Now, warner, client,
		unzipper, parallelResolver, ipFetcher, openvpnFileExtractor, allSettings.Updater)
	providerConf := providers.Get(allSettings.VPN.Provider.Name)
	connection, err := providerConf.GetConnection(
		allSettings.VPN.Provider.ServerSelection, ipv6Supported)
	if err != nil {
		return err
	}

	lines := providerConf.OpenVPNConfig(connection,
		allSettings.VPN.OpenVPN, ipv6Supported)

	fmt.Println(strings.Join(lines, "\n"))
	return nil
}


================================================
FILE: internal/cli/update.go
================================================
package cli

import (
	"context"
	"errors"
	"flag"
	"fmt"
	"net/http"
	"slices"
	"strings"
	"time"

	"github.com/qdm12/dns/v2/pkg/doh"
	dnsprovider "github.com/qdm12/dns/v2/pkg/provider"
	"github.com/qdm12/gluetun/internal/configuration/settings"
	"github.com/qdm12/gluetun/internal/constants"
	"github.com/qdm12/gluetun/internal/constants/providers"
	"github.com/qdm12/gluetun/internal/openvpn/extract"
	"github.com/qdm12/gluetun/internal/provider"
	"github.com/qdm12/gluetun/internal/publicip/api"
	"github.com/qdm12/gluetun/internal/storage"
	"github.com/qdm12/gluetun/internal/updater"
	"github.com/qdm12/gluetun/internal/updater/resolver"
	"github.com/qdm12/gluetun/internal/updater/unzip"
)

var (
	ErrModeUnspecified     = errors.New("at least one of -enduser or -maintainer must be specified")
	ErrNoProviderSpecified = errors.New("no provider was specified")
	ErrUsernameMissing     = errors.New("username is required for this provider")
	ErrPasswordMissing     = errors.New("password is required for this provider")
)

type UpdaterLogger interface {
	Info(s string)
	Warn(s string)
	Error(s string)
}

func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) error {
	options := settings.Updater{}
	var endUserMode, maintainerMode, updateAll bool
	var dnsServer, csvProviders, ipToken, protonUsername, protonEmail, protonPassword string
	flagSet := flag.NewFlagSet("update", flag.ExitOnError)
	flagSet.BoolVar(&endUserMode, "enduser", false, "Write results to /gluetun/servers.json (for end users)")
	flagSet.BoolVar(&maintainerMode, "maintainer", false,
		"Write results to ./internal/storage/servers.json to modify the program (for maintainers)")
	flagSet.StringVar(&dnsServer, "dns", "", "no longer used, your DNS will use DoH with Cloudflare and Google")
	const defaultMinRatio = 0.8
	flagSet.Float64Var(&options.MinRatio, "minratio", defaultMinRatio,
		"Minimum ratio of servers to find for the update to succeed")
	flagSet.BoolVar(&updateAll, "all", false, "Update servers for all VPN providers")
	flagSet.StringVar(&csvProviders, "providers", "", "CSV string of VPN providers to update server data for")
	flagSet.StringVar(&ipToken, "ip-token", "", "IP data service token (e.g. ipinfo.io) to use")
	flagSet.StringVar(&protonUsername, "proton-username", "",
		"(Retro-compatibility) Username to use to authenticate with Proton. Use -proton-email instead.") // v4 remove this
	flagSet.StringVar(&protonEmail, "proton-email", "", "Email to use to authenticate with Proton")
	flagSet.StringVar(&protonPassword, "proton-password", "", "Password to use to authenticate with Proton")
	if err := flagSet.Parse(args); err != nil {
		return err
	}

	if dnsServer != "" {
		logger.Warn("The -dns flag is no longer used, your DNS will use DoH with Cloudflare and Google")
	}

	if !endUserMode && !maintainerMode {
		return fmt.Errorf("%w", ErrModeUnspecified)
	}

	if updateAll {
		options.Providers = providers.All()
	} else {
		if csvProviders == "" {
			return fmt.Errorf("%w", ErrNoProviderSpecified)
		}
		options.Providers = strings.Split(csvProviders, ",")
	}

	if slices.Contains(options.Providers, providers.Protonvpn) {
		if protonEmail == "" && protonUsername != "" {
			protonEmail = protonUsername + "@protonmail.com"
			logger.Warn("use -proton-email instead of -proton-username in the future. " +
				"This assumes the email is " + protonEmail + " and may not work.")
		}
		options.ProtonEmail = &protonEmail
		options.ProtonPassword = &protonPassword
	}

	options.SetDefaults(options.Providers[0])

	err := options.Validate()
	if err != nil {
		return fmt.Errorf("options validation failed: %w", err)
	}

	serversDataPath := constants.ServersData
	if maintainerMode {
		serversDataPath = ""
	}
	storage, err := storage.New(logger, serversDataPath)
	if err != nil {
		return fmt.Errorf("creating servers storage: %w", err)
	}

	dohSettings := doh.Settings{
		UpstreamResolvers: []dnsprovider.Provider{
			dnsprovider.Cloudflare(),
			dnsprovider.Google(),
		},
	}
	dnsDialer, err := doh.New(dohSettings)
	if err != nil {
		return fmt.Errorf("creating DoH dialer: %w", err)
	}

	const clientTimeout = 10 * time.Second
	httpClient := &http.Client{Timeout: clientTimeout}
	unzipper := unzip.New(httpClient)
	parallelResolver := resolver.NewParallelResolver(dnsDialer)
	nameTokenPairs := []api.NameToken{
		{Name: string(api.IPInfo), Token: ipToken},
		{Name: string(api.IP2Location)},
		{Name: string(api.IfConfigCo)},
	}
	fetchers, err := api.New(nameTokenPairs, httpClient)
	if err != nil {
		return fmt.Errorf("creating public IP fetchers: %w", err)
	}
	ipFetcher := api.NewResilient(fetchers, logger)

	openvpnFileExtractor := extract.New()

	providers := provider.NewProviders(storage, time.Now, logger, httpClient,
		unzipper, parallelResolver, ipFetcher, openvpnFileExtractor, options)

	updater := updater.New(httpClient, storage, providers, logger)
	err = updater.UpdateServers(ctx, options.Providers, options.MinRatio)
	if err != nil {
		return fmt.Errorf("updating server information: %w", err)
	}

	if maintainerMode {
		err := storage.FlushToFile(c.repoServersPath)
		if err != nil {
			return fmt.Errorf("writing servers data to embedded JSON file: %w", err)
		}
	}

	return nil
}


================================================
FILE: internal/cli/warner.go
================================================
package cli

type Warner interface {
	Warn(s string)
}


================================================
FILE: internal/command/cmder.go
================================================
package command

// Cmder handles running subprograms synchronously and asynchronously.
type Cmder struct{}

func New() *Cmder {
	return &Cmder{}
}


================================================
FILE: internal/command/interfaces.go
================================================
package command

type Logger interface {
	Info(s string)
	Error(s string)
}


================================================
FILE: internal/command/interfaces_local.go
================================================
package command

import "io"

type execCmd interface {
	CombinedOutput() ([]byte, error)
	StdoutPipe() (io.ReadCloser, error)
	StderrPipe() (io.ReadCloser, error)
	Start() error
	Wait() error
}


================================================
FILE: internal/command/mocks_generate_test.go
================================================
package command

//go:generate mockgen -destination=mocks_local_test.go -package=$GOPACKAGE -source=interfaces_local.go


================================================
FILE: internal/command/mocks_local_test.go
================================================
// Code generated by MockGen. DO NOT EDIT.
// Source: interfaces_local.go

// Package command is a generated GoMock package.
package command

import (
	io "io"
	reflect "reflect"

	gomock "github.com/golang/mock/gomock"
)

// MockexecCmd is a mock of execCmd interface.
type MockexecCmd struct {
	ctrl     *gomock.Controller
	recorder *MockexecCmdMockRecorder
}

// MockexecCmdMockRecorder is the mock recorder for MockexecCmd.
type MockexecCmdMockRecorder struct {
	mock *MockexecCmd
}

// NewMockexecCmd creates a new mock instance.
func NewMockexecCmd(ctrl *gomock.Controller) *MockexecCmd {
	mock := &MockexecCmd{ctrl: ctrl}
	mock.recorder = &MockexecCmdMockRecorder{mock}
	return mock
}

// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockexecCmd) EXPECT() *MockexecCmdMockRecorder {
	return m.recorder
}

// CombinedOutput mocks base method.
func (m *MockexecCmd) CombinedOutput() ([]byte, error) {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "CombinedOutput")
	ret0, _ := ret[0].([]byte)
	ret1, _ := ret[1].(error)
	return ret0, ret1
}

// CombinedOutput indicates an expected call of CombinedOutput.
func (mr *MockexecCmdMockRecorder) CombinedOutput() *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CombinedOutput", reflect.TypeOf((*MockexecCmd)(nil).CombinedOutput))
}

// Start mocks base method.
func (m *MockexecCmd) Start() error {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "Start")
	ret0, _ := ret[0].(error)
	return ret0
}

// Start indicates an expected call of Start.
func (mr *MockexecCmdMockRecorder) Start() *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockexecCmd)(nil).Start))
}

// StderrPipe mocks base method.
func (m *MockexecCmd) StderrPipe() (io.ReadCloser, error) {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "StderrPipe")
	ret0, _ := ret[0].(io.ReadCloser)
	ret1, _ := ret[1].(error)
	return ret0, ret1
}

// StderrPipe indicates an expected call of StderrPipe.
func (mr *MockexecCmdMockRecorder) StderrPipe() *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StderrPipe", reflect.TypeOf((*MockexecCmd)(nil).StderrPipe))
}

// StdoutPipe mocks base method.
func (m *MockexecCmd) StdoutPipe() (io.ReadCloser, error) {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "StdoutPipe")
	ret0, _ := ret[0].(io.ReadCloser)
	ret1, _ := ret[1].(error)
	return ret0, ret1
}

// StdoutPipe indicates an expected call of StdoutPipe.
func (mr *MockexecCmdMockRecorder) StdoutPipe() *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StdoutPipe", reflect.TypeOf((*MockexecCmd)(nil).StdoutPipe))
}

// Wait mocks base method.
func (m *MockexecCmd) Wait() error {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "Wait")
	ret0, _ := ret[0].(error)
	return ret0
}

// Wait indicates an expected call of Wait.
func (mr *MockexecCmdMockRecorder) Wait() *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Wait", reflect.TypeOf((*MockexecCmd)(nil).Wait))
}


================================================
FILE: internal/command/run.go
================================================
package command

import (
	"os/exec"
	"strings"
)

// Run runs a command in a blocking manner, returning its output and
// an error if it failed.
func (c *Cmder) Run(cmd *exec.Cmd) (output string, err error) {
	return run(cmd)
}

func run(cmd execCmd) (output string, err error) {
	stdout, err := cmd.CombinedOutput()
	output = string(stdout)
	output = strings.TrimSuffix(output, "\n")
	lines := stringToLines(output)
	for i := range lines {
		lines[i] = strings.TrimPrefix(lines[i], "'")
		lines[i] = strings.TrimSuffix(lines[i], "'")
	}
	output = strings.Join(lines, "\n")
	return output, err
}

func stringToLines(s string) (lines []string) {
	s = strings.TrimSuffix(s, "\n")
	return strings.Split(s, "\n")
}


================================================
FILE: internal/command/run_test.go
================================================
package command

import (
	"errors"
	"testing"

	gomock "github.com/golang/mock/gomock"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func Test_run(t *testing.T) {
	t.Parallel()

	errDummy := errors.New("dummy")

	testCases := map[string]struct {
		stdout []byte
		cmdErr error
		output string
		err    error
	}{
		"no output": {},
		"cmd error": {
			stdout: []byte("'hello \nworld'\n"),
			cmdErr: errDummy,
			output: "hello \nworld",
			err:    errDummy,
		},
	}

	for name, testCase := range testCases {
		t.Run(name, func(t *testing.T) {
			t.Parallel()

			ctrl := gomock.NewController(t)

			mockCmd := NewMockexecCmd(ctrl)

			mockCmd.EXPECT().CombinedOutput().Return(testCase.stdout, testCase.cmdErr)

			output, err := run(mockCmd)

			if testCase.err != nil {
				require.Error(t, err)
				assert.Equal(t, testCase.err.Error(), err.Error())
			} else {
				assert.NoError(t, err)
			}

			assert.Equal(t, testCase.output, output)
		})
	}
}


================================================
FILE: internal/command/split.go
================================================
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)
}


================================================
FILE: internal/command/split_test.go
================================================
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)
			}
		})
	}
}


================================================
FILE: internal/command/start.go
================================================
package command

import (
	"bufio"
	"errors"
	"io"
	"os"
	"os/exec"
)

// Start launches a command and streams stdout and stderr to channels.
// All the channels returned are ready only and won't be closed
// if the command fails later.
func (c *Cmder) Start(cmd *exec.Cmd) (
	stdoutLines, stderrLines <-chan string,
	waitError <-chan error, startErr error,
) {
	return start(cmd)
}

func start(cmd execCmd) (stdoutLines, stderrLines <-chan string,
	waitError <-chan error, startErr error,
) {
	stop := make(chan struct{})
	stdoutReady := make(chan struct{})
	stdoutLinesCh := make(chan string)
	stdoutDone := make(chan struct{})
	stderrReady := make(chan struct{})
	stderrLinesCh := make(chan string)
	stderrDone := make(chan struct{})

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, nil, nil, err
	}
	go streamToChannel(stdoutReady, stop, stdoutDone, stdout, stdoutLinesCh)

	stderr, err := cmd.StderrPipe()
	if err != nil {
		_ = stdout.Close()
		close(stop)
		<-stdoutDone
		return nil, nil, nil, err
	}
	go streamToChannel(stderrReady, stop, stderrDone, stderr, stderrLinesCh)

	err = cmd.Start()
	if err != nil {
		_ = stdout.Close()
		_ = stderr.Close()
		close(stop)
		<-stdoutDone
		<-stderrDone
		return nil, nil, nil, err
	}

	waitErrorCh := make(chan error)
	go func() {
		err := cmd.Wait()
		_ = stdout.Close()
		_ = stderr.Close()
		close(stop)
		<-stdoutDone
		<-stderrDone
		waitErrorCh <- err
	}()

	return stdoutLinesCh, stderrLinesCh, waitErrorCh, nil
}

func streamToChannel(ready chan<- struct{},
	stop <-chan struct{}, done chan<- struct{},
	stream io.Reader, lines chan<- string,
) {
	defer close(done)
	close(ready)
	scanner := bufio.NewScanner(stream)
	lineBuffer := make([]byte, bufio.MaxScanTokenSize) // 64KB
	const maxCapacity = 20 * 1024 * 1024               // 20MB
	scanner.Buffer(lineBuffer, maxCapacity)

	for scanner.Scan() {
		// scanner is closed if the context is canceled
		// or if the command failed starting because the
		// stream is closed (io.EOF error).
		lines <- scanner.Text()
	}
	err := scanner.Err()
	if err == nil || errors.Is(err, os.ErrClosed) {
		return
	}

	// ignore the error if it is stopped.
	select {
	case <-stop:
		return
	default:
		lines <- "stream error: " + err.Error()
	}
}


================================================
FILE: internal/command/start_test.go
================================================
package command

import (
	"bytes"
	"errors"
	"io"
	"strings"
	"testing"

	gomock "github.com/golang/mock/gomock"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func linesToReadCloser(lines []string) io.ReadCloser {
	s := strings.Join(lines, "\n")
	return io.NopCloser(bytes.NewBufferString(s))
}

func Test_start(t *testing.T) {
	t.Parallel()

	errDummy := errors.New("dummy")

	testCases := map[string]struct {
		stdout        []string
		stdoutPipeErr error
		stderr        []string
		stderrPipeErr error
		startErr      error
		waitErr       error
		err           error
	}{
		"no output": {},
		"success": {
			stdout: []string{"hello", "world"},
			stderr: []string{"some", "error"},
		},
		"stdout pipe error": {
			stdoutPipeErr: errDummy,
			err:           errDummy,
		},
		"stderr pipe error": {
			stderrPipeErr: errDummy,
			err:           errDummy,
		},
		"start error": {
			startErr: errDummy,
			err:      errDummy,
		},
		"wait error": {
			waitErr: errDummy,
		},
	}

	for name, testCase := range testCases {
		t.Run(name, func(t *testing.T) {
			t.Parallel()

			ctrl := gomock.NewController(t)

			stdout := linesToReadCloser(testCase.stdout)
			stderr := linesToReadCloser(testCase.stderr)

			mockCmd := NewMockexecCmd(ctrl)

			mockCmd.EXPECT().StdoutPipe().
				Return(stdout, testCase.stdoutPipeErr)
			if testCase.stdoutPipeErr == nil {
				mockCmd.EXPECT().StderrPipe().Return(stderr, testCase.stderrPipeErr)
				if testCase.stderrPipeErr == nil {
					mockCmd.EXPECT().Start().Return(testCase.startErr)
					if testCase.startErr == nil {
						mockCmd.EXPECT().Wait().Return(testCase.waitErr)
					}
				}
			}

			stdoutLines, stderrLines, waitError, err := start(mockCmd)

			if testCase.err != nil {
				require.Error(t, err)
				assert.Equal(t, testCase.err.Error(), err.Error())
				assert.Nil(t, stdoutLines)
				assert.Nil(t, stderrLines)
				assert.Nil(t, waitError)
				return
			}

			require.NoError(t, err)

			var stdoutIndex, stderrIndex int

			done := false
			for !done {
				select {
				case line := <-stdoutLines:
					assert.Equal(t, testCase.stdout[stdoutIndex], line)
					stdoutIndex++
				case line := <-stderrLines:
					assert.Equal(t, testCase.stderr[stderrIndex], line)
					stderrIndex++
				case err := <-waitError:
					if testCase.waitErr != nil {
						require.Error(t, err)
						assert.Equal(t, testCase.waitErr.Error(), err.Error())
					} else {
						assert.NoError(t, err)
					}
					done = true
				}
			}

			assert.Equal(t, len(testCase.stdout), stdoutIndex)
			assert.Equal(t, len(testCase.stderr), stderrIndex)
		})
	}
}


================================================
FILE: internal/command/startnlog.go
================================================
package command

import (
	"context"
	"fmt"
	"os/exec"
)

func (c *Cmder) RunAndLog(ctx context.Context, command string, logger Logger) (err error) {
	args, err := split(command)
	if err != nil {
		return fmt.Errorf("parsing command: %w", err)
	}

	cmd := exec.CommandContext(ctx, args[0], args[1:]...) // #nosec G204
	stdout, stderr, waitError, err := c.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)
		}
	}
}


================================================
FILE: internal/configuration/settings/amneziawg.go
================================================
package settings

import (
	"errors"
	"fmt"
	"strconv"
	"strings"

	"github.com/qdm12/gosettings"
	"github.com/qdm12/gosettings/reader"
	"github.com/qdm12/gotree"
)

type AmneziaWg struct {
	// Wireguard contains the configuration for Wireguard, given
	// AmneziaWg is based on Wireguard
	Wireguard       Wireguard `json:"wireguard"`
	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 (a *AmneziaWg) read(r *reader.Reader) (err error) {
	const amneziawg = true
	err = a.Wireguard.read(r, amneziawg)
	if err != nil {
		return err // do not wrap this error
	}

	uint16Fields := map[string]**uint16{
		"AMNEZIAWG_JC":   &a.JunkPacketCount,
		"AMNEZIAWG_JMIN": &a.JunkPacketMin,
		"AMNEZIAWG_JMAX": &a.JunkPacketMax,
		"AMNEZIAWG_S1":   &a.PaddingS1,
		"AMNEZIAWG_S2":   &a.PaddingS2,
		"AMNEZIAWG_S3":   &a.PaddingS3,
		"AMNEZIAWG_S4":   &a.PaddingS4,
	}
	for key, dst := range uint16Fields {
		*dst, err = r.Uint16Ptr(key)
		if err != nil {
			return err
		}
	}
	stringFields := map[string]**string{
		"AMNEZIAWG_H1": &a.HeaderH1,
		"AMNEZIAWG_H2": &a.HeaderH2,
		"AMNEZIAWG_H3": &a.HeaderH3,
		"AMNEZIAWG_H4": &a.HeaderH4,
		"AMNEZIAWG_I1": &a.InitPacketI1,
		"AMNEZIAWG_I2": &a.InitPacketI2,
		"AMNEZIAWG_I3": &a.InitPacketI3,
		"AMNEZIAWG_I4": &a.InitPacketI4,
		"AMNEZIAWG_I5": &a.InitPacketI5,
	}
	opt := reader.ForceLowercase(false)
	for key, dst := range stringFields {
		*dst = r.Get(key, opt)
	}
	return nil
}

func (a AmneziaWg) copy() (copied AmneziaWg) {
	return AmneziaWg{
		Wireguard:       a.Wireguard.copy(),
		JunkPacketCount: gosettings.CopyPointer(a.JunkPacketCount),
		JunkPacketMin:   gosettings.CopyPointer(a.JunkPacketMin),
		JunkPacketMax:   gosettings.CopyPointer(a.JunkPacketMax),
		PaddingS1:       gosettings.CopyPointer(a.PaddingS1),
		PaddingS2:       gosettings.CopyPointer(a.PaddingS2),
		PaddingS3:       gosettings.CopyPointer(a.PaddingS3),
		PaddingS4:       gosettings.CopyPointer(a.PaddingS4),
		HeaderH1:        gosettings.CopyPointer(a.HeaderH1),
		HeaderH2:        gosettings.CopyPointer(a.HeaderH2),
		HeaderH3:        gosettings.CopyPointer(a.HeaderH3),
		HeaderH4:        gosettings.CopyPointer(a.HeaderH4),
		InitPacketI1:    gosettings.CopyPointer(a.InitPacketI1),
		InitPacketI2:    gosettings.CopyPointer(a.InitPacketI2),
		InitPacketI3:    gosettings.CopyPointer(a.InitPacketI3),
		InitPacketI4:    gosettings.CopyPointer(a.InitPacketI4),
		InitPacketI5:    gosettings.CopyPointer(a.InitPacketI5),
	}
}

func (a *AmneziaWg) overrideWith(other AmneziaWg) {
	a.Wireguard.overrideWith(other.Wireguard)
	a.JunkPacketCount = gosettings.OverrideWithPointer(a.JunkPacketCount, other.JunkPacketCount)
	a.JunkPacketMin = gosettings.OverrideWithPointer(a.JunkPacketMin, other.JunkPacketMin)
	a.JunkPacketMax = gosettings.OverrideWithPointer(a.JunkPacketMax, other.JunkPacketMax)
	a.PaddingS1 = gosettings.OverrideWithPointer(a.PaddingS1, other.PaddingS1)
	a.PaddingS2 = gosettings.OverrideWithPointer(a.PaddingS2, other.PaddingS2)
	a.PaddingS3 = gosettings.OverrideWithPointer(a.PaddingS3, other.PaddingS3)
	a.PaddingS4 = gosettings.OverrideWithPointer(a.PaddingS4, other.PaddingS4)
	a.HeaderH1 = gosettings.OverrideWithPointer(a.HeaderH1, other.HeaderH1)
	a.HeaderH2 = gosettings.OverrideWithPointer(a.HeaderH2, other.HeaderH2)
	a.HeaderH3 = gosettings.OverrideWithPointer(a.HeaderH3, other.HeaderH3)
	a.HeaderH4 = gosettings.OverrideWithPointer(a.HeaderH4, other.HeaderH4)
	a.InitPacketI1 = gosettings.OverrideWithPointer(a.InitPacketI1, other.InitPacketI1)
	a.InitPacketI2 = gosettings.OverrideWithPointer(a.InitPacketI2, other.InitPacketI2)
	a.InitPacketI3 = gosettings.OverrideWithPointer(a.InitPacketI3, other.InitPacketI3)
	a.InitPacketI4 = gosettings.OverrideWithPointer(a.InitPacketI4, other.InitPacketI4)
	a.InitPacketI5 = gosettings.OverrideWithPointer(a.InitPacketI5, other.InitPacketI5)
}

func (a *AmneziaWg) setDefaults(vpnProvider string) {
	a.Wireguard.setDefaults(vpnProvider)
	a.Wireguard
Download .txt
gitextract_eckrvmuz/

├── .devcontainer/
│   ├── .dockerignore
│   ├── Dockerfile
│   ├── README.md
│   └── devcontainer.json
├── .dockerignore
├── .github/
│   ├── CODEOWNERS
│   ├── CONTRIBUTING.md
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug.yml
│   │   ├── config.yml
│   │   ├── feature_request.yml
│   │   └── provider.md
│   ├── dependabot.yml
│   ├── labels.yml
│   ├── pull_request_template.md
│   └── workflows/
│       ├── ci-skip.yml
│       ├── ci.yml
│       ├── closed-issue.yml
│       ├── configs/
│       │   └── mlc-config.json
│       ├── labels.yml
│       ├── markdown-skip.yml
│       ├── markdown.yml
│       ├── opened-issue.yml
│       └── update-servers-list.yml
├── .gitignore
├── .golangci.yml
├── .markdownlint-cli2.jsonc
├── .vscode/
│   ├── extensions.json
│   ├── settings.json
│   └── tasks.json
├── Dockerfile
├── LICENSE
├── README.md
├── ci/
│   ├── cmd/
│   │   └── main.go
│   ├── go.mod
│   ├── go.sum
│   └── internal/
│       ├── mullvad.go
│       ├── protonvpn.go
│       ├── secrets.go
│       └── simple.go
├── cmd/
│   └── gluetun/
│       └── main.go
├── go.mod
├── go.sum
├── internal/
│   ├── alpine/
│   │   ├── alpine.go
│   │   ├── users.go
│   │   └── version.go
│   ├── amneziawg/
│   │   ├── constructor.go
│   │   ├── constructor_test.go
│   │   ├── helpers_test.go
│   │   ├── log.go
│   │   ├── log_mock_test.go
│   │   ├── netlinker.go
│   │   ├── netlinker_mock_test.go
│   │   ├── run.go
│   │   └── settings.go
│   ├── boringpoll/
│   │   ├── boringpoll.go
│   │   └── interfaces.go
│   ├── cleanup/
│   │   ├── cleanup.go
│   │   ├── cleanup_test.go
│   │   ├── interfaces.go
│   │   ├── mocks_generate_test.go
│   │   └── mocks_test.go
│   ├── cli/
│   │   ├── ci.go
│   │   ├── cli.go
│   │   ├── clientkey.go
│   │   ├── formatservers.go
│   │   ├── genkey.go
│   │   ├── healthcheck.go
│   │   ├── interfaces.go
│   │   ├── nooplogger.go
│   │   ├── openvpnconfig.go
│   │   ├── update.go
│   │   └── warner.go
│   ├── command/
│   │   ├── cmder.go
│   │   ├── interfaces.go
│   │   ├── interfaces_local.go
│   │   ├── mocks_generate_test.go
│   │   ├── mocks_local_test.go
│   │   ├── run.go
│   │   ├── run_test.go
│   │   ├── split.go
│   │   ├── split_test.go
│   │   ├── start.go
│   │   ├── start_test.go
│   │   └── startnlog.go
│   ├── configuration/
│   │   ├── settings/
│   │   │   ├── amneziawg.go
│   │   │   ├── boringpoll.go
│   │   │   ├── deprecated.go
│   │   │   ├── dns.go
│   │   │   ├── dns_test.go
│   │   │   ├── dnsblacklist.go
│   │   │   ├── errors.go
│   │   │   ├── firewall.go
│   │   │   ├── firewall_test.go
│   │   │   ├── health.go
│   │   │   ├── helpers/
│   │   │   │   └── belong.go
│   │   │   ├── helpers.go
│   │   │   ├── helpers_test.go
│   │   │   ├── httpproxy.go
│   │   │   ├── interfaces.go
│   │   │   ├── iptables.go
│   │   │   ├── log.go
│   │   │   ├── mocks_generate_test.go
│   │   │   ├── mocks_reader_test.go
│   │   │   ├── mocks_test.go
│   │   │   ├── nordvpn_retro.go
│   │   │   ├── openvpn.go
│   │   │   ├── openvpn_test.go
│   │   │   ├── openvpnselection.go
│   │   │   ├── pmtud.go
│   │   │   ├── portforward.go
│   │   │   ├── portforward_test.go
│   │   │   ├── provider.go
│   │   │   ├── publicip.go
│   │   │   ├── publicip_test.go
│   │   │   ├── server.go
│   │   │   ├── serverselection.go
│   │   │   ├── settings.go
│   │   │   ├── settings_test.go
│   │   │   ├── shadowsocks.go
│   │   │   ├── storage.go
│   │   │   ├── surfshark_retro.go
│   │   │   ├── system.go
│   │   │   ├── updater.go
│   │   │   ├── validation/
│   │   │   │   ├── servers.go
│   │   │   │   └── surfshark.go
│   │   │   ├── version.go
│   │   │   ├── vpn.go
│   │   │   ├── wireguard.go
│   │   │   └── wireguardselection.go
│   │   └── sources/
│   │       ├── files/
│   │       │   ├── amneziawg.go
│   │       │   ├── amneziawg_test.go
│   │       │   ├── helpers.go
│   │       │   ├── interfaces.go
│   │       │   ├── reader.go
│   │       │   ├── wireguard.go
│   │       │   └── wireguard_test.go
│   │       └── secrets/
│   │           ├── amneziawg.go
│   │           ├── helpers.go
│   │           ├── interfaces.go
│   │           ├── reader.go
│   │           ├── reader_test.go
│   │           └── wireguard.go
│   ├── constants/
│   │   ├── colors.go
│   │   ├── countries.go
│   │   ├── openvpn/
│   │   │   ├── auth.go
│   │   │   ├── ciphers.go
│   │   │   ├── paths.go
│   │   │   └── versions.go
│   │   ├── paths.go
│   │   ├── protocol.go
│   │   ├── providers/
│   │   │   ├── providers.go
│   │   │   └── providers_test.go
│   │   ├── status.go
│   │   └── vpn/
│   │       └── protocol.go
│   ├── dns/
│   │   ├── leak.go
│   │   ├── leak_test.go
│   │   ├── logger.go
│   │   ├── loop.go
│   │   ├── plaintext.go
│   │   ├── run.go
│   │   ├── settings.go
│   │   ├── setup.go
│   │   ├── state/
│   │   │   ├── settings.go
│   │   │   └── state.go
│   │   ├── status.go
│   │   ├── ticker.go
│   │   └── update.go
│   ├── firewall/
│   │   ├── enable.go
│   │   ├── firewall.go
│   │   ├── interfaces.go
│   │   ├── iptables/
│   │   │   ├── atomic.go
│   │   │   ├── cmd_matcher_test.go
│   │   │   ├── delete.go
│   │   │   ├── delete_test.go
│   │   │   ├── firewall.go
│   │   │   ├── interfaces.go
│   │   │   ├── ip6tables.go
│   │   │   ├── iptables.go
│   │   │   ├── iptablesmix.go
│   │   │   ├── list.go
│   │   │   ├── list_test.go
│   │   │   ├── mocks_generate_test.go
│   │   │   ├── mocks_test.go
│   │   │   ├── parse.go
│   │   │   ├── parse_test.go
│   │   │   ├── support.go
│   │   │   ├── support_test.go
│   │   │   └── tcp.go
│   │   ├── logger.go
│   │   ├── outboundsubnets.go
│   │   ├── ports.go
│   │   ├── redirect.go
│   │   ├── vpn.go
│   │   └── wrappers.go
│   ├── format/
│   │   ├── duration.go
│   │   └── duration_test.go
│   ├── healthcheck/
│   │   ├── checker.go
│   │   ├── checker_test.go
│   │   ├── client.go
│   │   ├── dns/
│   │   │   └── dns.go
│   │   ├── handler.go
│   │   ├── icmp/
│   │   │   ├── apple_ipv4.go
│   │   │   ├── echo.go
│   │   │   ├── interfaces.go
│   │   │   └── listen.go
│   │   ├── interfaces.go
│   │   ├── run.go
│   │   └── server.go
│   ├── httpproxy/
│   │   ├── accept.go
│   │   ├── auth.go
│   │   ├── handler.go
│   │   ├── handler_test.go
│   │   ├── http.go
│   │   ├── https.go
│   │   ├── logger.go
│   │   ├── loop.go
│   │   ├── run.go
│   │   ├── server.go
│   │   ├── settings.go
│   │   ├── state/
│   │   │   ├── settings.go
│   │   │   └── state.go
│   │   └── status.go
│   ├── httpserver/
│   │   ├── address.go
│   │   ├── helpers_test.go
│   │   ├── logger.go
│   │   ├── logger_mock_test.go
│   │   ├── run.go
│   │   ├── run_test.go
│   │   ├── server.go
│   │   ├── server_test.go
│   │   ├── settings.go
│   │   └── settings_test.go
│   ├── loopstate/
│   │   ├── apply.go
│   │   ├── get.go
│   │   ├── lock.go
│   │   ├── set.go
│   │   └── state.go
│   ├── mod/
│   │   ├── configgz_linux.go
│   │   ├── info_linux.go
│   │   ├── load_linux.go
│   │   ├── probe_linux.go
│   │   └── probe_unspecified.go
│   ├── models/
│   │   ├── alias.go
│   │   ├── build.go
│   │   ├── connection.go
│   │   ├── filters.go
│   │   ├── markdown.go
│   │   ├── markdown_test.go
│   │   ├── publicip.go
│   │   ├── server.go
│   │   ├── server_test.go
│   │   ├── servers.go
│   │   ├── servers_test.go
│   │   └── sort.go
│   ├── natpmp/
│   │   ├── checks.go
│   │   ├── checks_test.go
│   │   ├── externaladdress.go
│   │   ├── externaladdress_test.go
│   │   ├── helpers_test.go
│   │   ├── natpmp.go
│   │   ├── natpmp_test.go
│   │   ├── portmapping.go
│   │   ├── portmapping_test.go
│   │   ├── rpc.go
│   │   └── rpc_test.go
│   ├── netlink/
│   │   ├── address.go
│   │   ├── conntrack_linux.go
│   │   ├── conntrack_unspecified.go
│   │   ├── conversion.go
│   │   ├── conversion_test.go
│   │   ├── family.go
│   │   ├── family_linux.go
│   │   ├── helpers_test.go
│   │   ├── interfaces.go
│   │   ├── ipv6.go
│   │   ├── link.go
│   │   ├── link_linux.go
│   │   ├── link_test.go
│   │   ├── netlink.go
│   │   ├── netlink_unspecified.go
│   │   ├── route.go
│   │   ├── route_linux.go
│   │   ├── rule.go
│   │   ├── rule_linux.go
│   │   ├── rule_test.go
│   │   ├── wireguard_linux.go
│   │   └── wireguard_test.go
│   ├── openvpn/
│   │   ├── auth.go
│   │   ├── config.go
│   │   ├── extract/
│   │   │   ├── data.go
│   │   │   ├── extract.go
│   │   │   ├── extract_test.go
│   │   │   ├── extractor.go
│   │   │   ├── helpers_test.go
│   │   │   ├── pem.go
│   │   │   ├── pem_test.go
│   │   │   ├── read.go
│   │   │   └── read_test.go
│   │   ├── interfaces.go
│   │   ├── logger.go
│   │   ├── logs.go
│   │   ├── logs_test.go
│   │   ├── openvpn.go
│   │   ├── paths.go
│   │   ├── pkcs8/
│   │   │   ├── algorithms.go
│   │   │   ├── algorithms_test.go
│   │   │   ├── descbc.go
│   │   │   ├── testdata/
│   │   │   │   ├── readme.txt
│   │   │   │   ├── rsa_pkcs8_aes128cbc_decrypted.pem
│   │   │   │   ├── rsa_pkcs8_aes128cbc_encrypted.pem
│   │   │   │   ├── rsa_pkcs8_descbc_decrypted.pem
│   │   │   │   └── rsa_pkcs8_descbc_encrypted.pem
│   │   │   ├── upgrade.go
│   │   │   └── upgrade_test.go
│   │   ├── run.go
│   │   ├── start.go
│   │   ├── start_linux.go
│   │   ├── start_unspecified.go
│   │   ├── stream.go
│   │   └── version.go
│   ├── pmtud/
│   │   ├── constants/
│   │   │   ├── lengths.go
│   │   │   ├── syscall_unix.go
│   │   │   └── syscall_windows.go
│   │   ├── icmp/
│   │   │   ├── apple_ipv4.go
│   │   │   ├── check.go
│   │   │   ├── df_linux.go
│   │   │   ├── df_unspecified.go
│   │   │   ├── df_windows.go
│   │   │   ├── errors.go
│   │   │   ├── icmp.go
│   │   │   ├── interfaces.go
│   │   │   ├── ipv4.go
│   │   │   ├── ipv6.go
│   │   │   ├── message.go
│   │   │   └── multi.go
│   │   ├── interfaces.go
│   │   ├── ip/
│   │   │   ├── family.go
│   │   │   ├── ipheader.go
│   │   │   ├── ipheader_darwin.go
│   │   │   ├── ipheader_unspecified.go
│   │   │   ├── ipv4_unix.go
│   │   │   ├── ipv4_unspecified.go
│   │   │   ├── ipv4_windows.go
│   │   │   ├── source.go
│   │   │   ├── source_unix.go
│   │   │   └── source_windows.go
│   │   ├── nooplogger.go
│   │   ├── pmtud.go
│   │   ├── pmtud_integration_test.go
│   │   ├── tcp/
│   │   │   ├── helpers_test.go
│   │   │   ├── interfaces.go
│   │   │   ├── mocks_generate_test.go
│   │   │   ├── mocks_test.go
│   │   │   ├── mss.go
│   │   │   ├── mss_test.go
│   │   │   ├── multi.go
│   │   │   ├── packet.go
│   │   │   ├── tcp.go
│   │   │   ├── tcp_darwin.go
│   │   │   ├── tcp_integration_test.go
│   │   │   ├── tcp_linux.go
│   │   │   ├── tcp_notdarwin.go
│   │   │   ├── tcp_test.go
│   │   │   ├── tcp_unix.go
│   │   │   ├── tcp_unspecified.go
│   │   │   ├── tcp_windows.go
│   │   │   ├── tcpheader.go
│   │   │   └── tracker.go
│   │   ├── test/
│   │   │   ├── mtu.go
│   │   │   └── mtu_test.go
│   │   └── vpn.go
│   ├── portforward/
│   │   ├── interfaces.go
│   │   ├── loop.go
│   │   ├── service/
│   │   │   ├── command.go
│   │   │   ├── command_test.go
│   │   │   ├── fs.go
│   │   │   ├── helpers.go
│   │   │   ├── helpers_test.go
│   │   │   ├── interfaces.go
│   │   │   ├── mocks_generate_test.go
│   │   │   ├── mocks_test.go
│   │   │   ├── service.go
│   │   │   ├── settings.go
│   │   │   ├── start.go
│   │   │   └── stop.go
│   │   └── settings.go
│   ├── pprof/
│   │   ├── helpers_test.go
│   │   ├── logger_mock_test.go
│   │   ├── server.go
│   │   ├── server_test.go
│   │   ├── settings.go
│   │   └── settings_test.go
│   ├── provider/
│   │   ├── airvpn/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── api.go
│   │   │       ├── servers.go
│   │   │       └── updater.go
│   │   ├── common/
│   │   │   ├── mocks.go
│   │   │   ├── mocks_generate_test.go
│   │   │   ├── portforward.go
│   │   │   ├── storage.go
│   │   │   └── updater.go
│   │   ├── custom/
│   │   │   ├── connection.go
│   │   │   ├── interfaces.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── openvpnconf_test.go
│   │   │   └── provider.go
│   │   ├── cyberghost/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── constants.go
│   │   │       ├── countries.go
│   │   │       ├── hosttoserver.go
│   │   │       ├── resolve.go
│   │   │       ├── servers.go
│   │   │       └── updater.go
│   │   ├── example/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── api.go
│   │   │       ├── resolve.go
│   │   │       ├── servers.go
│   │   │       └── updater.go
│   │   ├── expressvpn/
│   │   │   ├── connection.go
│   │   │   ├── connection_test.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── hardcoded.go
│   │   │       ├── resolve.go
│   │   │       ├── servers.go
│   │   │       └── updater.go
│   │   ├── fastestvpn/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── api.go
│   │   │       ├── api_test.go
│   │   │       ├── hosttoserver.go
│   │   │       ├── resolve.go
│   │   │       ├── servers.go
│   │   │       └── updater.go
│   │   ├── giganews/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── filename.go
│   │   │       ├── hosttoserver.go
│   │   │       ├── resolve.go
│   │   │       ├── servers.go
│   │   │       └── updater.go
│   │   ├── hidemyass/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── hosts.go
│   │   │       ├── hosttourl.go
│   │   │       ├── index.go
│   │   │       ├── resolve.go
│   │   │       ├── servers.go
│   │   │       ├── updater.go
│   │   │       └── url.go
│   │   ├── ipvanish/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── filename.go
│   │   │       ├── filename_test.go
│   │   │       ├── hosttoserver.go
│   │   │       ├── hosttoserver_test.go
│   │   │       ├── resolve.go
│   │   │       ├── servers.go
│   │   │       ├── servers_test.go
│   │   │       └── updater.go
│   │   ├── ivpn/
│   │   │   ├── connection.go
│   │   │   ├── connection_test.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── api.go
│   │   │       ├── api_test.go
│   │   │       ├── resolve.go
│   │   │       ├── roundtrip_test.go
│   │   │       ├── servers.go
│   │   │       ├── servers_test.go
│   │   │       └── updater.go
│   │   ├── mullvad/
│   │   │   ├── connection.go
│   │   │   ├── connection_test.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── api.go
│   │   │       ├── hosttoserver.go
│   │   │       ├── ips.go
│   │   │       ├── ips_test.go
│   │   │       ├── servers.go
│   │   │       └── updater.go
│   │   ├── nordvpn/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── api.go
│   │   │       ├── models.go
│   │   │       ├── name.go
│   │   │       ├── servers.go
│   │   │       └── updater.go
│   │   ├── perfectprivacy/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── portforward.go
│   │   │   ├── portforward_test.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── citytoserver.go
│   │   │       ├── filename.go
│   │   │       ├── servers.go
│   │   │       └── updater.go
│   │   ├── privado/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── servers.go
│   │   │       └── updater.go
│   │   ├── privateinternetaccess/
│   │   │   ├── connection.go
│   │   │   ├── httpclient.go
│   │   │   ├── httpclient_test.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── portforward.go
│   │   │   ├── portforward_test.go
│   │   │   ├── presets/
│   │   │   │   └── presets.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── api.go
│   │   │       ├── hosttoserver.go
│   │   │       ├── servers.go
│   │   │       └── updater.go
│   │   ├── privatevpn/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── portforward.go
│   │   │   ├── portforward_test.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── countries.go
│   │   │       ├── filename.go
│   │   │       ├── hosttoserver.go
│   │   │       ├── resolve.go
│   │   │       ├── servers.go
│   │   │       └── updater.go
│   │   ├── protonvpn/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── portforward.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── api.go
│   │   │       ├── countries.go
│   │   │       ├── iptoserver.go
│   │   │       ├── servers.go
│   │   │       ├── updater.go
│   │   │       └── version.go
│   │   ├── provider.go
│   │   ├── providers.go
│   │   ├── purevpn/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── compare.go
│   │   │       ├── compare_test.go
│   │   │       ├── hosttoserver.go
│   │   │       ├── parse.go
│   │   │       ├── resolve.go
│   │   │       ├── servers.go
│   │   │       └── updater.go
│   │   ├── slickvpn/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── resolve.go
│   │   │       ├── servers.go
│   │   │       └── updater.go
│   │   ├── surfshark/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   ├── servers/
│   │   │   │   └── locationdata.go
│   │   │   └── updater/
│   │   │       ├── api.go
│   │   │       ├── api_test.go
│   │   │       ├── hosttoserver.go
│   │   │       ├── location.go
│   │   │       ├── remaining.go
│   │   │       ├── resolve.go
│   │   │       ├── roundtrip_test.go
│   │   │       ├── servers.go
│   │   │       ├── updater.go
│   │   │       └── zip.go
│   │   ├── torguard/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── filename.go
│   │   │       ├── hosttoserver.go
│   │   │       ├── resolve.go
│   │   │       ├── servers.go
│   │   │       └── updater.go
│   │   ├── utils/
│   │   │   ├── cipher.go
│   │   │   ├── cipher_test.go
│   │   │   ├── connection.go
│   │   │   ├── connection_test.go
│   │   │   ├── filtering.go
│   │   │   ├── filtering_test.go
│   │   │   ├── logger.go
│   │   │   ├── nofetcher.go
│   │   │   ├── openvpn.go
│   │   │   ├── pick.go
│   │   │   ├── pick_test.go
│   │   │   ├── port.go
│   │   │   ├── port_test.go
│   │   │   ├── portforward.go
│   │   │   ├── protocol.go
│   │   │   └── protocol_test.go
│   │   ├── vpnsecure/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── helpers_test.go
│   │   │       ├── hosttoserver.go
│   │   │       ├── resolve.go
│   │   │       ├── servers.go
│   │   │       ├── testdata/
│   │   │       │   └── index.html
│   │   │       ├── updater.go
│   │   │       ├── website.go
│   │   │       └── website_test.go
│   │   ├── vpnunlimited/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── constants.go
│   │   │       ├── hosttoserver.go
│   │   │       ├── resolve.go
│   │   │       ├── servers.go
│   │   │       └── updater.go
│   │   ├── vyprvpn/
│   │   │   ├── connection.go
│   │   │   ├── openvpnconf.go
│   │   │   ├── provider.go
│   │   │   └── updater/
│   │   │       ├── filename.go
│   │   │       ├── hosttoserver.go
│   │   │       ├── resolve.go
│   │   │       ├── servers.go
│   │   │       └── updater.go
│   │   └── windscribe/
│   │       ├── connection.go
│   │       ├── connection_test.go
│   │       ├── openvpnconf.go
│   │       ├── provider.go
│   │       └── updater/
│   │           ├── api.go
│   │           ├── servers.go
│   │           └── updater.go
│   ├── publicip/
│   │   ├── api/
│   │   │   ├── api.go
│   │   │   ├── api_test.go
│   │   │   ├── cloudflare.go
│   │   │   ├── echoip.go
│   │   │   ├── errors.go
│   │   │   ├── interfaces.go
│   │   │   ├── ip2location.go
│   │   │   ├── ipinfo.go
│   │   │   ├── multi.go
│   │   │   ├── resilient.go
│   │   │   └── resilient_test.go
│   │   ├── data.go
│   │   ├── fs.go
│   │   ├── interfaces.go
│   │   ├── loop.go
│   │   └── update.go
│   ├── routing/
│   │   ├── conversion.go
│   │   ├── conversion_test.go
│   │   ├── default.go
│   │   ├── enable.go
│   │   ├── errors.go
│   │   ├── inbound.go
│   │   ├── ip.go
│   │   ├── ip_test.go
│   │   ├── local.go
│   │   ├── logger.go
│   │   ├── mocks_generate_test.go
│   │   ├── mocks_test.go
│   │   ├── outbound.go
│   │   ├── routes.go
│   │   ├── routing.go
│   │   ├── rules.go
│   │   ├── rules_test.go
│   │   ├── tables_linux.go
│   │   ├── tables_unspecified.go
│   │   └── vpn.go
│   ├── server/
│   │   ├── dns.go
│   │   ├── handler.go
│   │   ├── handlerv0.go
│   │   ├── handlerv1.go
│   │   ├── helpers.go
│   │   ├── interfaces.go
│   │   ├── logger.go
│   │   ├── middlewares/
│   │   │   ├── auth/
│   │   │   │   ├── apikey.go
│   │   │   │   ├── basic.go
│   │   │   │   ├── configfile.go
│   │   │   │   ├── configfile_test.go
│   │   │   │   ├── format.go
│   │   │   │   ├── interfaces.go
│   │   │   │   ├── interfaces_local.go
│   │   │   │   ├── lookup.go
│   │   │   │   ├── lookup_test.go
│   │   │   │   ├── middleware.go
│   │   │   │   ├── middleware_test.go
│   │   │   │   ├── mocks_generate_test.go
│   │   │   │   ├── mocks_test.go
│   │   │   │   ├── none.go
│   │   │   │   └── settings.go
│   │   │   └── log/
│   │   │       ├── interfaces.go
│   │   │       └── middleware.go
│   │   ├── openvpn.go
│   │   ├── portforward.go
│   │   ├── publicip.go
│   │   ├── server.go
│   │   ├── updater.go
│   │   ├── vpn.go
│   │   └── wrappers.go
│   ├── shadowsocks/
│   │   ├── logger.go
│   │   ├── loop.go
│   │   └── state.go
│   ├── storage/
│   │   ├── choices.go
│   │   ├── copy.go
│   │   ├── copy_test.go
│   │   ├── filter.go
│   │   ├── flush.go
│   │   ├── formatting.go
│   │   ├── hardcoded.go
│   │   ├── hardcoded_test.go
│   │   ├── helpers.go
│   │   ├── merge.go
│   │   ├── mocks_generate_test.go
│   │   ├── mocks_test.go
│   │   ├── read.go
│   │   ├── read_test.go
│   │   ├── servers.go
│   │   ├── servers.json
│   │   ├── storage.go
│   │   └── sync.go
│   ├── subnet/
│   │   └── subsets.go
│   ├── tun/
│   │   ├── check.go
│   │   ├── check_unspecified.go
│   │   ├── create.go
│   │   ├── create_unspecified.go
│   │   ├── tun.go
│   │   └── tun_test.go
│   ├── updater/
│   │   ├── html/
│   │   │   ├── attribute.go
│   │   │   ├── bfs.go
│   │   │   ├── css.go
│   │   │   ├── errors.go
│   │   │   ├── fetch.go
│   │   │   ├── fetch_test.go
│   │   │   └── match.go
│   │   ├── interfaces.go
│   │   ├── loop/
│   │   │   ├── loop.go
│   │   │   └── state.go
│   │   ├── openvpn/
│   │   │   ├── extract.go
│   │   │   ├── fetch.go
│   │   │   └── multifetch.go
│   │   ├── providers.go
│   │   ├── resolver/
│   │   │   ├── interfaces.go
│   │   │   ├── ips.go
│   │   │   ├── ips_test.go
│   │   │   ├── net.go
│   │   │   ├── parallel.go
│   │   │   └── repeat.go
│   │   ├── unzip/
│   │   │   ├── extract.go
│   │   │   ├── fetch.go
│   │   │   └── unzip.go
│   │   └── updater.go
│   ├── version/
│   │   ├── github.go
│   │   └── version.go
│   ├── vpn/
│   │   ├── amneziawg.go
│   │   ├── cleanup.go
│   │   ├── helpers.go
│   │   ├── interfaces.go
│   │   ├── loop.go
│   │   ├── openvpn.go
│   │   ├── portforward.go
│   │   ├── run.go
│   │   ├── settings.go
│   │   ├── state/
│   │   │   ├── state.go
│   │   │   └── vpn.go
│   │   ├── status.go
│   │   ├── tunnelup.go
│   │   ├── wireguard.go
│   │   └── wireguard_test.go
│   └── wireguard/
│       ├── address.go
│       ├── address_test.go
│       ├── config.go
│       ├── config_test.go
│       ├── constructor.go
│       ├── constructor_test.go
│       ├── helpers_test.go
│       ├── log.go
│       ├── log_mock_test.go
│       ├── log_test.go
│       ├── netlink_integration_test.go
│       ├── netlinker.go
│       ├── netlinker_mock_test.go
│       ├── route.go
│       ├── route_test.go
│       ├── rule.go
│       ├── rule_test.go
│       ├── run.go
│       ├── settings.go
│       ├── settings_test.go
│       ├── wireguard_linux.go
│       └── wireguard_unspecified.go
└── maintenance.md
Download .txt
Showing preview only (243K chars total). Download the full file or copy to clipboard to get everything.
SYMBOL INDEX (2510 symbols across 737 files)

FILE: ci/cmd/main.go
  function main (line 13) | func main() {

FILE: ci/internal/mullvad.go
  function MullvadTest (line 8) | func MullvadTest(ctx context.Context, logger Logger) error {

FILE: ci/internal/protonvpn.go
  function ProtonVPNTest (line 8) | func ProtonVPNTest(ctx context.Context, logger Logger) error {

FILE: ci/internal/secrets.go
  type Logger (line 11) | type Logger interface
  function readSecrets (line 16) | func readSecrets(ctx context.Context, expectedSecrets []string,

FILE: ci/internal/simple.go
  function ptrTo (line 17) | func ptrTo[T any](v T) *T { return &v }
  function simpleTest (line 19) | func simpleTest(ctx context.Context, env []string, logger Logger) error {
  function stopContainer (line 63) | func stopContainer(client *client.Client, containerID string) {
  function waitForLogLine (line 76) | func waitForLogLine(ctx context.Context, client *client.Client, containe...
  function logSeenLines (line 129) | func logSeenLines(logger Logger, lines []string) {

FILE: cmd/gluetun/main.go
  function main (line 68) | func main() {
  function _main (line 148) | func _main(ctx context.Context, buildInfo models.BuildInformation,
  type printVersionElement (line 540) | type printVersionElement struct
  type infoer (line 545) | type infoer interface
  function printVersions (line 549) | func printVersions(ctx context.Context, logger infoer,
  function localNetworksToPrefixes (line 567) | func localNetworksToPrefixes(localNetworks []routing.LocalNetwork) (pref...
  type netLinker (line 575) | type netLinker interface
  type Addresser (line 586) | type Addresser interface
  type Router (line 592) | type Router interface
  type Ruler (line 599) | type Ruler interface
  type Linker (line 605) | type Linker interface
  type clier (line 616) | type clier interface
  type Tun (line 625) | type Tun interface
  type RunStarter (line 630) | type RunStarter interface
  constant gluetunLogo (line 638) | gluetunLogo = `                         @@@

FILE: internal/alpine/alpine.go
  type Alpine (line 7) | type Alpine struct
  function New (line 14) | func New() *Alpine {

FILE: internal/alpine/users.go
  method CreateUser (line 15) | func (a *Alpine) CreateUser(username string, uid int) (createdUsername s...

FILE: internal/alpine/version.go
  method Version (line 10) | func (a *Alpine) Version(context.Context) (version string, err error) {

FILE: internal/amneziawg/constructor.go
  type Amneziawg (line 3) | type Amneziawg struct
  function New (line 9) | func New(settings Settings, netlink NetLinker,

FILE: internal/amneziawg/constructor_test.go
  function Test_New (line 13) | func Test_New(t *testing.T) {

FILE: internal/amneziawg/helpers_test.go
  function ptrTo (line 3) | func ptrTo[T any](v T) *T {

FILE: internal/amneziawg/log.go
  type Logger (line 5) | type Logger interface

FILE: internal/amneziawg/log_mock_test.go
  type MockLogger (line 14) | type MockLogger struct
    method EXPECT (line 32) | func (m *MockLogger) EXPECT() *MockLoggerMockRecorder {
    method Debug (line 37) | func (m *MockLogger) Debug(arg0 string) {
    method Debugf (line 49) | func (m *MockLogger) Debugf(arg0 string, arg1 ...interface{}) {
    method Error (line 66) | func (m *MockLogger) Error(arg0 string) {
    method Errorf (line 78) | func (m *MockLogger) Errorf(arg0 string, arg1 ...interface{}) {
    method Info (line 95) | func (m *MockLogger) Info(arg0 string) {
  type MockLoggerMockRecorder (line 20) | type MockLoggerMockRecorder struct
    method Debug (line 43) | func (mr *MockLoggerMockRecorder) Debug(arg0 interface{}) *gomock.Call {
    method Debugf (line 59) | func (mr *MockLoggerMockRecorder) Debugf(arg0 interface{}, arg1 ...int...
    method Error (line 72) | func (mr *MockLoggerMockRecorder) Error(arg0 interface{}) *gomock.Call {
    method Errorf (line 88) | func (mr *MockLoggerMockRecorder) Errorf(arg0 interface{}, arg1 ...int...
    method Info (line 101) | func (mr *MockLoggerMockRecorder) Info(arg0 interface{}) *gomock.Call {
  function NewMockLogger (line 25) | func NewMockLogger(ctrl *gomock.Controller) *MockLogger {

FILE: internal/amneziawg/netlinker.go
  type NetLinker (line 11) | type NetLinker interface
  type Router (line 19) | type Router interface
  type Ruler (line 24) | type Ruler interface
  type Linker (line 29) | type Linker interface

FILE: internal/amneziawg/netlinker_mock_test.go
  type MockNetLinker (line 16) | type MockNetLinker struct
    method EXPECT (line 34) | func (m *MockNetLinker) EXPECT() *MockNetLinkerMockRecorder {
    method AddrReplace (line 39) | func (m *MockNetLinker) AddrReplace(arg0 uint32, arg1 netip.Prefix) er...
    method IsWireguardSupported (line 53) | func (m *MockNetLinker) IsWireguardSupported() (bool, error) {
    method LinkAdd (line 68) | func (m *MockNetLinker) LinkAdd(arg0 netlink.Link) (uint32, error) {
    method LinkByName (line 83) | func (m *MockNetLinker) LinkByName(arg0 string) (netlink.Link, error) {
    method LinkDel (line 98) | func (m *MockNetLinker) LinkDel(arg0 uint32) error {
    method LinkList (line 112) | func (m *MockNetLinker) LinkList() ([]netlink.Link, error) {
    method LinkSetDown (line 127) | func (m *MockNetLinker) LinkSetDown(arg0 uint32) error {
    method LinkSetUp (line 141) | func (m *MockNetLinker) LinkSetUp(arg0 uint32) error {
    method RouteAdd (line 155) | func (m *MockNetLinker) RouteAdd(arg0 netlink.Route) error {
    method RouteList (line 169) | func (m *MockNetLinker) RouteList(arg0 byte) ([]netlink.Route, error) {
    method RuleAdd (line 184) | func (m *MockNetLinker) RuleAdd(arg0 netlink.Rule) error {
    method RuleDel (line 198) | func (m *MockNetLinker) RuleDel(arg0 netlink.Rule) error {
  type MockNetLinkerMockRecorder (line 22) | type MockNetLinkerMockRecorder struct
    method AddrReplace (line 47) | func (mr *MockNetLinkerMockRecorder) AddrReplace(arg0, arg1 interface{...
    method IsWireguardSupported (line 62) | func (mr *MockNetLinkerMockRecorder) IsWireguardSupported() *gomock.Ca...
    method LinkAdd (line 77) | func (mr *MockNetLinkerMockRecorder) LinkAdd(arg0 interface{}) *gomock...
    method LinkByName (line 92) | func (mr *MockNetLinkerMockRecorder) LinkByName(arg0 interface{}) *gom...
    method LinkDel (line 106) | func (mr *MockNetLinkerMockRecorder) LinkDel(arg0 interface{}) *gomock...
    method LinkList (line 121) | func (mr *MockNetLinkerMockRecorder) LinkList() *gomock.Call {
    method LinkSetDown (line 135) | func (mr *MockNetLinkerMockRecorder) LinkSetDown(arg0 interface{}) *go...
    method LinkSetUp (line 149) | func (mr *MockNetLinkerMockRecorder) LinkSetUp(arg0 interface{}) *gomo...
    method RouteAdd (line 163) | func (mr *MockNetLinkerMockRecorder) RouteAdd(arg0 interface{}) *gomoc...
    method RouteList (line 178) | func (mr *MockNetLinkerMockRecorder) RouteList(arg0 interface{}) *gomo...
    method RuleAdd (line 192) | func (mr *MockNetLinkerMockRecorder) RuleAdd(arg0 interface{}) *gomock...
    method RuleDel (line 206) | func (mr *MockNetLinkerMockRecorder) RuleDel(arg0 interface{}) *gomock...
  function NewMockNetLinker (line 27) | func NewMockNetLinker(ctrl *gomock.Controller) *MockNetLinker {

FILE: internal/amneziawg/run.go
  method Run (line 26) | func (a *Amneziawg) Run(ctx context.Context, waitError chan<- error, rea...
  function setupUserspace (line 37) | func setupUserspace(ctx context.Context,
  function acceptAndHandle (line 122) | func acceptAndHandle(uapi net.Listener, device *amneziadevice.Device,

FILE: internal/amneziawg/settings.go
  type Settings (line 10) | type Settings struct
    method uapiConfig (line 30) | func (s Settings) uapiConfig() string {
    method SetDefaults (line 63) | func (s *Settings) SetDefaults() {
    method Check (line 67) | func (s *Settings) Check() error {

FILE: internal/boringpoll/boringpoll.go
  type BoringPoll (line 15) | type BoringPoll struct
    method Start (line 43) | func (b *BoringPoll) Start() (runError <-chan error, err error) {
    method Stop (line 161) | func (b *BoringPoll) Stop() error {
  type urlData (line 29) | type urlData struct
  function New (line 31) | func New(client *http.Client, logger Logger, settings settings.BoringPol...
  function fetchURL (line 114) | func fetchURL(ctx context.Context, client *http.Client, url string) (dow...
  function getRandomUserAgent (line 140) | func getRandomUserAgent() string {
  function byteCountSI (line 175) | func byteCountSI(b uint64) string {

FILE: internal/boringpoll/interfaces.go
  type Logger (line 3) | type Logger interface

FILE: internal/cleanup/cleanup.go
  type Cleanups (line 5) | type Cleanups
    method Add (line 18) | func (c *Cleanups) Add(operation string, orderIndex uint,
    method Cleanup (line 33) | func (c *Cleanups) Cleanup(logger Logger) {
  type cleanup (line 7) | type cleanup struct

FILE: internal/cleanup/cleanup_test.go
  function Test_Cleanups (line 11) | func Test_Cleanups(t *testing.T) {

FILE: internal/cleanup/interfaces.go
  type Logger (line 3) | type Logger interface

FILE: internal/cleanup/mocks_test.go
  type MockLogger (line 14) | type MockLogger struct
    method EXPECT (line 32) | func (m *MockLogger) EXPECT() *MockLoggerMockRecorder {
    method Debug (line 37) | func (m *MockLogger) Debug(arg0 string) {
    method Error (line 49) | func (m *MockLogger) Error(arg0 string) {
  type MockLoggerMockRecorder (line 20) | type MockLoggerMockRecorder struct
    method Debug (line 43) | func (mr *MockLoggerMockRecorder) Debug(arg0 interface{}) *gomock.Call {
    method Error (line 55) | func (mr *MockLoggerMockRecorder) Error(arg0 interface{}) *gomock.Call {
  function NewMockLogger (line 25) | func NewMockLogger(ctrl *gomock.Controller) *MockLogger {

FILE: internal/cli/ci.go
  method CI (line 5) | func (c *CLI) CI(context.Context) error {

FILE: internal/cli/cli.go
  type CLI (line 3) | type CLI struct
  function New (line 7) | func New() *CLI {

FILE: internal/cli/clientkey.go
  method ClientKey (line 11) | func (c *CLI) ClientKey(args []string) error {

FILE: internal/cli/formatservers.go
  function addProviderFlag (line 24) | func addProviderFlag(flagSet *flag.FlagSet, providerToFormat map[string]...
  method FormatServers (line 34) | func (c *CLI) FormatServers(args []string) error {

FILE: internal/cli/genkey.go
  method GenKey (line 9) | func (c *CLI) GenKey(args []string) (err error) {
  function base58Encode (line 27) | func base58Encode(data []byte) string {

FILE: internal/cli/healthcheck.go
  method HealthCheck (line 14) | func (c *CLI) HealthCheck(ctx context.Context, reader *reader.Reader, _ ...

FILE: internal/cli/interfaces.go
  type Source (line 5) | type Source interface

FILE: internal/cli/nooplogger.go
  type noopLogger (line 3) | type noopLogger struct
    method Info (line 9) | func (l *noopLogger) Info(string) {}
    method Warn (line 10) | func (l *noopLogger) Warn(string) {}
  function newNoopLogger (line 5) | func newNoopLogger() *noopLogger {

FILE: internal/cli/openvpnconfig.go
  type OpenvpnConfigLogger (line 21) | type OpenvpnConfigLogger interface
  type Unzipper (line 26) | type Unzipper interface
  type ParallelResolver (line 31) | type ParallelResolver interface
  type IPFetcher (line 36) | type IPFetcher interface
  type IPv6Checker (line 42) | type IPv6Checker interface
  method OpenvpnConfig (line 46) | func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, reader *reader.R...

FILE: internal/cli/update.go
  type UpdaterLogger (line 34) | type UpdaterLogger interface
  method Update (line 40) | func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterL...

FILE: internal/cli/warner.go
  type Warner (line 3) | type Warner interface

FILE: internal/command/cmder.go
  type Cmder (line 4) | type Cmder struct
  function New (line 6) | func New() *Cmder {

FILE: internal/command/interfaces.go
  type Logger (line 3) | type Logger interface

FILE: internal/command/interfaces_local.go
  type execCmd (line 5) | type execCmd interface

FILE: internal/command/mocks_local_test.go
  type MockexecCmd (line 15) | type MockexecCmd struct
    method EXPECT (line 33) | func (m *MockexecCmd) EXPECT() *MockexecCmdMockRecorder {
    method CombinedOutput (line 38) | func (m *MockexecCmd) CombinedOutput() ([]byte, error) {
    method Start (line 53) | func (m *MockexecCmd) Start() error {
    method StderrPipe (line 67) | func (m *MockexecCmd) StderrPipe() (io.ReadCloser, error) {
    method StdoutPipe (line 82) | func (m *MockexecCmd) StdoutPipe() (io.ReadCloser, error) {
    method Wait (line 97) | func (m *MockexecCmd) Wait() error {
  type MockexecCmdMockRecorder (line 21) | type MockexecCmdMockRecorder struct
    method CombinedOutput (line 47) | func (mr *MockexecCmdMockRecorder) CombinedOutput() *gomock.Call {
    method Start (line 61) | func (mr *MockexecCmdMockRecorder) Start() *gomock.Call {
    method StderrPipe (line 76) | func (mr *MockexecCmdMockRecorder) StderrPipe() *gomock.Call {
    method StdoutPipe (line 91) | func (mr *MockexecCmdMockRecorder) StdoutPipe() *gomock.Call {
    method Wait (line 105) | func (mr *MockexecCmdMockRecorder) Wait() *gomock.Call {
  function NewMockexecCmd (line 26) | func NewMockexecCmd(ctrl *gomock.Controller) *MockexecCmd {

FILE: internal/command/run.go
  method Run (line 10) | func (c *Cmder) Run(cmd *exec.Cmd) (output string, err error) {
  function run (line 14) | func run(cmd execCmd) (output string, err error) {
  function stringToLines (line 27) | func stringToLines(s string) (lines []string) {

FILE: internal/command/run_test.go
  function Test_run (line 12) | func Test_run(t *testing.T) {

FILE: internal/command/split.go
  function split (line 26) | func split(command string) (words []string, err error) {
  function splitWord (line 65) | func splitWord(input string, startIndex int, buffer *bytes.Buffer) (
  function handleDoubleQuoted (line 97) | func handleDoubleQuoted(input string, startIndex int, buffer *bytes.Buff...
  function handleSingleQuoted (line 125) | func handleSingleQuoted(input string, startIndex int, buffer *bytes.Buff...
  function handleEscaped (line 138) | func handleEscaped(input string, startIndex int, buffer *bytes.Buffer) (

FILE: internal/command/split_test.go
  function Test_split (line 9) | func Test_split(t *testing.T) {

FILE: internal/command/start.go
  method Start (line 14) | func (c *Cmder) Start(cmd *exec.Cmd) (
  function start (line 21) | func start(cmd execCmd) (stdoutLines, stderrLines <-chan string,
  function streamToChannel (line 71) | func streamToChannel(ready chan<- struct{},

FILE: internal/command/start_test.go
  function linesToReadCloser (line 15) | func linesToReadCloser(lines []string) io.ReadCloser {
  function Test_start (line 20) | func Test_start(t *testing.T) {

FILE: internal/command/startnlog.go
  method RunAndLog (line 9) | func (c *Cmder) RunAndLog(ctx context.Context, command string, logger Lo...
  function streamLines (line 31) | func streamLines(ctx context.Context, done chan<- struct{},

FILE: internal/configuration/settings/amneziawg.go
  type AmneziaWg (line 14) | type AmneziaWg struct
    method read (line 36) | func (a *AmneziaWg) read(r *reader.Reader) (err error) {
    method copy (line 76) | func (a AmneziaWg) copy() (copied AmneziaWg) {
    method overrideWith (line 98) | func (a *AmneziaWg) overrideWith(other AmneziaWg) {
    method setDefaults (line 118) | func (a *AmneziaWg) setDefaults(vpnProvider string) {
    method toLinesNode (line 139) | func (a AmneziaWg) toLinesNode() (node *gotree.Node) {
    method validate (line 188) | func (a AmneziaWg) validate(vpnProvider string, ipv6Supported bool) er...

FILE: internal/configuration/settings/boringpoll.go
  type BoringPoll (line 9) | type BoringPoll struct
    method validate (line 13) | func (b BoringPoll) validate() error {
    method Copy (line 17) | func (b BoringPoll) Copy() BoringPoll {
    method overrideWith (line 23) | func (b *BoringPoll) overrideWith(other BoringPoll) {
    method setDefaults (line 27) | func (b *BoringPoll) setDefaults() {
    method String (line 31) | func (b BoringPoll) String() string {
    method toLinesNode (line 35) | func (b BoringPoll) toLinesNode() *gotree.Node {
    method read (line 45) | func (b *BoringPoll) read(r *reader.Reader) (err error) {

FILE: internal/configuration/settings/deprecated.go
  function readObsolete (line 10) | func readObsolete(r *reader.Reader) (warnings []string) {

FILE: internal/configuration/settings/dns.go
  constant DNSUpstreamTypeDot (line 18) | DNSUpstreamTypeDot   = "dot"
  constant DNSUpstreamTypeDoh (line 19) | DNSUpstreamTypeDoh   = "doh"
  constant DNSUpstreamTypePlain (line 20) | DNSUpstreamTypePlain = "plain"
  type DNS (line 24) | type DNS struct
    method validate (line 61) | func (d DNS) validate() (err error) {
    method Copy (line 108) | func (d *DNS) Copy() (copied DNS) {
    method overrideWith (line 123) | func (d *DNS) overrideWith(other DNS) {
    method setDefaults (line 133) | func (d *DNS) setDefaults() {
    method GetFirstPlaintextIPv4 (line 154) | func (d DNS) GetFirstPlaintextIPv4() (ipv4 netip.Addr) {
    method String (line 194) | func (d DNS) String() string {
    method toLinesNode (line 198) | func (d DNS) toLinesNode() (node *gotree.Node) {
    method read (line 232) | func (d *DNS) read(r *reader.Reader) (err error) {
    method readUpstreamPlainAddresses (line 265) | func (d *DNS) readUpstreamPlainAddresses(r *reader.Reader) (err error) {
  function defaultDNSProviders (line 148) | func defaultDNSProviders() []string {
  function findPlainIPv4InProviders (line 178) | func findPlainIPv4InProviders(providerNames []string) netip.Addr {

FILE: internal/configuration/settings/dns_test.go
  function Test_defaultDNSProviders (line 10) | func Test_defaultDNSProviders(t *testing.T) {

FILE: internal/configuration/settings/dnsblacklist.go
  type DNSBlacklist (line 17) | type DNSBlacklist struct
    method setDefaults (line 32) | func (b *DNSBlacklist) setDefaults() {
    method validate (line 46) | func (b DNSBlacklist) validate() (err error) {
    method copy (line 71) | func (b DNSBlacklist) copy() (copied DNSBlacklist) {
    method overrideWith (line 84) | func (b *DNSBlacklist) overrideWith(other DNSBlacklist) {
    method ToBlockBuilderSettings (line 96) | func (b DNSBlacklist) ToBlockBuilderSettings(client *http.Client) (
    method String (line 111) | func (b DNSBlacklist) String() string {
    method toLinesNode (line 115) | func (b DNSBlacklist) toLinesNode() (node *gotree.Node) {
    method read (line 160) | func (b *DNSBlacklist) read(r *reader.Reader) (err error) {
  function readDNSBlockedIPs (line 189) | func readDNSBlockedIPs(r *reader.Reader) (ips []netip.Addr,
  function readDNSPrivateAddresses (line 214) | func readDNSPrivateAddresses(r *reader.Reader) (ips []netip.Addr,

FILE: internal/configuration/settings/firewall.go
  type Firewall (line 13) | type Firewall struct
    method validate (line 21) | func (f Firewall) validate() (err error) {
    method copy (line 53) | func (f *Firewall) copy() (copied Firewall) {
    method overrideWith (line 66) | func (f *Firewall) overrideWith(other Firewall) {
    method setDefaults (line 74) | func (f *Firewall) setDefaults(globalLogLevel string) {
    method String (line 79) | func (f Firewall) String() string {
    method toLinesNode (line 83) | func (f Firewall) toLinesNode() (node *gotree.Node) {
    method read (line 117) | func (f *Firewall) read(r *reader.Reader) (err error) {
  function hasZeroPort (line 44) | func hasZeroPort(ports []uint16) (has bool) {

FILE: internal/configuration/settings/firewall_test.go
  function Test_Firewall_validate (line 11) | func Test_Firewall_validate(t *testing.T) {

FILE: internal/configuration/settings/health.go
  type Health (line 16) | type Health struct
    method Validate (line 47) | func (h Health) Validate() (err error) {
    method copy (line 71) | func (h *Health) copy() (copied Health) {
    method OverrideWith (line 84) | func (h *Health) OverrideWith(other Health) {
    method SetDefaults (line 92) | func (h *Health) SetDefaults() {
    method String (line 103) | func (h Health) String() string {
    method toLinesNode (line 107) | func (h Health) toLinesNode() (node *gotree.Node) {
    method Read (line 132) | func (h *Health) Read(r *reader.Reader) (err error) {

FILE: internal/configuration/settings/helpers.go
  function ptrTo (line 3) | func ptrTo[T any](value T) *T {

FILE: internal/configuration/settings/helpers/belong.go
  function IsOneOf (line 3) | func IsOneOf[T comparable](value T, choices ...T) (ok bool) {

FILE: internal/configuration/settings/helpers_test.go
  type sourceKeyValue (line 5) | type sourceKeyValue struct
  function newMockSource (line 10) | func newMockSource(ctrl *gomock.Controller, keyValues []sourceKeyValue) ...

FILE: internal/configuration/settings/httpproxy.go
  type HTTPProxy (line 16) | type HTTPProxy struct
    method validate (line 47) | func (h HTTPProxy) validate() (err error) {
    method copy (line 57) | func (h *HTTPProxy) copy() (copied HTTPProxy) {
    method overrideWith (line 73) | func (h *HTTPProxy) overrideWith(other HTTPProxy) {
    method setDefaults (line 84) | func (h *HTTPProxy) setDefaults() {
    method String (line 97) | func (h HTTPProxy) String() string {
    method toLinesNode (line 101) | func (h HTTPProxy) toLinesNode() (node *gotree.Node) {
    method read (line 119) | func (h *HTTPProxy) read(r *reader.Reader) (err error) {
  function readHTTProxyListeningAddress (line 151) | func readHTTProxyListeningAddress(r *reader.Reader) (listeningAddress st...
  function readHTTProxyLog (line 165) | func readHTTProxyLog(r *reader.Reader) (enabled *bool, err error) {

FILE: internal/configuration/settings/interfaces.go
  type Warner (line 3) | type Warner interface

FILE: internal/configuration/settings/iptables.go
  type Iptables (line 13) | type Iptables struct
    method validate (line 17) | func (i Iptables) validate() (err error) {
    method copy (line 26) | func (i *Iptables) copy() (copied Iptables) {
    method overrideWith (line 32) | func (i *Iptables) overrideWith(other Iptables) {
    method setDefaults (line 36) | func (i *Iptables) setDefaults(globalLogLevel string) {
    method String (line 47) | func (i Iptables) String() string {
    method toLinesNode (line 51) | func (i Iptables) toLinesNode() (node *gotree.Node) {
    method read (line 57) | func (i *Iptables) read(r *reader.Reader) (err error) {

FILE: internal/configuration/settings/log.go
  type Log (line 13) | type Log struct
    method validate (line 19) | func (l Log) validate() (err error) {
    method copy (line 27) | func (l *Log) copy() (copied Log) {
    method overrideWith (line 36) | func (l *Log) overrideWith(other Log) {
    method setDefaults (line 40) | func (l *Log) setDefaults() {
    method String (line 44) | func (l Log) String() string {
    method toLinesNode (line 48) | func (l Log) toLinesNode() (node *gotree.Node) {
    method read (line 54) | func (l *Log) read(r *reader.Reader) (err error) {

FILE: internal/configuration/settings/mocks_reader_test.go
  type MockSource (line 14) | type MockSource struct
    method EXPECT (line 32) | func (m *MockSource) EXPECT() *MockSourceMockRecorder {
    method Get (line 37) | func (m *MockSource) Get(arg0 string) (string, bool) {
    method KeyTransform (line 52) | func (m *MockSource) KeyTransform(arg0 string) string {
    method String (line 66) | func (m *MockSource) String() string {
  type MockSourceMockRecorder (line 20) | type MockSourceMockRecorder struct
    method Get (line 46) | func (mr *MockSourceMockRecorder) Get(arg0 interface{}) *gomock.Call {
    method KeyTransform (line 60) | func (mr *MockSourceMockRecorder) KeyTransform(arg0 interface{}) *gomo...
    method String (line 74) | func (mr *MockSourceMockRecorder) String() *gomock.Call {
  function NewMockSource (line 25) | func NewMockSource(ctrl *gomock.Controller) *MockSource {

FILE: internal/configuration/settings/mocks_test.go
  type MockWarner (line 14) | type MockWarner struct
    method EXPECT (line 32) | func (m *MockWarner) EXPECT() *MockWarnerMockRecorder {
    method Warn (line 37) | func (m *MockWarner) Warn(arg0 string) {
  type MockWarnerMockRecorder (line 20) | type MockWarnerMockRecorder struct
    method Warn (line 43) | func (mr *MockWarnerMockRecorder) Warn(arg0 interface{}) *gomock.Call {
  function NewMockWarner (line 25) | func NewMockWarner(ctrl *gomock.Controller) *MockWarner {

FILE: internal/configuration/settings/nordvpn_retro.go
  function nordvpnRetroRegion (line 6) | func nordvpnRetroRegion(selection ServerSelection, validRegions, validCo...
  function stringSliceToMap (line 37) | func stringSliceToMap(slice []string) (m map[string]struct{}) {

FILE: internal/configuration/settings/openvpn.go
  type OpenVPN (line 20) | type OpenVPN struct
    method validate (line 91) | func (o OpenVPN) validate(vpnProvider string) (err error) {
    method copy (line 247) | func (o *OpenVPN) copy() (copied OpenVPN) {
    method overrideWith (line 271) | func (o *OpenVPN) overrideWith(other OpenVPN) {
    method setDefaults (line 290) | func (o *OpenVPN) setDefaults(vpnProvider string) {
    method String (line 317) | func (o OpenVPN) String() string {
    method toLinesNode (line 321) | func (o OpenVPN) toLinesNode() (node *gotree.Node) {
    method WithDefaults (line 377) | func (o OpenVPN) WithDefaults(provider string) OpenVPN {
    method read (line 382) | func (o *OpenVPN) read(r *reader.Reader) (err error) {
  function validateOpenVPNConfigFilepath (line 157) | func validateOpenVPNConfigFilepath(isCustom bool,
  function validateOpenVPNClientCertificate (line 182) | func validateOpenVPNClientCertificate(vpnProvider,
  function validateOpenVPNClientKey (line 207) | func validateOpenVPNClientKey(vpnProvider, clientKey string) (err error) {
  function validateOpenVPNEncryptedKey (line 229) | func validateOpenVPNEncryptedKey(vpnProvider,
  function readOpenVPNProcessUser (line 422) | func readOpenVPNProcessUser(r *reader.Reader) (processUser string, err e...

FILE: internal/configuration/settings/openvpn_test.go
  function Test_ivpnAccountID (line 9) | func Test_ivpnAccountID(t *testing.T) {

FILE: internal/configuration/settings/openvpnselection.go
  type OpenVPNSelection (line 18) | type OpenVPNSelection struct
    method validate (line 44) | func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
    method copy (line 147) | func (o *OpenVPNSelection) copy() (copied OpenVPNSelection) {
    method overrideWith (line 157) | func (o *OpenVPNSelection) overrideWith(other OpenVPNSelection) {
    method setDefaults (line 165) | func (o *OpenVPNSelection) setDefaults(vpnProvider string) {
    method String (line 178) | func (o OpenVPNSelection) String() string {
    method toLinesNode (line 182) | func (o OpenVPNSelection) toLinesNode() (node *gotree.Node) {
    method read (line 205) | func (o *OpenVPNSelection) read(r *reader.Reader) (err error) {

FILE: internal/configuration/settings/pmtud.go
  type PMTUD (line 14) | type PMTUD struct
    method validate (line 33) | func (p PMTUD) validate() (err error) {
    method copy (line 47) | func (p *PMTUD) copy() (copied PMTUD) {
    method overrideWith (line 54) | func (p *PMTUD) overrideWith(other PMTUD) {
    method setDefaults (line 59) | func (p *PMTUD) setDefaults() {
    method String (line 80) | func (p PMTUD) String() string {
    method toLinesNode (line 84) | func (p PMTUD) toLinesNode() (node *gotree.Node) {
    method read (line 99) | func (p *PMTUD) read(r *reader.Reader) (err error) {

FILE: internal/configuration/settings/portforward.go
  type PortForwarding (line 15) | type PortForwarding struct
    method Validate (line 50) | func (p PortForwarding) Validate(vpnProvider string) (err error) {
    method Copy (line 90) | func (p *PortForwarding) Copy() (copied PortForwarding) {
    method OverrideWith (line 103) | func (p *PortForwarding) OverrideWith(other PortForwarding) {
    method setDefaults (line 114) | func (p *PortForwarding) setDefaults() {
    method String (line 123) | func (p PortForwarding) String() string {
    method toLinesNode (line 127) | func (p PortForwarding) toLinesNode() (node *gotree.Node) {
    method read (line 168) | func (p *PortForwarding) read(r *reader.Reader) (err error) {

FILE: internal/configuration/settings/portforward_test.go
  function Test_PortForwarding_String (line 9) | func Test_PortForwarding_String(t *testing.T) {

FILE: internal/configuration/settings/provider.go
  type Provider (line 18) | type Provider struct
    method validate (line 30) | func (p *Provider) validate(vpnType string, filterChoicesGetter Filter...
    method copy (line 74) | func (p *Provider) copy() (copied Provider) {
    method overrideWith (line 82) | func (p *Provider) overrideWith(other Provider) {
    method setDefaults (line 88) | func (p *Provider) setDefaults() {
    method String (line 94) | func (p Provider) String() string {
    method toLinesNode (line 98) | func (p Provider) toLinesNode() (node *gotree.Node) {
    method read (line 106) | func (p *Provider) read(r *reader.Reader, vpnType string) (err error) {
  function readVPNServiceProvider (line 122) | func readVPNServiceProvider(r *reader.Reader, vpnType string) (vpnProvid...

FILE: internal/configuration/settings/publicip.go
  type PublicIP (line 14) | type PublicIP struct
    method UpdateWith (line 43) | func (p PublicIP) UpdateWith(partialUpdate PublicIP) (updatedSettings ...
    method validate (line 53) | func (p PublicIP) validate() (err error) {
    method copy (line 71) | func (p *PublicIP) copy() (copied PublicIP) {
    method overrideWith (line 79) | func (p *PublicIP) overrideWith(other PublicIP) {
    method setDefaults (line 85) | func (p *PublicIP) setDefaults() {
    method String (line 96) | func (p PublicIP) String() string {
    method toLinesNode (line 100) | func (p PublicIP) toLinesNode() (node *gotree.Node) {
    method read (line 130) | func (p *PublicIP) read(r *reader.Reader, warner Warner) (err error) {
  type PublicIPAPI (line 31) | type PublicIPAPI struct
  function readPublicIPEnabled (line 156) | func readPublicIPEnabled(r *reader.Reader, warner Warner) (

FILE: internal/configuration/settings/publicip_test.go
  function Test_PublicIP_read (line 11) | func Test_PublicIP_read(t *testing.T) {

FILE: internal/configuration/settings/server.go
  type ControlServer (line 18) | type ControlServer struct
    method validate (line 35) | func (c ControlServer) validate() (err error) {
    method copy (line 71) | func (c *ControlServer) copy() (copied ControlServer) {
    method overrideWith (line 83) | func (c *ControlServer) overrideWith(other ControlServer) {
    method setDefaults (line 90) | func (c *ControlServer) setDefaults() {
    method String (line 104) | func (c ControlServer) String() string {
    method toLinesNode (line 108) | func (c ControlServer) toLinesNode() (node *gotree.Node) {
    method read (line 121) | func (c *ControlServer) read(r *reader.Reader) (err error) {

FILE: internal/configuration/settings/serverselection.go
  type ServerSelection (line 19) | type ServerSelection struct
    method validate (line 86) | func (ss *ServerSelection) validate(vpnServiceProvider string,
    method copy (line 292) | func (ss *ServerSelection) copy() (copied ServerSelection) {
    method overrideWith (line 316) | func (ss *ServerSelection) overrideWith(other ServerSelection) {
    method setDefaults (line 338) | func (ss *ServerSelection) setDefaults(vpnProvider string, portForward...
    method String (line 354) | func (ss ServerSelection) String() string {
    method toLinesNode (line 358) | func (ss ServerSelection) toLinesNode() (node *gotree.Node) {
    method WithDefaults (line 440) | func (ss ServerSelection) WithDefaults(provider string) ServerSelection {
    method read (line 446) | func (ss *ServerSelection) read(r *reader.Reader,
  function getLocationFilterChoices (line 138) | func getLocationFilterChoices(vpnServiceProvider string,
  function validateServerFilters (line 162) | func validateServerFilters(settings ServerSelection, filterChoices model...
  function atLeastOneIsOneOfCaseInsensitive (line 217) | func atLeastOneIsOneOfCaseInsensitive(values, choices []string,
  function validateSubscriptionTierFilters (line 254) | func validateSubscriptionTierFilters(settings ServerSelection, vpnServic...
  function validateFeatureFilters (line 269) | func validateFeatureFilters(settings ServerSelection, vpnServiceProvider...

FILE: internal/configuration/settings/settings.go
  type Settings (line 15) | type Settings struct
    method Validate (line 40) | func (s *Settings) Validate(filterChoicesGetter FilterChoicesGetter, i...
    method copy (line 73) | func (s *Settings) copy() (copied Settings) {
    method OverrideWith (line 93) | func (s *Settings) OverrideWith(other Settings,
    method SetDefaults (line 120) | func (s *Settings) SetDefaults() {
    method String (line 138) | func (s Settings) String() string {
    method toLinesNode (line 142) | func (s Settings) toLinesNode() (node *gotree.Node) {
    method Warnings (line 164) | func (s Settings) Warnings() (warnings []string) {
    method Read (line 192) | func (s *Settings) Read(r *reader.Reader, warner Warner) (err error) {
  type FilterChoicesGetter (line 33) | type FilterChoicesGetter interface

FILE: internal/configuration/settings/settings_test.go
  function Test_Settings_String (line 9) | func Test_Settings_String(t *testing.T) {

FILE: internal/configuration/settings/shadowsocks.go
  type Shadowsocks (line 13) | type Shadowsocks struct
    method validate (line 21) | func (s Shadowsocks) validate() (err error) {
    method copy (line 25) | func (s *Shadowsocks) copy() (copied Shadowsocks) {
    method overrideWith (line 35) | func (s *Shadowsocks) overrideWith(other Shadowsocks) {
    method setDefaults (line 40) | func (s *Shadowsocks) setDefaults() {
    method String (line 45) | func (s Shadowsocks) String() string {
    method toLinesNode (line 49) | func (s Shadowsocks) toLinesNode() (node *gotree.Node) {
    method read (line 66) | func (s *Shadowsocks) read(r *reader.Reader) (err error) {
  function readShadowsocksAddress (line 88) | func readShadowsocksAddress(r *reader.Reader) (address *string, err erro...

FILE: internal/configuration/settings/storage.go
  type Storage (line 13) | type Storage struct
    method validate (line 18) | func (s Storage) validate() (err error) {
    method copy (line 28) | func (s *Storage) copy() (copied Storage) {
    method overrideWith (line 34) | func (s *Storage) overrideWith(other Storage) {
    method setDefaults (line 38) | func (s *Storage) setDefaults() {
    method String (line 43) | func (s Storage) String() string {
    method toLinesNode (line 47) | func (s Storage) toLinesNode() (node *gotree.Node) {
    method read (line 56) | func (s *Storage) read(r *reader.Reader) (err error) {

FILE: internal/configuration/settings/surfshark_retro.go
  function surfsharkRetroRegion (line 9) | func surfsharkRetroRegion(selection ServerSelection) (
  function dedupSlice (line 41) | func dedupSlice(slice []string) (deduped []string) {

FILE: internal/configuration/settings/system.go
  type System (line 10) | type System struct
    method validate (line 17) | func (s System) validate() (err error) {
    method copy (line 21) | func (s *System) copy() (copied System) {
    method overrideWith (line 29) | func (s *System) overrideWith(other System) {
    method setDefaults (line 35) | func (s *System) setDefaults() {
    method String (line 41) | func (s System) String() string {
    method toLinesNode (line 45) | func (s System) toLinesNode() (node *gotree.Node) {
    method read (line 58) | func (s *System) read(r *reader.Reader) (err error) {

FILE: internal/configuration/settings/updater.go
  type Updater (line 18) | type Updater struct
    method Validate (line 37) | func (u Updater) Validate() (err error) {
    method copy (line 72) | func (u *Updater) copy() (copied Updater) {
    method overrideWith (line 85) | func (u *Updater) overrideWith(other Updater) {
    method SetDefaults (line 93) | func (u *Updater) SetDefaults(vpnProvider string) {
    method String (line 110) | func (u Updater) String() string {
    method toLinesNode (line 114) | func (u Updater) toLinesNode() (node *gotree.Node) {
    method read (line 131) | func (u *Updater) read(r *reader.Reader) (err error) {

FILE: internal/configuration/settings/validation/servers.go
  function sortedInsert (line 9) | func sortedInsert(ss []string, s string) []string {
  function ExtractCountries (line 17) | func ExtractCountries(servers []models.Server) (values []string) {
  function ExtractCategories (line 36) | func ExtractCategories(servers []models.Server) (values []string) {
  function ExtractRegions (line 58) | func ExtractRegions(servers []models.Server) (values []string) {
  function ExtractCities (line 77) | func ExtractCities(servers []models.Server) (values []string) {
  function ExtractISPs (line 96) | func ExtractISPs(servers []models.Server) (values []string) {
  function ExtractServerNames (line 115) | func ExtractServerNames(servers []models.Server) (values []string) {
  function ExtractHostnames (line 134) | func ExtractHostnames(servers []models.Server) (values []string) {

FILE: internal/configuration/settings/validation/surfshark.go
  function SurfsharkRetroLocChoices (line 8) | func SurfsharkRetroLocChoices() (choices []string) {

FILE: internal/configuration/settings/version.go
  type Version (line 11) | type Version struct
    method validate (line 17) | func (v Version) validate() (err error) {
    method copy (line 21) | func (v *Version) copy() (copied Version) {
    method overrideWith (line 30) | func (v *Version) overrideWith(other Version) {
    method setDefaults (line 34) | func (v *Version) setDefaults() {
    method String (line 38) | func (v Version) String() string {
    method toLinesNode (line 42) | func (v Version) toLinesNode() (node *gotree.Node) {
    method read (line 50) | func (v *Version) read(r *reader.Reader) (err error) {

FILE: internal/configuration/settings/vpn.go
  type VPN (line 13) | type VPN struct
    method Validate (line 36) | func (v *VPN) Validate(filterChoicesGetter FilterChoicesGetter, ipv6Su...
    method Copy (line 75) | func (v *VPN) Copy() (copied VPN) {
    method OverrideWith (line 88) | func (v *VPN) OverrideWith(other VPN) {
    method setDefaults (line 99) | func (v *VPN) setDefaults() {
    method String (line 110) | func (v VPN) String() string {
    method toLinesNode (line 114) | func (v VPN) toLinesNode() (node *gotree.Node) {
    method read (line 139) | func (v *VPN) read(r *reader.Reader) (err error) {

FILE: internal/configuration/settings/wireguard.go
  type Wireguard (line 19) | type Wireguard struct
    method validate (line 54) | func (w Wireguard) validate(vpnProvider string, ipv6Supported, amnezia...
    method copy (line 133) | func (w *Wireguard) copy() (copied Wireguard) {
    method overrideWith (line 146) | func (w *Wireguard) overrideWith(other Wireguard) {
    method setDefaults (line 158) | func (w *Wireguard) setDefaults(vpnProvider string) {
    method String (line 182) | func (w Wireguard) String() string {
    method toLinesNode (line 186) | func (w Wireguard) toLinesNode() (node *gotree.Node) {
    method read (line 227) | func (w *Wireguard) read(r *reader.Reader, amneziaWG bool) (err error) {

FILE: internal/configuration/settings/wireguardselection.go
  type WireguardSelection (line 15) | type WireguardSelection struct
    method validate (line 38) | func (w WireguardSelection) validate(vpnProvider string) (err error) {
    method copy (line 114) | func (w *WireguardSelection) copy() (copied WireguardSelection) {
    method overrideWith (line 122) | func (w *WireguardSelection) overrideWith(other WireguardSelection) {
    method setDefaults (line 128) | func (w *WireguardSelection) setDefaults() {
    method String (line 133) | func (w WireguardSelection) String() string {
    method toLinesNode (line 137) | func (w WireguardSelection) toLinesNode() (node *gotree.Node) {
    method read (line 155) | func (w *WireguardSelection) read(r *reader.Reader) (err error) {

FILE: internal/configuration/sources/files/amneziawg.go
  method lazyLoadAmneziawgConf (line 12) | func (s *Source) lazyLoadAmneziawgConf() AmneziawgConfig {
  type AmneziawgConfig (line 26) | type AmneziawgConfig struct
  function ParseAmneziawgConf (line 46) | func ParseAmneziawgConf(path string) (config AmneziawgConfig, err error) {

FILE: internal/configuration/sources/files/amneziawg_test.go
  function Test_Source_ParseAmneziawgConf (line 13) | func Test_Source_ParseAmneziawgConf(t *testing.T) {

FILE: internal/configuration/sources/files/helpers.go
  function ReadFromFile (line 14) | func ReadFromFile(filepath string) (content string, isSet bool, err erro...
  function ReadPEMFile (line 39) | func ReadPEMFile(filepath string) (base64Str string, isSet bool, err err...

FILE: internal/configuration/sources/files/interfaces.go
  type Warner (line 3) | type Warner interface

FILE: internal/configuration/sources/files/reader.go
  type Source (line 9) | type Source struct
    method String (line 37) | func (s *Source) String() string { return "files" }
    method Get (line 39) | func (s *Source) Get(key string) (value string, isSet bool) {
    method getAmneziawgKey (line 88) | func (s *Source) getAmneziawgKey(key string) (value string, isSet, mat...
    method KeyTransform (line 140) | func (s *Source) KeyTransform(key string) string {
  function New (line 21) | func New(warner Warner) (source *Source) {
  function strPtrToStringIsSet (line 155) | func strPtrToStringIsSet(ptr *string) (s string, isSet bool) {

FILE: internal/configuration/sources/files/wireguard.go
  method lazyLoadWireguardConf (line 14) | func (s *Source) lazyLoadWireguardConf() WireguardConfig {
  type WireguardConfig (line 28) | type WireguardConfig struct
  function ParseWireguardConf (line 39) | func ParseWireguardConf(path string) (config WireguardConfig, err error) {
  function parseWireguardInterfaceSection (line 68) | func parseWireguardInterfaceSection(interfaceSection *ini.Section) (
  function parseWireguardPeerSection (line 78) | func parseWireguardPeerSection(peerSection *ini.Section) (
  function getINIKeyFromSection (line 99) | func getINIKeyFromSection(section *ini.Section, key string) (value *stri...

FILE: internal/configuration/sources/files/wireguard_test.go
  function ptrTo (line 14) | func ptrTo[T any](value T) *T { return &value }
  function Test_Source_ParseWireguardConf (line 16) | func Test_Source_ParseWireguardConf(t *testing.T) {
  function Test_parseWireguardInterfaceSection (line 96) | func Test_parseWireguardInterfaceSection(t *testing.T) {
  function Test_parseWireguardPeerSection (line 141) | func Test_parseWireguardPeerSection(t *testing.T) {

FILE: internal/configuration/sources/secrets/amneziawg.go
  method lazyLoadAmneziawgConf (line 10) | func (s *Source) lazyLoadAmneziawgConf() files.AmneziawgConfig {

FILE: internal/configuration/sources/secrets/helpers.go
  function strPtrToStringIsSet (line 3) | func strPtrToStringIsSet(ptr *string) (s string, isSet bool) {

FILE: internal/configuration/sources/secrets/interfaces.go
  type Warner (line 3) | type Warner interface

FILE: internal/configuration/sources/secrets/reader.go
  type Source (line 11) | type Source struct
    method String (line 40) | func (s *Source) String() string { return "secret files" }
    method Get (line 42) | func (s *Source) Get(key string) (value string, isSet bool) {
    method KeyTransform (line 100) | func (s *Source) KeyTransform(key string) string {
    method getAmneziaWg (line 115) | func (s *Source) getAmneziaWg(key string) (value string, isSet, matche...
  function New (line 23) | func New(warner Warner) (source *Source) {

FILE: internal/configuration/sources/secrets/reader_test.go
  function Test_Source_Get (line 13) | func Test_Source_Get(t *testing.T) {

FILE: internal/configuration/sources/secrets/wireguard.go
  method lazyLoadWireguardConf (line 10) | func (s *Source) lazyLoadWireguardConf() files.WireguardConfig {

FILE: internal/constants/colors.go
  function ColorOpenvpn (line 5) | func ColorOpenvpn() *color.Color {

FILE: internal/constants/countries.go
  function CountryCodes (line 3) | func CountryCodes() map[string]string {

FILE: internal/constants/openvpn/auth.go
  constant SHA1 (line 4) | SHA1   = "sha1"
  constant SHA256 (line 5) | SHA256 = "sha256"
  constant SHA512 (line 6) | SHA512 = "sha512"

FILE: internal/constants/openvpn/ciphers.go
  constant AES128cbc (line 4) | AES128cbc        = "aes-128-cbc"
  constant AES192cbc (line 5) | AES192cbc        = "aes-192-cbc"
  constant AES256cbc (line 6) | AES256cbc        = "aes-256-cbc"
  constant AES128gcm (line 7) | AES128gcm        = "aes-128-gcm"
  constant AES192gcm (line 8) | AES192gcm        = "aes-192-gcm"
  constant AES256gcm (line 9) | AES256gcm        = "aes-256-gcm"
  constant Chacha20Poly1305 (line 10) | Chacha20Poly1305 = "chacha20-poly1305"

FILE: internal/constants/openvpn/paths.go
  constant AuthConf (line 5) | AuthConf = "/etc/openvpn/auth.conf"
  constant AskPassPath (line 8) | AskPassPath = "/etc/openvpn/askpass"

FILE: internal/constants/openvpn/versions.go
  constant Openvpn25 (line 4) | Openvpn25 = "2.5"
  constant Openvpn26 (line 5) | Openvpn26 = "2.6"

FILE: internal/constants/paths.go
  constant ServersData (line 5) | ServersData = "/gluetun/servers.json"

FILE: internal/constants/protocol.go
  constant TCP (line 5) | TCP string = "tcp"
  constant UDP (line 7) | UDP string = "udp"

FILE: internal/constants/providers/providers.go
  constant Airvpn (line 6) | Airvpn                = "airvpn"
  constant Custom (line 7) | Custom                = "custom"
  constant Cyberghost (line 8) | Cyberghost            = "cyberghost"
  constant Example (line 9) | Example               = "example"
  constant Expressvpn (line 10) | Expressvpn            = "expressvpn"
  constant Fastestvpn (line 11) | Fastestvpn            = "fastestvpn"
  constant Giganews (line 12) | Giganews              = "giganews"
  constant HideMyAss (line 13) | HideMyAss             = "hidemyass"
  constant Ipvanish (line 14) | Ipvanish              = "ipvanish"
  constant Ivpn (line 15) | Ivpn                  = "ivpn"
  constant Mullvad (line 16) | Mullvad               = "mullvad"
  constant Nordvpn (line 17) | Nordvpn               = "nordvpn"
  constant Perfectprivacy (line 18) | Perfectprivacy        = "perfect privacy"
  constant Privado (line 19) | Privado               = "privado"
  constant PrivateInternetAccess (line 20) | PrivateInternetAccess = "private internet access"
  constant Privatevpn (line 21) | Privatevpn            = "privatevpn"
  constant Protonvpn (line 22) | Protonvpn             = "protonvpn"
  constant Purevpn (line 23) | Purevpn               = "purevpn"
  constant SlickVPN (line 24) | SlickVPN              = "slickvpn"
  constant Surfshark (line 25) | Surfshark             = "surfshark"
  constant Torguard (line 26) | Torguard              = "torguard"
  constant VPNSecure (line 27) | VPNSecure             = "vpnsecure"
  constant VPNUnlimited (line 28) | VPNUnlimited          = "vpn unlimited"
  constant Vyprvpn (line 29) | Vyprvpn               = "vyprvpn"
  constant Windscribe (line 30) | Windscribe            = "windscribe"
  function All (line 34) | func All() []string {
  function AllWithCustom (line 62) | func AllWithCustom() []string {

FILE: internal/constants/providers/providers_test.go
  function Test_All (line 9) | func Test_All(t *testing.T) {
  function Test_AllWithCustom (line 17) | func Test_AllWithCustom(t *testing.T) {

FILE: internal/constants/status.go
  constant Starting (line 8) | Starting  models.LoopStatus = "starting"
  constant Running (line 9) | Running   models.LoopStatus = "running"
  constant Stopping (line 10) | Stopping  models.LoopStatus = "stopping"
  constant Stopped (line 11) | Stopped   models.LoopStatus = "stopped"
  constant Crashed (line 12) | Crashed   models.LoopStatus = "crashed"
  constant Completed (line 13) | Completed models.LoopStatus = "completed"

FILE: internal/constants/vpn/protocol.go
  constant AmneziaWg (line 4) | AmneziaWg = "amneziawg"
  constant OpenVPN (line 5) | OpenVPN   = "openvpn"
  constant Wireguard (line 6) | Wireguard = "wireguard"

FILE: internal/dns/leak.go
  function leakCheck (line 15) | func leakCheck(ctx context.Context, client *http.Client) (report string,...
  function generateRandomString (line 57) | func generateRandomString(length uint) string {
  function triggerDNSQuery (line 68) | func triggerDNSQuery(ctx context.Context, client *http.Client, session s...
  function formatPercentages (line 102) | func formatPercentages(data map[string]uint) string {

FILE: internal/dns/leak_test.go
  function Test_leakCheck (line 12) | func Test_leakCheck(t *testing.T) {

FILE: internal/dns/logger.go
  type Logger (line 3) | type Logger interface

FILE: internal/dns/loop.go
  type Loop (line 19) | type Loop struct
    method logAndWait (line 82) | func (l *Loop) logAndWait(ctx context.Context, err error) {
    method signalOrSetStatus (line 98) | func (l *Loop) signalOrSetStatus(status models.LoopStatus) {
  constant defaultBackoffTime (line 40) | defaultBackoffTime = 10 * time.Second
  function NewLoop (line 42) | func NewLoop(settings settings.DNS,
  type filterLogger (line 110) | type filterLogger struct
    method Log (line 114) | func (l *filterLogger) Log(msg string) {
  function buildFilterLogger (line 118) | func buildFilterLogger(logger Logger) *filterLogger {

FILE: internal/dns/plaintext.go
  method useUnencryptedDNS (line 10) | func (l *Loop) useUnencryptedDNS(fallback bool) {

FILE: internal/dns/run.go
  method Run (line 11) | func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
  method runWait (line 76) | func (l *Loop) runWait(ctx context.Context, runError <-chan error) (exit...
  method stopServer (line 104) | func (l *Loop) stopServer() {

FILE: internal/dns/settings.go
  method GetSettings (line 22) | func (l *Loop) GetSettings() (settings settings.DNS) { return l.state.Ge...
  method SetSettings (line 24) | func (l *Loop) SetSettings(ctx context.Context, settings settings.DNS) (
  function buildServerSettings (line 30) | func buildServerSettings(userSettings settings.DNS,
  function buildProviders (line 120) | func buildProviders(userSettings settings.DNS, localSubnets []netip.Prefix,

FILE: internal/dns/setup.go
  method setupServer (line 14) | func (l *Loop) setupServer(ctx context.Context, settings settings.DNS) (...

FILE: internal/dns/state/settings.go
  method GetSettings (line 11) | func (s *State) GetSettings() (settings settings.DNS) {
  method SetSettings (line 17) | func (s *State) SetSettings(ctx context.Context, settings settings.DNS) (

FILE: internal/dns/state/state.go
  function New (line 11) | func New(statusApplier StatusApplier,
  type State (line 22) | type State struct
  type StatusApplier (line 31) | type StatusApplier interface

FILE: internal/dns/status.go
  method GetStatus (line 9) | func (l *Loop) GetStatus() (status models.LoopStatus) {
  method ApplyStatus (line 13) | func (l *Loop) ApplyStatus(ctx context.Context, status models.LoopStatus) (

FILE: internal/dns/ticker.go
  method RunRestartTicker (line 10) | func (l *Loop) RunRestartTicker(ctx context.Context, done chan<- struct{...

FILE: internal/dns/update.go
  method updateFiles (line 12) | func (l *Loop) updateFiles(ctx context.Context, settings settings.DNS) (...

FILE: internal/firewall/enable.go
  method SetEnabled (line 10) | func (c *Config) SetEnabled(ctx context.Context, enabled bool) (err erro...
  method enable (line 42) | func (c *Config) enable(ctx context.Context) (err error) {
  method allowVPNIP (line 127) | func (c *Config) allowVPNIP(ctx context.Context) (err error) {
  method allowOutboundSubnets (line 149) | func (c *Config) allowOutboundSubnets(ctx context.Context) (err error) {
  method allowInputPorts (line 176) | func (c *Config) allowInputPorts(ctx context.Context) (err error) {
  method redirectPorts (line 190) | func (c *Config) redirectPorts(ctx context.Context) (err error) {

FILE: internal/firewall/firewall.go
  type Config (line 14) | type Config struct
  function NewConfig (line 37) | func NewConfig(ctx context.Context, logger, iptablesLogger Logger,

FILE: internal/firewall/interfaces.go
  type CmdRunner (line 11) | type CmdRunner interface
  type Logger (line 15) | type Logger interface
  type firewallImpl (line 22) | type firewallImpl interface

FILE: internal/firewall/iptables/atomic.go
  method SaveAndRestore (line 12) | func (c *Config) SaveAndRestore(ctx context.Context) (restore func(conte...
  method saveAndRestore (line 24) | func (c *Config) saveAndRestore(ctx context.Context) (restore func(conte...
  method saveAndRestoreIPv4 (line 45) | func (c *Config) saveAndRestoreIPv4(ctx context.Context) (restore func(c...
  method saveAndRestoreIPv6 (line 65) | func (c *Config) saveAndRestoreIPv6(ctx context.Context) (restore func(c...

FILE: internal/firewall/iptables/cmd_matcher_test.go
  type cmdMatcher (line 13) | type cmdMatcher struct
    method Matches (line 19) | func (cm *cmdMatcher) Matches(x interface{}) bool {
    method String (line 47) | func (cm *cmdMatcher) String() string {
  function newCmdMatcher (line 51) | func newCmdMatcher(path string, argsRegex ...string) *cmdMatcher {

FILE: internal/firewall/iptables/delete.go
  function isDeleteMatchInstruction (line 15) | func isDeleteMatchInstruction(instruction string) bool {
  function deleteIPTablesRule (line 35) | func deleteIPTablesRule(ctx context.Context, iptablesBinary, instruction...
  function findLineNumber (line 71) | func findLineNumber(ctx context.Context, iptablesBinary string,

FILE: internal/firewall/iptables/delete_test.go
  function Test_isDeleteMatchInstruction (line 12) | func Test_isDeleteMatchInstruction(t *testing.T) {
  function newCmdMatcherListRules (line 51) | func newCmdMatcherListRules(iptablesBinary, table, chain string) *cmdMat...
  function Test_deleteIPTablesRule (line 56) | func Test_deleteIPTablesRule(t *testing.T) {

FILE: internal/firewall/iptables/firewall.go
  type Config (line 8) | type Config struct
  function New (line 19) | func New(ctx context.Context, runner CmdRunner, logger Logger) (*Config,...

FILE: internal/firewall/iptables/interfaces.go
  type CmdRunner (line 5) | type CmdRunner interface
  type Logger (line 9) | type Logger interface

FILE: internal/firewall/iptables/ip6tables.go
  function findIP6tablesSupported (line 14) | func findIP6tablesSupported(ctx context.Context, runner CmdRunner) (
  method runIP6tablesInstructions (line 26) | func (c *Config) runIP6tablesInstructions(ctx context.Context, instructi...
  method runIP6tablesInstructionsNoSave (line 41) | func (c *Config) runIP6tablesInstructionsNoSave(ctx context.Context, ins...
  method runIP6tablesInstruction (line 50) | func (c *Config) runIP6tablesInstruction(ctx context.Context, instructio...
  method runIP6tablesInstructionNoSave (line 65) | func (c *Config) runIP6tablesInstructionNoSave(ctx context.Context, inst...
  method SetIPv6AllPolicies (line 87) | func (c *Config) SetIPv6AllPolicies(ctx context.Context, policy string) ...

FILE: internal/firewall/iptables/iptables.go
  function appendOrDelete (line 22) | func appendOrDelete(remove bool) string {
  method Version (line 30) | func (c *Config) Version(ctx context.Context) (string, error) {
  method runIptablesInstructions (line 44) | func (c *Config) runIptablesInstructions(ctx context.Context, instructio...
  method runIptablesInstructionsNoSave (line 60) | func (c *Config) runIptablesInstructionsNoSave(ctx context.Context, inst...
  method runIptablesInstruction (line 69) | func (c *Config) runIptablesInstruction(ctx context.Context, instruction...
  method runIptablesInstructionNoSave (line 85) | func (c *Config) runIptablesInstructionNoSave(ctx context.Context, instr...
  method SetIPv4AllPolicies (line 101) | func (c *Config) SetIPv4AllPolicies(ctx context.Context, policy string) ...
  method AcceptInputThroughInterface (line 114) | func (c *Config) AcceptInputThroughInterface(ctx context.Context, intf s...
  method AcceptInputToSubnet (line 119) | func (c *Config) AcceptInputToSubnet(ctx context.Context, intf string, d...
  method AcceptOutputThroughInterface (line 137) | func (c *Config) AcceptOutputThroughInterface(ctx context.Context, intf ...
  method AcceptEstablishedRelatedTraffic (line 143) | func (c *Config) AcceptEstablishedRelatedTraffic(ctx context.Context) er...
  method AcceptOutputTrafficToVPN (line 150) | func (c *Config) AcceptOutputTrafficToVPN(ctx context.Context,
  method AcceptOutputFromIPToSubnet (line 169) | func (c *Config) AcceptOutputFromIPToSubnet(ctx context.Context,
  method AcceptIpv6MulticastOutput (line 194) | func (c *Config) AcceptIpv6MulticastOutput(ctx context.Context, intf str...
  method AcceptInputToPort (line 207) | func (c *Config) AcceptInputToPort(ctx context.Context, intf string, por...
  method RedirectPort (line 223) | func (c *Config) RedirectPort(ctx context.Context, intf string,
  method RunUserPostRules (line 282) | func (c *Config) RunUserPostRules(ctx context.Context, filepath string) ...

FILE: internal/firewall/iptables/iptablesmix.go
  method runMixedIptablesInstructions (line 7) | func (c *Config) runMixedIptablesInstructions(ctx context.Context, instr...
  method runMixedIptablesInstruction (line 27) | func (c *Config) runMixedIptablesInstruction(ctx context.Context, instru...
  method runMixedIptablesInstructionNoSave (line 44) | func (c *Config) runMixedIptablesInstructionNoSave(ctx context.Context, ...

FILE: internal/firewall/iptables/list.go
  type chain (line 12) | type chain struct
  type chainRule (line 20) | type chainRule struct
  type mark (line 38) | type mark struct
  function parseChain (line 45) | func parseChain(iptablesOutput string) (c chain, err error) {
  function parseChainGeneralDataLine (line 104) | func parseChainGeneralDataLine(line string) (base chain, err error) {
  function parseChainRuleLine (line 157) | func parseChainRuleLine(line string) (rule chainRule, err error) {
  function parseChainRuleField (line 187) | func parseChainRuleField(fieldIndex int, field string, rule *chainRule) ...
  function parseChainRuleOptionalFields (line 251) | func parseChainRuleOptionalFields(optionalFields []string, rule *chainRu...
  function parseUDPOptional (line 306) | func parseUDPOptional(optionalFields []string, rule *chainRule) (consume...
  function parseTCPOptional (line 334) | func parseTCPOptional(optionalFields []string, rule *chainRule) (consume...
  function parseDestinationPort (line 366) | func parseDestinationPort(value string) (port uint16, err error) {
  function parseSourcePort (line 371) | func parseSourcePort(value string) (port uint16, err error) {
  function parseTCPFlags (line 378) | func parseTCPFlags(value string) (tcpFlags, error) {
  function parsePortsCSV (line 409) | func parsePortsCSV(s string) (ports []uint16, err error) {
  function parseMark (line 427) | func parseMark(optionalFields []string) (m mark, consumed int, err error) {
  function parseLineNumber (line 453) | func parseLineNumber(s string) (n uint16, err error) {
  function checkTarget (line 466) | func checkTarget(target string) (err error) {
  function parseProtocol (line 476) | func parseProtocol(s string) (protocol string, err error) {
  function parseMetricSize (line 495) | func parseMetricSize(size string) (n uint64, err error) {

FILE: internal/firewall/iptables/list_test.go
  function Test_parseChain (line 10) | func Test_parseChain(t *testing.T) {

FILE: internal/firewall/iptables/mocks_test.go
  type MockCmdRunner (line 15) | type MockCmdRunner struct
    method EXPECT (line 33) | func (m *MockCmdRunner) EXPECT() *MockCmdRunnerMockRecorder {
    method Run (line 38) | func (m *MockCmdRunner) Run(arg0 *exec.Cmd) (string, error) {
  type MockCmdRunnerMockRecorder (line 21) | type MockCmdRunnerMockRecorder struct
    method Run (line 47) | func (mr *MockCmdRunnerMockRecorder) Run(arg0 interface{}) *gomock.Call {
  function NewMockCmdRunner (line 26) | func NewMockCmdRunner(ctrl *gomock.Controller) *MockCmdRunner {
  type MockLogger (line 53) | type MockLogger struct
    method EXPECT (line 71) | func (m *MockLogger) EXPECT() *MockLoggerMockRecorder {
    method Debug (line 76) | func (m *MockLogger) Debug(arg0 string) {
    method Warn (line 88) | func (m *MockLogger) Warn(arg0 string) {
  type MockLoggerMockRecorder (line 59) | type MockLoggerMockRecorder struct
    method Debug (line 82) | func (mr *MockLoggerMockRecorder) Debug(arg0 interface{}) *gomock.Call {
    method Warn (line 94) | func (mr *MockLoggerMockRecorder) Warn(arg0 interface{}) *gomock.Call {
  function NewMockLogger (line 64) | func NewMockLogger(ctrl *gomock.Controller) *MockLogger {

FILE: internal/firewall/iptables/parse.go
  type iptablesInstruction (line 12) | type iptablesInstruction struct
    method setDefaults (line 30) | func (i *iptablesInstruction) setDefaults() {
    method equalToRule (line 37) | func (i *iptablesInstruction) equalToRule(table, chain string, rule ch...
  function networkInterfacesEqual (line 74) | func networkInterfacesEqual(instruction, chainRule string) bool {
  function ipPrefixesEqual (line 78) | func ipPrefixesEqual(instruction, chainRule netip.Prefix) bool {
  function parseIptablesInstruction (line 85) | func parseIptablesInstruction(s string) (instruction iptablesInstruction...
  function parseInstructionFlag (line 104) | func parseInstructionFlag(fields []string, instruction *iptablesInstruct...
  function preCheckInstructionFields (line 181) | func preCheckInstructionFields(fields []string) (consumed int, err error) {
  function parseIPPrefix (line 202) | func parseIPPrefix(value string) (prefix netip.Prefix, err error) {
  function parsePort (line 215) | func parsePort(value string) (port uint16, err error) {
  function parseMatchModule (line 224) | func parseMatchModule(fields []string, instruction *iptablesInstruction) (
  function parseToPorts (line 252) | func parseToPorts(value string) (toPorts []uint16, err error) {

FILE: internal/firewall/iptables/parse_test.go
  function Test_parseIptablesInstruction (line 10) | func Test_parseIptablesInstruction(t *testing.T) {
  function Test_parseIPPrefix (line 85) | func Test_parseIPPrefix(t *testing.T) {

FILE: internal/firewall/iptables/support.go
  function checkIptablesSupport (line 20) | func checkIptablesSupport(ctx context.Context, runner CmdRunner,
  function testIptablesPath (line 63) | func testIptablesPath(ctx context.Context, path string,
  function isPermissionDenied (line 126) | func isPermissionDenied(errMessage string) (ok bool) {
  function extractInputPolicy (line 131) | func extractInputPolicy(line string) (policy string, ok bool) {
  function randomInterfaceName (line 153) | func randomInterfaceName() (interfaceName string) {

FILE: internal/firewall/iptables/support_test.go
  function newAppendTestRuleMatcher (line 13) | func newAppendTestRuleMatcher(path string) *cmdMatcher {
  function newDeleteTestRuleMatcher (line 19) | func newDeleteTestRuleMatcher(path string) *cmdMatcher {
  function newListInputRulesMatcher (line 25) | func newListInputRulesMatcher(path string) *cmdMatcher {
  function newSetPolicyMatcher (line 30) | func newSetPolicyMatcher(path, inputPolicy string) *cmdMatcher { //nolin...
  function Test_checkIptablesSupport (line 35) | func Test_checkIptablesSupport(t *testing.T) {
  function Test_testIptablesPath (line 130) | func Test_testIptablesPath(t *testing.T) {
  function Test_isPermissionDenied (line 268) | func Test_isPermissionDenied(t *testing.T) {
  function Test_extractInputPolicy (line 296) | func Test_extractInputPolicy(t *testing.T) {
  function Test_randomInterfaceName (line 339) | func Test_randomInterfaceName(t *testing.T) {

FILE: internal/firewall/iptables/tcp.go
  type tcpFlags (line 11) | type tcpFlags struct
  type tcpFlag (line 16) | type tcpFlag
    method String (line 29) | func (f tcpFlag) String() string {
  constant tcpFlagFIN (line 19) | tcpFlagFIN tcpFlag = 1 << iota
  constant tcpFlagSYN (line 20) | tcpFlagSYN
  constant tcpFlagRST (line 21) | tcpFlagRST
  constant tcpFlagPSH (line 22) | tcpFlagPSH
  constant tcpFlagACK (line 23) | tcpFlagACK
  constant tcpFlagURG (line 24) | tcpFlagURG
  constant tcpFlagECE (line 25) | tcpFlagECE
  constant tcpFlagCWR (line 26) | tcpFlagCWR
  function parseTCPFlag (line 54) | func parseTCPFlag(s string) (tcpFlag, error) {
  method TempDropOutputTCPRST (line 73) | func (c *Config) TempDropOutputTCPRST(ctx context.Context,

FILE: internal/firewall/logger.go
  method logIgnoredSubnetFamily (line 8) | func (c *Config) logIgnoredSubnetFamily(subnet netip.Prefix) {

FILE: internal/firewall/outboundsubnets.go
  method SetOutboundSubnets (line 12) | func (c *Config) SetOutboundSubnets(ctx context.Context, subnets []netip...
  method removeOutboundSubnets (line 38) | func (c *Config) removeOutboundSubnets(ctx context.Context, subnets []ne...
  method addOutboundSubnets (line 67) | func (c *Config) addOutboundSubnets(ctx context.Context, subnets []netip...

FILE: internal/firewall/ports.go
  method SetAllowedPort (line 9) | func (c *Config) SetAllowedPort(ctx context.Context, port uint16, intf s...
  method RemoveAllowedPort (line 48) | func (c *Config) RemoveAllowedPort(ctx context.Context, port uint16) (er...

FILE: internal/firewall/redirect.go
  method RedirectPort (line 13) | func (c *Config) RedirectPort(ctx context.Context, intf string, sourcePort,
  type portRedirection (line 72) | type portRedirection struct
  type portRedirections (line 78) | type portRedirections
    method remove (line 80) | func (p *portRedirections) remove(intf string, sourcePort uint16) {
    method check (line 93) | func (p *portRedirections) check(dryRun portRedirection) (alreadyExist...
    method append (line 117) | func (p *portRedirections) append(newRedirection portRedirection) {

FILE: internal/firewall/vpn.go
  method SetVPNConnection (line 10) | func (c *Config) SetVPNConnection(ctx context.Context,

FILE: internal/firewall/wrappers.go
  method Version (line 8) | func (c *Config) Version(ctx context.Context) (version string, err error) {
  method TempDropOutputTCPRST (line 16) | func (c *Config) TempDropOutputTCPRST(ctx context.Context,

FILE: internal/format/duration.go
  function FriendlyDuration (line 10) | func FriendlyDuration(duration time.Duration) string {

FILE: internal/format/duration_test.go
  function Test_FriendlyDuration (line 10) | func Test_FriendlyDuration(t *testing.T) {

FILE: internal/healthcheck/checker.go
  type Checker (line 18) | type Checker struct
    method SetConfig (line 55) | func (c *Checker) SetConfig(tlsDialAddrs []string, icmpTargets []netip...
    method Start (line 86) | func (c *Checker) Start(ctx context.Context) (runError <-chan error, e...
    method Stop (line 159) | func (c *Checker) Stop() error {
    method smallPeriodicCheck (line 168) | func (c *Checker) smallPeriodicCheck(ctx context.Context) error {
    method fullPeriodicCheck (line 204) | func (c *Checker) fullPeriodicCheck(ctx context.Context) error {
    method startupCheck (line 303) | func (c *Checker) startupCheck(ctx context.Context) error {
  function NewChecker (line 36) | func NewChecker(logger Logger) *Checker {
  function tcpTLSCheck (line 215) | func tcpTLSCheck(ctx context.Context, dialer *net.Dialer, targetAddress ...
  function makeAddressToDial (line 253) | func makeAddressToDial(address string) (addressToDial string, err error) {
  function withRetries (line 271) | func withRetries(ctx context.Context, tryTimeouts []time.Duration,
  constant smallCheckDNS (line 349) | smallCheckDNS  = "dns"
  constant smallCheckICMP (line 350) | smallCheckICMP = "icmp"
  function smallCheckTypeToString (line 353) | func smallCheckTypeToString(smallCheckType string) string {

FILE: internal/healthcheck/checker_test.go
  function Test_Checker_fullcheck (line 14) | func Test_Checker_fullcheck(t *testing.T) {
  function Test_makeAddressToDial (line 65) | func Test_makeAddressToDial(t *testing.T) {

FILE: internal/healthcheck/client.go
  type Client (line 14) | type Client struct
    method Check (line 24) | func (c *Client) Check(ctx context.Context, url string) error {
  function NewClient (line 18) | func NewClient(httpClient *http.Client) *Client {

FILE: internal/healthcheck/dns/dns.go
  type Client (line 17) | type Client struct
    method Check (line 46) | func (c *Client) Check(ctx context.Context) error {
  function New (line 22) | func New() *Client {
  function concatAddrPorts (line 36) | func concatAddrPorts(addrs [][]netip.AddrPort) []netip.AddrPort {

FILE: internal/healthcheck/handler.go
  type handler (line 9) | type handler struct
    method ServeHTTP (line 24) | func (h *handler) ServeHTTP(responseWriter http.ResponseWriter, reques...
    method setErr (line 36) | func (h *handler) setErr(err error) {
    method getErr (line 42) | func (h *handler) getErr() (err error) {
  function newHandler (line 17) | func newHandler(logger Logger) *handler {

FILE: internal/healthcheck/icmp/apple_ipv4.go
  type ipv4Wrapper (line 14) | type ipv4Wrapper struct
    method ReadFrom (line 22) | func (i *ipv4Wrapper) ReadFrom(p []byte) (n int, addr net.Addr, err er...
    method WriteTo (line 27) | func (i *ipv4Wrapper) WriteTo(p []byte, addr net.Addr) (n int, err err...
    method Close (line 31) | func (i *ipv4Wrapper) Close() error {
    method LocalAddr (line 35) | func (i *ipv4Wrapper) LocalAddr() net.Addr {
    method SetDeadline (line 39) | func (i *ipv4Wrapper) SetDeadline(t time.Time) error {
    method SetReadDeadline (line 43) | func (i *ipv4Wrapper) SetReadDeadline(t time.Time) error {
    method SetWriteDeadline (line 47) | func (i *ipv4Wrapper) SetWriteDeadline(t time.Time) error {
  function ipv4ToNetPacketConn (line 18) | func ipv4ToNetPacketConn(ipv4 *ipv4.PacketConn) *ipv4Wrapper {

FILE: internal/healthcheck/icmp/echo.go
  type Echoer (line 27) | type Echoer struct
    method Reset (line 54) | func (e *Echoer) Reset() {
    method Echo (line 68) | func (e *Echoer) Echo(ctx context.Context, ip netip.Addr) (err error) {
  function NewEchoer (line 36) | func NewEchoer(logger Logger) *Echoer {
  function buildMessageToSend (line 130) | func buildMessageToSend(ipVersion string, id, seq int, randomSource io.R...
  function receiveEchoReply (line 158) | func receiveEchoReply(conn net.PacketConn, id, seq int, buffer []byte, i...

FILE: internal/healthcheck/icmp/interfaces.go
  type Logger (line 3) | type Logger interface

FILE: internal/healthcheck/icmp/listen.go
  function listenICMPv4 (line 12) | func listenICMPv4(ctx context.Context) (conn net.PacketConn, err error) {
  function listenICMPv6 (line 27) | func listenICMPv6(ctx context.Context) (conn net.PacketConn, err error) {

FILE: internal/healthcheck/interfaces.go
  type Logger (line 3) | type Logger interface

FILE: internal/healthcheck/run.go
  method Run (line 10) | func (s *Server) Run(ctx context.Context, done chan<- struct{}) {

FILE: internal/healthcheck/server.go
  type Server (line 10) | type Server struct
    method SetError (line 24) | func (s *Server) SetError(err error) {
  function NewServer (line 16) | func NewServer(config settings.Health, logger Logger) *Server {
  type StatusApplier (line 28) | type StatusApplier interface

FILE: internal/httpproxy/accept.go
  method isAccepted (line 8) | func (h *handler) isAccepted(responseWriter http.ResponseWriter, request...

FILE: internal/httpproxy/auth.go
  method isAuthorized (line 10) | func (h *handler) isAuthorized(responseWriter http.ResponseWriter, reque...

FILE: internal/httpproxy/handler.go
  function newHandler (line 10) | func newHandler(ctx context.Context, wg *sync.WaitGroup, logger Logger,
  type handler (line 29) | type handler struct
    method ServeHTTP (line 38) | func (h *handler) ServeHTTP(responseWriter http.ResponseWriter, reques...
  function returnRedirect (line 69) | func returnRedirect(*http.Request, []*http.Request) error {

FILE: internal/httpproxy/handler_test.go
  function Test_returnRedirect (line 10) | func Test_returnRedirect(t *testing.T) {

FILE: internal/httpproxy/http.go
  method handleHTTP (line 11) | func (h *handler) handleHTTP(responseWriter http.ResponseWriter, request...
  function setForwardedHeaders (line 62) | func setForwardedHeaders(request *http.Request) {

FILE: internal/httpproxy/https.go
  method handleHTTPS (line 9) | func (h *handler) handleHTTPS(responseWriter http.ResponseWriter, reques...
  function transfer (line 60) | func transfer(destination io.WriteCloser, source io.ReadCloser, done cha...

FILE: internal/httpproxy/logger.go
  type Logger (line 3) | type Logger interface
  type infoErrorer (line 9) | type infoErrorer interface

FILE: internal/httpproxy/loop.go
  type Loop (line 14) | type Loop struct
    method logAndWait (line 52) | func (l *Loop) logAndWait(ctx context.Context, err error) {
  constant defaultBackoffTime (line 27) | defaultBackoffTime = 10 * time.Second
  function NewLoop (line 29) | func NewLoop(logger Logger, settings settings.HTTPProxy) *Loop {

FILE: internal/httpproxy/run.go
  method Run (line 9) | func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {

FILE: internal/httpproxy/server.go
  type Server (line 10) | type Server struct
    method Run (line 34) | func (s *Server) Run(ctx context.Context, errorCh chan<- error) {
  function New (line 19) | func New(ctx context.Context, address string, logger Logger,

FILE: internal/httpproxy/settings.go
  method GetSettings (line 9) | func (l *Loop) GetSettings() (settings settings.HTTPProxy) {
  method SetSettings (line 13) | func (l *Loop) SetSettings(ctx context.Context, settings settings.HTTPPr...

FILE: internal/httpproxy/state/settings.go
  method GetSettings (line 11) | func (s *State) GetSettings() (settings settings.HTTPProxy) {
  method SetSettings (line 17) | func (s *State) SetSettings(ctx context.Context,

FILE: internal/httpproxy/state/state.go
  function New (line 11) | func New(statusApplier StatusApplier,
  type State (line 20) | type State struct
  type StatusApplier (line 26) | type StatusApplier interface

FILE: internal/httpproxy/status.go
  method GetStatus (line 9) | func (l *Loop) GetStatus() (status models.LoopStatus) {
  method ApplyStatus (line 13) | func (l *Loop) ApplyStatus(ctx context.Context, status models.LoopStatus) (

FILE: internal/httpserver/address.go
  method GetAddress (line 4) | func (s *Server) GetAddress() (address string) {

FILE: internal/httpserver/helpers_test.go
  type testLogger (line 11) | type testLogger struct
    method Info (line 13) | func (t *testLogger) Info(string)  {}
    method Warn (line 14) | func (t *testLogger) Warn(string)  {}
    method Error (line 15) | func (t *testLogger) Error(string) {}
  type regexMatcher (line 19) | type regexMatcher struct
    method Matches (line 23) | func (r *regexMatcher) Matches(x interface{}) bool {
    method String (line 31) | func (r *regexMatcher) String() string {
  function newRegexMatcher (line 35) | func newRegexMatcher(regex string) *regexMatcher {

FILE: internal/httpserver/logger.go
  type Logger (line 5) | type Logger interface

FILE: internal/httpserver/logger_mock_test.go
  type MockLogger (line 14) | type MockLogger struct
    method EXPECT (line 32) | func (m *MockLogger) EXPECT() *MockLoggerMockRecorder {
    method Error (line 37) | func (m *MockLogger) Error(arg0 string) {
    method Info (line 49) | func (m *MockLogger) Info(arg0 string) {
    method Warn (line 61) | func (m *MockLogger) Warn(arg0 string) {
  type MockLoggerMockRecorder (line 20) | type MockLoggerMockRecorder struct
    method Error (line 43) | func (mr *MockLoggerMockRecorder) Error(arg0 interface{}) *gomock.Call {
    method Info (line 55) | func (mr *MockLoggerMockRecorder) Info(arg0 interface{}) *gomock.Call {
    method Warn (line 67) | func (mr *MockLoggerMockRecorder) Warn(arg0 interface{}) *gomock.Call {
  function NewMockLogger (line 25) | func NewMockLogger(ctrl *gomock.Controller) *MockLogger {

FILE: internal/httpserver/run.go
  method Run (line 13) | func (s *Server) Run(ctx context.Context, ready chan<- struct{}, done ch...

FILE: internal/httpserver/run_test.go
  function Test_Server_Run_success (line 13) | func Test_Server_Run_success(t *testing.T) {
  function Test_Server_Run_failure (line 48) | func Test_Server_Run_failure(t *testing.T) {

FILE: internal/httpserver/server.go
  type Server (line 11) | type Server struct
  function New (line 23) | func New(settings Settings) (s *Server, err error) {

FILE: internal/httpserver/server_test.go
  function Test_New (line 14) | func Test_New(t *testing.T) {

FILE: internal/httpserver/settings.go
  type Settings (line 15) | type Settings struct
    method SetDefaults (line 36) | func (s *Settings) SetDefaults() {
    method Copy (line 45) | func (s Settings) Copy() Settings {
    method OverrideWith (line 56) | func (s *Settings) OverrideWith(other Settings) {
    method Validate (line 75) | func (s Settings) Validate() (err error) {
    method ToLinesNode (line 112) | func (s Settings) ToLinesNode() (node *gotree.Node) {
    method String (line 121) | func (s Settings) String() string {

FILE: internal/httpserver/settings_test.go
  function Test_Settings_SetDefaults (line 12) | func Test_Settings_SetDefaults(t *testing.T) {
  function Test_Settings_Copy (line 57) | func Test_Settings_Copy(t *testing.T) {
  function Test_Settings_OverrideWith (line 99) | func Test_Settings_OverrideWith(t *testing.T) {
  function Test_Settings_Validate (line 184) | func Test_Settings_Validate(t *testing.T) {
  function Test_Settings_String (line 276) | func Test_Settings_String(t *testing.T) {

FILE: internal/loopstate/apply.go
  method ApplyStatus (line 18) | func (s *State) ApplyStatus(ctx context.Context, status models.LoopStatu...

FILE: internal/loopstate/get.go
  method GetStatus (line 6) | func (s *State) GetStatus() (status models.LoopStatus) {

FILE: internal/loopstate/lock.go
  method Lock (line 3) | func (s *State) Lock()   { s.loopMu.Lock() }
  method Unlock (line 4) | func (s *State) Unlock() { s.loopMu.Unlock() }

FILE: internal/loopstate/set.go
  method SetStatus (line 8) | func (s *State) SetStatus(status models.LoopStatus) {

FILE: internal/loopstate/state.go
  function New (line 9) | func New(status models.LoopStatus,
  type State (line 22) | type State struct

FILE: internal/mod/configgz_linux.go
  function checkProcConfig (line 26) | func checkProcConfig(moduleName string) error {
  function moduleNameToKernelFeatureGroups (line 77) | func moduleNameToKernelFeatureGroups(moduleName string) (featureGroups [...
  function allFeaturesOK (line 148) | func allFeaturesOK(featureToOK map[string]bool) bool {

FILE: internal/mod/info_linux.go
  type state (line 17) | type state
  constant unloaded (line 20) | unloaded state = iota
  constant loading (line 21) | loading
  constant loaded (line 22) | loaded
  constant builtin (line 23) | builtin
  type moduleInfo (line 26) | type moduleInfo struct
  function getModulesInfo (line 33) | func getModulesInfo(modulesPath string) (modulesInfo map[string]moduleIn...
  function getModulesPath (line 85) | func getModulesPath() (string, error) {
  function getReleaseName (line 107) | func getReleaseName() (release string, err error) {
  function getBuiltinModules (line 118) | func getBuiltinModules(modulesDirPath string, modulesInfo map[string]mod...
  function getLoadedModules (line 149) | func getLoadedModules(modulesInfo map[string]moduleInfo) (err error) {
  function findModulePath (line 186) | func findModulePath(moduleName string, modulesInfo map[string]moduleInfo...

FILE: internal/mod/load_linux.go
  function initDependencies (line 22) | func initDependencies(path string, modulesInfo map[string]moduleInfo) (e...
  function initModule (line 57) | func initModule(path string) (err error) {

FILE: internal/mod/probe_linux.go
  function Probe (line 16) | func Probe(moduleName string) error {
  function modProbe (line 40) | func modProbe(modulesPath, moduleName string) error {

FILE: internal/mod/probe_unspecified.go
  function Probe (line 5) | func Probe(moduleName string) error {

FILE: internal/models/alias.go
  type LoopStatus (line 5) | type LoopStatus
    method String (line 8) | func (ls LoopStatus) String() string {

FILE: internal/models/build.go
  type BuildInformation (line 3) | type BuildInformation struct

FILE: internal/models/connection.go
  type Connection (line 7) | type Connection struct
    method Equal (line 28) | func (c *Connection) Equal(other Connection) bool {
    method UpdateEmptyWith (line 37) | func (c *Connection) UpdateEmptyWith(ip netip.Addr, port uint16, proto...

FILE: internal/models/filters.go
  type FilterChoices (line 3) | type FilterChoices struct

FILE: internal/models/markdown.go
  function boolToMarkdown (line 12) | func boolToMarkdown(b bool) string {
  function markdownTableHeading (line 19) | func markdownTableHeading(legendFields ...string) (markdown string) {
  constant categoriesHeader (line 25) | categoriesHeader  = "Categories"
  constant cityHeader (line 26) | cityHeader        = "City"
  constant countryHeader (line 27) | countryHeader     = "Country"
  constant freeHeader (line 28) | freeHeader        = "Free"
  constant hostnameHeader (line 29) | hostnameHeader    = "Hostname"
  constant ispHeader (line 30) | ispHeader         = "ISP"
  constant multiHopHeader (line 31) | multiHopHeader    = "MultiHop"
  constant nameHeader (line 32) | nameHeader        = "Name"
  constant numberHeader (line 33) | numberHeader      = "Number"
  constant ownedHeader (line 34) | ownedHeader       = "Owned"
  constant portForwardHeader (line 35) | portForwardHeader = "Port forwarding"
  constant premiumHeader (line 36) | premiumHeader     = "Premium"
  constant regionHeader (line 37) | regionHeader      = "Region"
  constant secureHeader (line 38) | secureHeader      = "Secure"
  constant streamHeader (line 39) | streamHeader      = "Stream"
  constant tcpHeader (line 40) | tcpHeader         = "TCP"
  constant torHeader (line 41) | torHeader         = "Tor"
  constant udpHeader (line 42) | udpHeader         = "UDP"
  constant vpnHeader (line 43) | vpnHeader         = "VPN"
  method ToMarkdown (line 46) | func (s *Server) ToMarkdown(headers ...string) (markdown string) {
  method toMarkdown (line 94) | func (s *Servers) toMarkdown(vpnProvider string) (formatted string, err ...
  function getMarkdownHeaders (line 114) | func getMarkdownHeaders(vpnProvider string) (headers []string, err error) {

FILE: internal/models/markdown_test.go
  function Test_Servers_ToMarkdown (line 11) | func Test_Servers_ToMarkdown(t *testing.T) {

FILE: internal/models/publicip.go
  type PublicIP (line 7) | type PublicIP struct
    method Copy (line 19) | func (p *PublicIP) Copy() (publicIPCopy PublicIP) {

FILE: internal/models/server.go
  type Server (line 13) | type Server struct
    method HasMinimumInformation (line 50) | func (s *Server) HasMinimumInformation() (err error) {
    method Equal (line 67) | func (s *Server) Equal(other Server) (equal bool) {
    method Key (line 92) | func (s *Server) Key() (key string) {
  function ipsAreEqual (line 78) | func ipsAreEqual(a, b []netip.Addr) (equal bool) {

FILE: internal/models/server_test.go
  function Test_Server_Equal (line 10) | func Test_Server_Equal(t *testing.T) {

FILE: internal/models/servers.go
  type AllServers (line 15) | type AllServers struct
    method MarshalJSON (line 26) | func (a *AllServers) MarshalJSON() (data []byte, err error) {
    method UnmarshalJSON (line 77) | func (a *AllServers) UnmarshalJSON(data []byte) (err error) {
    method Count (line 148) | func (a *AllServers) Count() (count int) {
  type Servers (line 155) | type Servers struct
    method Format (line 163) | func (s *Servers) Format(vpnProvider, format string) (formatted string...
    method toJSON (line 174) | func (s *Servers) toJSON() (formatted string, err error) {

FILE: internal/models/servers_test.go
  function Test_AllServers_MarshalJSON (line 13) | func Test_AllServers_MarshalJSON(t *testing.T) {
  function Test_AllServers_UnmarshalJSON (line 84) | func Test_AllServers_UnmarshalJSON(t *testing.T) {
  function Test_AllServers_JSON_Marshal_Unmarshal (line 143) | func Test_AllServers_JSON_Marshal_Unmarshal(t *testing.T) {

FILE: internal/models/sort.go
  type SortableServers (line 7) | type SortableServers
    method Len (line 9) | func (s SortableServers) Len() int {
    method Swap (line 13) | func (s SortableServers) Swap(i, j int) {
    method Less (line 17) | func (s SortableServers) Less(i, j int) bool {

FILE: internal/natpmp/checks.go
  function checkRequest (line 11) | func checkRequest(request []byte) (err error) {
  function checkResponse (line 28) | func checkResponse(response []byte, expectedOperationCode byte,
  function checkResultCode (line 76) | func checkResultCode(resultCode uint16) (err error) {

FILE: internal/natpmp/checks_test.go
  function Test_checkRequest (line 9) | func Test_checkRequest(t *testing.T) {
  function Test_checkResponse (line 41) | func Test_checkResponse(t *testing.T) {
  function Test_checkResultCode (line 105) | func Test_checkResultCode(t *testing.T) {

FILE: internal/natpmp/externaladdress.go
  method ExternalAddress (line 14) | func (c *Client) ExternalAddress(ctx context.Context, gateway netip.Addr) (

FILE: internal/natpmp/externaladdress_test.go
  function Test_Client_ExternalAddress (line 13) | func Test_Client_ExternalAddress(t *testing.T) {

FILE: internal/natpmp/helpers_test.go
  constant initialConnectionDuration (line 14) | initialConnectionDuration = 3 * time.Second
  type udpExchange (line 16) | type udpExchange struct
  function launchUDPServer (line 29) | func launchUDPServer(t *testing.T, exchanges []udpExchange) (

FILE: internal/natpmp/natpmp.go
  type Client (line 8) | type Client struct
  function New (line 15) | func New() (client *Client) {

FILE: internal/natpmp/natpmp_test.go
  function Test_New (line 10) | func Test_New(t *testing.T) {

FILE: internal/natpmp/portmapping.go
  method AddPortMapping (line 20) | func (c *Client) AddPortMapping(ctx context.Context, gateway netip.Addr,

FILE: internal/natpmp/portmapping_test.go
  function Test_Client_AddPortMapping (line 12) | func Test_Client_AddPortMapping(t *testing.T) {

FILE: internal/natpmp/rpc.go
  method rpc (line 19) | func (c *Client) rpc(ctx context.Context, gateway netip.Addr,
  function dedupFailedAttempts (line 133) | func dedupFailedAttempts(failedAttempts []string) (errorMessage string) {
  function indicesToTryString (line 169) | func indicesToTryString(indices []int) string {

FILE: internal/natpmp/rpc_test.go
  function Test_Client_rpc (line 12) | func Test_Client_rpc(t *testing.T) {
  function Test_dedupFailedAttempts (line 168) | func Test_dedupFailedAttempts(t *testing.T) {

FILE: internal/netlink/address.go
  method AddrList (line 11) | func (n *NetLink) AddrList(linkIndex uint32, family uint8) (
  method AddrReplace (line 36) | func (n *NetLink) AddrReplace(linkIndex uint32, prefix netip.Prefix) err...

FILE: internal/netlink/conntrack_linux.go
  method FlushConntrack (line 10) | func (n *NetLink) FlushConntrack() error {

FILE: internal/netlink/conntrack_unspecified.go
  method FlushConntrack (line 5) | func (n *NetLink) FlushConntrack() error {

FILE: internal/netlink/conversion.go
  function netipPrefixToIPNet (line 9) | func netipPrefixToIPNet(prefix netip.Prefix) (ipNet *net.IPNet) {
  function netIPNetToNetipPrefix (line 24) | func netIPNetToNetipPrefix(ipNet *net.IPNet) (prefix netip.Prefix) {
  function ipAndLengthToPrefix (line 39) | func ipAndLengthToPrefix(ip *net.IP, length uint8) netip.Prefix {
  function prefixToIPAndLength (line 52) | func prefixToIPAndLength(prefix netip.Prefix) (ip *net.IP, length uint8) {
  function netipAddrToNetIP (line 63) | func netipAddrToNetIP(address netip.Addr) (ip net.IP) {
  function netIPToNetipAddress (line 76) | func netIPToNetipAddress(ip net.IP) (address netip.Addr) {

FILE: internal/netlink/conversion_test.go
  function Test_netipPrefixToIPNet (line 11) | func Test_netipPrefixToIPNet(t *testing.T) {
  function Test_netIPNetToNetipPrefix (line 55) | func Test_netIPNetToNetipPrefix(t *testing.T) {
  function Test_netIPToNetipAddress (line 102) | func Test_netIPToNetipAddress(t *testing.T) {

FILE: internal/netlink/family.go
  function FamilyToString (line 7) | func FamilyToString(family uint8) string {

FILE: internal/netlink/family_linux.go
  constant FamilyAll (line 6) | FamilyAll uint8 = unix.AF_UNSPEC
  constant FamilyV4 (line 7) | FamilyV4  uint8 = unix.AF_INET
  constant FamilyV6 (line 8) | FamilyV6  uint8 = unix.AF_INET6

FILE: internal/netlink/helpers_test.go
  function ptrTo (line 10) | func ptrTo[T any](v T) *T { return &v }
  function makeNetipPrefix (line 12) | func makeNetipPrefix(n byte) netip.Prefix {
  function makeLinkName (line 19) | func makeLinkName() string {
  type noopLogger (line 28) | type noopLogger struct
    method Debug (line 30) | func (l *noopLogger) Debug(_ string)            {}
    method Debugf (line 31) | func (l *noopLogger) Debugf(_ string, _ ...any) {}
    method Patch (line 32) | func (l *noopLogger) Patch(_ ...log.Option)     {}

FILE: internal/netlink/interfaces.go
  type DebugLogger (line 5) | type DebugLogger interface

FILE: internal/netlink/ipv6.go
  method IsIPv6Supported (line 7) | func (n *NetLink) IsIPv6Supported() (supported bool, err error) {

FILE: internal/netlink/link.go
  type DeviceType (line 10) | type DeviceType
  type Link (line 12) | type Link struct
  method LinkList (line 20) | func (n *NetLink) LinkList() (links []Link, err error) {
  method LinkByName (line 52) | func (n *NetLink) LinkByName(name string) (link Link, err error) {
  method LinkByIndex (line 67) | func (n *NetLink) LinkByIndex(index uint32) (link Link, err error) {
  method LinkAdd (line 82) | func (n *NetLink) LinkAdd(link Link) (linkIndex uint32, err error) {
  method LinkDel (line 120) | func (n *NetLink) LinkDel(linkIndex uint32) (err error) {
  method LinkSetUp (line 130) | func (n *NetLink) LinkSetUp(linkIndex uint32) (err error) {
  method LinkSetDown (line 150) | func (n *NetLink) LinkSetDown(linkIndex uint32) (err error) {
  method LinkSetMTU (line 170) | func (n *NetLink) LinkSetMTU(linkIndex, mtu uint32) error {

FILE: internal/netlink/link_linux.go
  constant DeviceTypeEthernet (line 6) | DeviceTypeEthernet DeviceType = unix.ARPHRD_ETHER
  constant DeviceTypeLoopback (line 7) | DeviceTypeLoopback DeviceType = unix.ARPHRD_LOOPBACK
  constant DeviceTypeNone (line 8) | DeviceTypeNone     DeviceType = unix.ARPHRD_NONE
  constant iffUp (line 10) | iffUp = unix.IFF_UP

FILE: internal/netlink/link_test.go
  function Test_NetLink_LinkList (line 12) | func Test_NetLink_LinkList(t *testing.T) {
  function Test_NetLink_LinkSetMTU (line 60) | func Test_NetLink_LinkSetMTU(t *testing.T) {

FILE: internal/netlink/netlink.go
  type NetLink (line 5) | type NetLink struct
    method PatchLoggerLevel (line 15) | func (n *NetLink) PatchLoggerLevel(level log.Level) {
  function New (line 9) | func New(debugLogger DebugLogger) *NetLink {

FILE: internal/netlink/netlink_unspecified.go
  constant FamilyAll (line 8) | FamilyAll uint8 = iota
  constant FamilyV4 (line 11) | FamilyV4
  constant FamilyV6 (line 14) | FamilyV6
  constant DeviceTypeEthernet (line 17) | DeviceTypeEthernet DeviceType = 0
  constant DeviceTypeLoopback (line 19) | DeviceTypeLoopback DeviceType = 0
  constant DeviceTypeNone (line 21) | DeviceTypeNone DeviceType = 0
  constant iffUp (line 24) | iffUp = 0
  constant RouteTypeUnicast (line 27) | RouteTypeUnicast = 0
  constant ScopeUniverse (line 29) | ScopeUniverse = 0
  constant ProtoStatic (line 31) | ProtoStatic = 0
  constant FlagInvert (line 34) | FlagInvert = 0
  constant ActionToTable (line 36) | ActionToTable = 0
  constant rtTableCompat (line 39) | rtTableCompat = 0
  method RuleList (line 42) | func (n *NetLink) RuleList(family uint8) (rules []Rule, err error) {
  method RuleAdd (line 46) | func (n *NetLink) RuleAdd(rule Rule) error {
  method RuleDel (line 50) | func (n *NetLink) RuleDel(rule Rule) error {
  method IsWireguardSupported (line 54) | func (n *NetLink) IsWireguardSupported() (bool, error) {

FILE: internal/netlink/route.go
  type Route (line 10) | type Route struct
    method fromMessage (line 24) | func (r *Route) fromMessage(message rtnetlink.RouteMessage) {
    method message (line 44) | func (r Route) message() *rtnetlink.RouteMessage {
  method RouteList (line 85) | func (n *NetLink) RouteList(family uint8) (routes []Route, err error) {
  method RouteAdd (line 109) | func (n *NetLink) RouteAdd(route Route) error {
  method RouteDel (line 119) | func (n *NetLink) RouteDel(route Route) error {
  method RouteReplace (line 129) | func (n *NetLink) RouteReplace(route Route) error {

FILE: internal/netlink/route_linux.go
  constant RouteTypeUnicast (line 6) | RouteTypeUnicast = unix.RTN_UNICAST
  constant ScopeUniverse (line 7) | ScopeUniverse    = unix.RT_SCOPE_UNIVERSE
  constant ProtoStatic (line 8) | ProtoStatic      = unix.RTPROT_STATIC
  constant rtTableCompat (line 10) | rtTableCompat = unix.RT_TABLE_COMPAT

FILE: internal/netlink/rule.go
  type Rule (line 10) | type Rule struct
    method fromMessage (line 21) | func (r *Rule) fromMessage(message rtnetlink.RuleMessage) {
    method message (line 36) | func (r Rule) message() *rtnetlink.RuleMessage {
    method String (line 64) | func (r Rule) String() string {
    method debugMessage (line 84) | func (r Rule) debugMessage(add bool) (debugMessage string) {

FILE: internal/netlink/rule_linux.go
  constant FlagInvert (line 11) | FlagInvert    = unix.FIB_RULE_INVERT
  constant ActionToTable (line 12) | ActionToTable = unix.FR_ACT_TO_TBL
  method RuleList (line 15) | func (n *NetLink) RuleList(family uint8) (rules []Rule, err error) {
  method RuleAdd (line 49) | func (n *NetLink) RuleAdd(rule Rule) error {
  method RuleDel (line 60) | func (n *NetLink) RuleDel(rule Rule) error {

FILE: internal/netlink/rule_test.go
  function Test_Rule_debugMessage (line 9) | func Test_Rule_debugMessage(t *testing.T) {

FILE: internal/netlink/wireguard_linux.go
  method IsWireguardSupported (line 12) | func (n *NetLink) IsWireguardSupported() (ok bool, err error) {
  function hasWireguardFamily (line 42) | func hasWireguardFamily() (ok bool, err error) {

FILE: internal/netlink/wireguard_test.go
  function Test_NetLink_IsWireguardSupported (line 11) | func Test_NetLink_IsWireguardSupported(t *testing.T) {

FILE: internal/openvpn/auth.go
  method WriteAuthFile (line 10) | func (c *Configurator) WriteAuthFile(user, password string) error {
  method WriteAskPassFile (line 16) | func (c *Configurator) WriteAskPassFile(passphrase string) error {
  function writeIfDifferent (line 20) | func writeIfDifferent(path, content string, puid, pgid int) (err error) {

FILE: internal/openvpn/config.go
  method WriteConfig (line 8) | func (c *Configurator) WriteConfig(lines []string) error {

FILE: internal/openvpn/extract/data.go
  method Data (line 16) | func (e *Extractor) Data(filepath string) (lines []string,

FILE: internal/openvpn/extract/extract.go
  function extractDataFromLines (line 16) | func extractDataFromLines(lines []string) (
  function extractDataFromLine (line 55) | func extractDataFromLine(line string) (
  function extractProto (line 86) | func extractProto(line string) (protocol string, err error) {
  function parseProto (line 97) | func parseProto(field string) (protocol string, err error) {
  function extractRemote (line 119) | func extractRemote(line string) (ip netip.Addr, port uint16,
  function extractPort (line 159) | func extractPort(line string) (port uint16, err error) {

FILE: internal/openvpn/extract/extract_test.go
  function Test_extractDataFromLines (line 14) | func Test_extractDataFromLines(t *testing.T) {
  function Test_extractDataFromLine (line 85) | func Test_extractDataFromLine(t *testing.T) {
  function Test_extractProto (line 149) | func Test_extractProto(t *testing.T) {
  function Test_extractRemote (line 193) | func Test_extractRemote(t *testing.T) {

FILE: internal/openvpn/extract/extractor.go
  type Extractor (line 3) | type Extractor struct
  function New (line 5) | func New() *Extractor {

FILE: internal/openvpn/extract/helpers_test.go
  function removeFile (line 10) | func removeFile(t *testing.T, filename string) {

FILE: internal/openvpn/extract/pem.go
  function PEM (line 12) | func PEM(b []byte) (encodedData string, err error) {

FILE: internal/openvpn/extract/pem_test.go
  function Test_PEM (line 10) | func Test_PEM(t *testing.T) {
  constant validCertPEM (line 57) | validCertPEM = `
  constant validCertData (line 97) | validCertData = "MIIGrDCCBJSgAwIBAgIEAdTnfTANBgkqhkiG9w0BAQsFADB7MQswCQY...

FILE: internal/openvpn/extract/read.go
  function readCustomConfigLines (line 9) | func readCustomConfigLines(filepath string) (

FILE: internal/openvpn/extract/read_test.go
  function Test_readCustomConfigLines (line 11) | func Test_readCustomConfigLines(t *testing.T) {

FILE: internal/openvpn/interfaces.go
  type CmdStarter (line 5) | type CmdStarter interface
  type CmdRunStarter (line 11) | type CmdRunStarter interface

FILE: internal/openvpn/logger.go
  type Logger (line 3) | type Logger interface
  type Infoer (line 10) | type Infoer interface

FILE: internal/openvpn/logs.go
  type logLevel (line 10) | type logLevel
  constant levelInfo (line 13) | levelInfo logLevel = iota
  constant levelWarn (line 14) | levelWarn
  constant levelError (line 15) | levelError
  function processLogLine (line 18) | func processLogLine(s string) (filtered string, level logLevel) {

FILE: internal/openvpn/logs_test.go
  function Test_processLogLine (line 9) | func Test_processLogLine(t *testing.T) {

FILE: internal/openvpn/openvpn.go
  type Configurator (line 7) | type Configurator struct
  function New (line 16) | func New(logger Infoer, cmder CmdRunStarter,

FILE: internal/openvpn/paths.go
  constant configPath (line 3) | configPath = "/etc/openvpn/target.ovpn"

FILE: internal/openvpn/pkcs8/algorithms.go
  type encryptedPrivateKey (line 16) | type encryptedPrivateKey struct
  type encryptedAlgorithmParams (line 21) | type encryptedAlgorithmParams struct
  function getEncryptionAlgorithmOid (line 26) | func getEncryptionAlgorithmOid(der []byte) (

FILE: internal/openvpn/pkcs8/algorithms_test.go
  function Test_getEncryptionAlgorithmOid (line 17) | func Test_getEncryptionAlgorithmOid(t *testing.T) {

FILE: internal/openvpn/pkcs8/descbc.go
  function init (line 13) | func init() { //nolint:gochecknoinits
  function newCipherDESCBCBlock (line 17) | func newCipherDESCBCBlock() pkcs8lib.Cipher {
  type cipherDESCBC (line 21) | type cipherDESCBC struct
    method IVSize (line 23) | func (c cipherDESCBC) IVSize() int {
    method KeySize (line 27) | func (c cipherDESCBC) KeySize() int {
    method OID (line 31) | func (c cipherDESCBC) OID() asn1.ObjectIdentifier {
    method Encrypt (line 35) | func (c cipherDESCBC) Encrypt(key, iv, plaintext []byte) ([]byte, erro...
    method Decrypt (line 50) | func (c cipherDESCBC) Decrypt(key, iv, ciphertext []byte) ([]byte, err...

FILE: internal/openvpn/pkcs8/upgrade.go
  function UpgradeEncryptedKey (line 22) | func UpgradeEncryptedKey(encryptedPKCS8DERKey, passphrase string) (secur...

FILE: internal/openvpn/pkcs8/upgrade_test.go
  function parsePEMFile (line 15) | func parsePEMFile(t *testing.T, pemFilepath string) (base64DER string) {
  function Test_UpgradeEncryptedKey (line 29) | func Test_UpgradeEncryptedKey(t *testing.T) {

FILE: internal/openvpn/run.go
  type Runner (line 9) | type Runner struct
    method Run (line 25) | func (r *Runner) Run(ctx context.Context, errCh chan<- error, ready ch...
  function NewRunner (line 15) | func NewRunner(settings settings.OpenVPN, starter CmdStarter,

FILE: internal/openvpn/start.go
  constant binOpenvpn25 (line 15) | binOpenvpn25 = "openvpn2.5"
  constant binOpenvpn26 (line 16) | binOpenvpn26 = "openvpn2.6"
  function start (line 19) | func start(ctx context.Context, starter CmdStarter, version string, flag...

FILE: internal/openvpn/start_linux.go
  function setCmdSysProcAttr (line 8) | func setCmdSysProcAttr(cmd *exec.Cmd) {

FILE: internal/openvpn/start_unspecified.go
  function setCmdSysProcAttr (line 10) | func setCmdSysProcAttr(cmd *exec.Cmd) {

FILE: internal/openvpn/stream.go
  function streamLines (line 8) | func streamLines(ctx context.Context, done chan<- struct{},

FILE: internal/openvpn/version.go
  method Version25 (line 11) | func (c *Configurator) Version25(ctx context.Context) (version string, e...
  method Version26 (line 15) | func (c *Configurator) Version26(ctx context.Context) (version string, e...
  method version (line 21) | func (c *Configurator) version(ctx context.Context, binName string) (ver...

FILE: internal/pmtud/constants/lengths.go
  constant MaxEthernetFrameSize (line 4) | MaxEthernetFrameSize uint32 = 1500
  constant MinIPv4MTU (line 7) | MinIPv4MTU uint32 = 68
  constant MinIPv6MTU (line 8) | MinIPv6MTU uint32 = 1280
  constant IPv4HeaderLength (line 10) | IPv4HeaderLength uint32 = 20
  constant IPv6HeaderLength (line 11) | IPv6HeaderLength uint32 = 40
  constant UDPHeaderLength (line 12) | UDPHeaderLength  uint32 = 8
  constant BaseTCPHeaderLength (line 15) | BaseTCPHeaderLength uint32 = 20
  constant MaxTCPHeaderLength (line 18) | MaxTCPHeaderLength     uint32 = 60
  constant WireguardHeaderLength (line 19) | WireguardHeaderLength  uint32 = 32
  constant OpenVPNHeaderMaxLength (line 20) | OpenVPNHeaderMaxLength uint32 = 1 + // opcode

FILE: internal/pmtud/constants/syscall_unix.go
  constant SOCK_RAW (line 9) | SOCK_RAW    = unix.SOCK_RAW
  constant SOCK_STREAM (line 10) | SOCK_STREAM = unix.SOCK_STREAM
  constant AF_INET (line 11) | AF_INET     = unix.AF_INET
  constant AF_INET6 (line 12) | AF_INET6    = unix.AF_INET6
  constant IPPROTO_TCP (line 13) | IPPROTO_TCP = unix.IPPROTO_TCP
  constant EAGAIN (line 14) | EAGAIN      = unix.EAGAIN
  constant EWOULDBLOCK (line 15) | EWOULDBLOCK = unix.EWOULDBLOCK

FILE: internal/pmtud/constants/syscall_windows.go
  constant SOCK_RAW (line 6) | SOCK_RAW    = windows.SOCK_RAW
  constant SOCK_STREAM (line 7) | SOCK_STREAM = windows.SOCK_STREAM
  constant AF_INET (line 8) | AF_INET     = windows.AF_INET
  constant AF_INET6 (line 9) | AF_INET6    = windows.AF_INET6
  constant IPPROTO_TCP (line 10) | IPPROTO_TCP = windows.IPPROTO_TCP
  constant EAGAIN (line 11) | EAGAIN      = windows.WSAEWOULDBLOCK
  constant EWOULDBLOCK (line 12) | EWOULDBLOCK = windows.WSAEWOULDBLOCK

FILE: internal/pmtud/icmp/apple_ipv4.go
  type ipv4Wrapper (line 14) | type ipv4Wrapper struct
    method ReadFrom (line 22) | func (i *ipv4Wrapper) ReadFrom(p []byte) (n int, addr net.Addr, err er...
    method WriteTo (line 27) | func (i *ipv4Wrapper) WriteTo(p []byte, addr net.Addr) (n int, err err...
    method Close (line 31) | func (i *ipv4Wrapper) Close() error {
    method LocalAddr (line 35) | func (i *ipv4Wrapper) LocalAddr() net.Addr {
    method SetDeadline (line 39) | func (i *ipv4Wrapper) SetDeadline(t time.Time) error {
    method SetReadDeadline (line 43) | func (i *ipv4Wrapper) SetReadDeadline(t time.Time) error {
    method SetWriteDeadline (line 47) | func (i *ipv4Wrapper) SetWriteDeadline(t time.Time) error {
  function ipv4ToNetPacketConn (line 18) | func ipv4ToNetPacketConn(ipv4 *ipv4.PacketConn) *ipv4Wrapper {

FILE: internal/pmtud/icmp/check.go
  function checkMTU (line 16) | func checkMTU(mtu, minMTU, physicalLinkMTU uint32) (err error) {
  function checkInvokingReplyIDMatch (line 28) | func checkInvokingReplyIDMatch(icmpProtocol int, received []byte,
  function checkEchoReply (line 45) | func checkEchoReply(icmpProtocol int, received []byte,
  function checkEchoBodies (line 70) | func checkEchoBodies(sent, received []byte, receivedTruncated bool) (err...

FILE: internal/pmtud/icmp/df_linux.go
  function setDontFragment (line 7) | func setDontFragment(fd uintptr, ipv4 bool) (err error) {

FILE: internal/pmtud/icmp/df_unspecified.go
  function setDontFragment (line 8) | func setDontFragment(fd uintptr, ipv4 bool) (err error) {

FILE: internal/pmtud/icmp/df_windows.go
  function setDontFragment (line 7) | func setDontFragment(fd uintptr, ipv4 bool) (err error) {

FILE: internal/pmtud/icmp/errors.go
  function wrapConnErr (line 20) | func wrapConnErr(err error, timedCtx context.Context, pingTimeout time.D...

FILE: internal/pmtud/icmp/icmp.go
  function PathMTUDiscover (line 19) | func PathMTUDiscover(ctx context.Context, ip netip.Addr,

FILE: internal/pmtud/icmp/interfaces.go
  type Logger (line 3) | type Logger interface

FILE: internal/pmtud/icmp/ipv4.go
  constant icmpv4Protocol (line 20) | icmpv4Protocol = 1
  function listenICMPv4 (line 23) | func listenICMPv4(ctx context.Context) (conn net.PacketConn, err error) {
  function findIPv4NextHopMTU (line 53) | func findIPv4NextHopMTU(ctx context.Context, ip netip.Addr,

FILE: internal/pmtud/icmp/ipv6.go
  constant icmpv6Protocol (line 18) | icmpv6Protocol = 58
  function listenICMPv6 (line 21) | func listenICMPv6(ctx context.Context) (conn net.PacketConn, err error) {
  function getIPv6PacketTooBig (line 45) | func getIPv6PacketTooBig(ctx context.Context, ip netip.Addr,

FILE: internal/pmtud/icmp/message.go
  function buildMessageToSend (line 14) | func buildMessageToSend(ipVersion string, mtu uint32) (id uint16, messag...

FILE: internal/pmtud/icmp/multi.go
  type icmpTestUnit (line 16) | type icmpTestUnit struct
  function pmtudMultiSizes (line 23) | func pmtudMultiSizes(ctx context.Context, ip netip.Addr,
  constant maxPossibleMTU (line 112) | maxPossibleMTU = 9196
  function collectReplies (line 114) | func collectReplies(conn net.PacketConn, ipVersion string,

FILE: internal/pmtud/interfaces.go
  type Logger (line 3) | type Logger interface

FILE: internal/pmtud/ip/family.go
  function GetFamilies (line 10) | func GetFamilies(dsts []netip.AddrPort) (families []int) {
  function GetFamily (line 22) | func GetFamily(dst netip.AddrPort) int {

FILE: internal/pmtud/ip/ipheader.go
  function HeaderLength (line 10) | func HeaderLength(ipv4 bool) uint32 {
  function HeaderV4 (line 17) | func HeaderV4(srcIP, dstIP netip.Addr, payloadLength uint32) []byte {
  function ipChecksum (line 44) | func ipChecksum(header []byte) uint16 {
  function HeaderV6 (line 61) | func HeaderV6(srcIP, dstIP netip.Addr,

FILE: internal/pmtud/ip/ipheader_darwin.go
  function putUint16 (line 7) | func putUint16(b []byte, v uint16) {

FILE: internal/pmtud/ip/ipheader_unspecified.go
  function putUint16 (line 7) | func putUint16(b []byte, v uint16) {

FILE: internal/pmtud/ip/ipv4_unix.go
  function SetIPv4HeaderIncluded (line 7) | func SetIPv4HeaderIncluded(fd int) error {

FILE: internal/pmtud/ip/ipv4_unspecified.go
  function SetIPv4HeaderIncluded (line 5) | func SetIPv4HeaderIncluded(fd int) error {

FILE: internal/pmtud/ip/ipv4_windows.go
  function SetIPv4HeaderIncluded (line 7) | func SetIPv4HeaderIncluded(handle windows.Handle) error {

FILE: internal/pmtud/ip/source.go
  function SrcAddr (line 17) | func SrcAddr(dst netip.AddrPort, proto int) (src netip.AddrPort, cleanup...
  function srcIP (line 36) | func srcIP(dst netip.Addr) (netip.Addr, error) {
  function srcPort (line 82) | func srcPort(srcAddr netip.Addr, proto int) (srcPort uint16, cleanup fun...

FILE: internal/pmtud/ip/source_unix.go
  function socket (line 12) | func socket(domain int, typ int, proto int) (fd int, err error) {
  function closeSocket (line 16) | func closeSocket(fd int) error {
  function bind (line 20) | func bind(fd int, addr unix.Sockaddr) error {
  function makeSockAddr (line 24) | func makeSockAddr(ip netip.Addr, port uint16) unix.Sockaddr {
  function extractPortFromFD (line 37) | func extractPortFromFD(fd int) (uint16, error) {

FILE: internal/pmtud/ip/source_windows.go
  function socket (line 10) | func socket(domain int, typ int, proto int) (fd windows.Handle, err erro...
  function closeSocket (line 14) | func closeSocket(fd windows.Handle) error {
  function bind (line 18) | func bind(fd windows.Handle, addr windows.Sockaddr) error {
  function makeSockAddr (line 22) | func makeSockAddr(ip netip.Addr, port uint16) windows.Sockaddr {
  function extractPortFromFD (line 35) | func extractPortFromFD(fd windows.Handle) (uint16, error) {

FILE: internal/pmtud/nooplogger.go
  type noopLogger (line 3) | type noopLogger struct
    method Debug (line 5) | func (noopLogger) Debug(_ string)            {}
    method Debugf (line 6) | func (noopLogger) Debugf(_ string, _ ...any) {}
    method Warnf (line 7) | func (noopLogger) Warnf(_ string, _ ...any)  {}

FILE: internal/pmtud/pmtud.go
  function PathMTUDiscover (line 31) | func PathMTUDiscover(ctx context.Context, icmpAddrs []netip.Addr, tcpAdd...

FILE: internal/pmtud/pmtud_integration_test.go
  function Test_PathMTUDiscover (line 14) | func Test_PathMTUDiscover(t *testing.T) {

FILE: internal/pmtud/tcp/helpers_test.go
  function getFirewall (line 31) | func getFirewall(t *testing.T) *firewall.Config {
  type noopLogger (line 50) | type noopLogger struct
    method Patch (line 52) | func (l *noopLogger) Patch(_ ...log.Option)     {}
    method Debug (line 53) | func (l *noopLogger) Debug(_ string)            {}
    method Debugf (line 54) | func (l *noopLogger) Debugf(_ string, _ ...any) {}
    method Info (line 55) | func (l *noopLogger) Info(_ string)             {}
    method Warn (line 56) | func (l *noopLogger) Warn(_ string)             {}
    method Warnf (line 57) | func (l *noopLogger) Warnf(_ string, _ ...any)  {}
    method Error (line 58) | func (l *noopLogger) Error(_ string)            {}
  function findLoopbackMTU (line 62) | func findLoopbackMTU(netlinker *netlink.NetLink) (mtu uint32, err error) {
  function findDefaultRouteMTU (line 82) | func findDefaultRouteMTU(netlinker *netlink.NetLink) (mtu uint32, err er...
  function reserveClosedPort (line 108) | func reserveClosedPort(t *testing.T) (port uint16) {

FILE: internal/pmtud/tcp/interfaces.go
  type Firewall (line 8) | type Firewall interface
  type Logger (line 13) | type Logger interface

FILE: internal/pmtud/tcp/mocks_test.go
  type MockLogger (line 14) | type MockLogger struct
    method EXPECT (line 32) | func (m *MockLogger) EXPECT() *MockLoggerMockRecorder {
    method Debug (line 37) | func (m *MockLogger) Debug(arg0 string) {
    method Debugf (line 49) | func (m *MockLogger) Debugf(arg0 string, arg1 ...interface{}) {
    method Warnf (line 66) | func (m *MockLogger) Warnf(arg0 string, arg1 ...interface{}) {
  type MockLoggerMockRecorder (line 20) | type MockLoggerMockRecorder struct
    method Debug (line 43) | func (mr *MockLoggerMockRecorder) Debug(arg0 interface{}) *gomock.Call {
    method Debugf (line 59) | func (mr *MockLoggerMockRecorder) Debugf(arg0 interface{}, arg1 ...int...
    method Warnf (line 76) | func (mr *MockLoggerMockRecorder) Warnf(arg0 interface{}, arg1 ...inte...
  function NewMockLogger (line 25) | func NewMockLogger(ctrl *gomock.Controller) *MockLogger {

FILE: internal/pmtud/tcp/mss.go
  function findHighestMSSDestination (line 19) | func findHighestMSSDestination(ctx context.Context, familyToFD map[int]f...
  function findMSS (line 82) | func findMSS(ctx context.Context, fd fileDescriptor, dst netip.AddrPort,

FILE: internal/pmtud/tcp/mss_test.go
  function Test_findHighestMSSDestination (line 17) | func Test_findHighestMSSDestination(t *testing.T) {

FILE: internal/pmtud/tcp/multi.go
  type testUnit (line 20) | type testUnit struct
  constant excludeMark (line 25) | excludeMark = 4545
  function PathMTUDiscover (line 35) | func PathMTUDiscover(ctx context.Context, dsts []netip.AddrPort,
  function pathMTUDiscover (line 119) | func pathMTUDiscover(ctx context.Context, fd fileDescriptor,

FILE: internal/pmtud/tcp/packet.go
  function createSYNPacket (line 17) | func createSYNPacket(src, dst netip.AddrPort, mtu uint32) (packet []byte...
  function createACKPacket (line 30) | func createACKPacket(src, dst netip.AddrPort, seq, ack uint32, mtu uint3...
  function createRSTPacket (line 39) | func createRSTPacket(src, dst netip.AddrPort, seq, ack uint32) []byte {
  function getPayloadLength (line 44) | func getPayloadLength(mtu uint32, dst netip.AddrPort) uint32 {
  function createPacket (line 57) | func createPacket(src, dst netip.AddrPort,
  function generatePayload (line 107) | func generatePayload(length uint16) []byte {
  function makeRandom (line 162) | func makeRandom(b []byte) {

FILE: internal/pmtud/tcp/tcp.go
  function startRawSockets (line 13) | func startRawSockets(families []int, excludeMark int) (familyToSocket ma...
  function startRawSocket (line 36) | func startRawSocket(family, excludeMark int) (fd fileDescriptor, stop fu...
  function runTest (line 88) | func runTest(ctx context.Context, dst netip.AddrPort, mtu uint32,
  function handleRSTReply (line 203) | func handleRSTReply(ctx context.Context, fd fileDescriptor, ch <-chan []...
  function sendRST (line 231) | func sendRST(fd fileDescriptor, src, dst netip.AddrPort,

FILE: internal/pmtud/tcp/tcp_darwin.go
  function stripIPv4Header (line 3) | func stripIPv4Header(reply []byte) (result []byte, ok bool) {

FILE: internal/pmtud/tcp/tcp_integration_test.go
  function Test_PathMTUDiscover (line 19) | func Test_PathMTUDiscover(t *testing.T) {

FILE: internal/pmtud/tcp/tcp_linux.go
  function setMark (line 12) | func setMark(fd, excludeMark int) error {
  function setMTUDiscovery (line 16) | func setMTUDiscovery(fd int, ipv4 bool) error {

FILE: internal/pmtud/tcp/tcp_notdarwin.go
  function stripIPv4Header (line 9) | func stripIPv4Header(reply []byte) (result []byte, ok bool) {

FILE: internal/pmtud/tcp/tcp_test.go
  function Test_runTest (line 18) | func Test_runTest(t *testing.T) {

FILE: internal/pmtud/tcp/tcp_unix.go
  type fileDescriptor (line 13) | type fileDescriptor
  function socket (line 15) | func socket(domain int, typ int, proto int) (fd int, err error) {
  function closeSocket (line 19) | func closeSocket(fd int) error {
  function sendTo (line 23) | func sendTo(fd fileDescriptor, p []byte, flags int, to unix.Sockaddr) (e...
  function setSocketTimeout (line 27) | func setSocketTimeout(fd fileDescriptor, timeout time.Duration) (err err...
  function recvFrom (line 32) | func recvFrom(fd fileDescriptor, p []byte, flags int) (n int, from unix....
  function setNonBlock (line 36) | func setNonBlock(fd int) error {
  function makeSockAddr (line 40) | func makeSockAddr(addr netip.AddrPort) unix.Sockaddr {

FILE: internal/pmtud/tcp/tcp_unspecified.go
  function setMark (line 5) | func setMark(fd, excludeMark int) error {
  function setMTUDiscovery (line 9) | func setMTUDiscovery(fd int, ipv4 bool) error {

FILE: internal/pmtud/tcp/tcp_windows.go
  type fileDescriptor (line 11) | type fileDescriptor
  function socket (line 13) | func socket(domain int, typ int, proto int) (fd windows.Handle, err erro...
  function closeSocket (line 17) | func closeSocket(fd windows.Handle) error {
  function sendTo (line 21) | func sendTo(fd fileDescriptor, p []byte, flags int, to windows.Sockaddr)...
  function setSocketTimeout (line 25) | func setSocketTimeout(fd fileDescriptor, timeout time.Duration) (err err...
  function recvFrom (line 30) | func recvFrom(fd fileDescriptor, p []byte, flags int) (n int, from windo...
  function setMark (line 34) | func setMark(fd windows.Handle, _ int) error {
  function setMTUDiscovery (line 38) | func setMTUDiscovery(fd windows.Handle, ipv4 bool) error {
  function setNonBlock (line 42) | func setNonBlock(fd windows.Handle) error {
  function makeSockAddr (line 51) | func makeSockAddr(addr netip.AddrPort) windows.Sockaddr {

FILE: internal/pmtud/tcp/tcpheader.go
  function makeTCPHeader (line 13) | func makeTCPHeader(srcPort, dstPort uint16, seq, ack uint32, flags byte)...
  function tcpChecksum (line 31) | func tcpChecksum(ipHeader, tcpHeader, payload []byte) uint16 {
  constant finFlag (line 66) | finFlag byte = 0x01
  constant synFlag (line 67) | synFlag byte = 0x02
  constant rstFlag (line 68) | rstFlag byte = 0x04
  constant pshFlag (line 69) | pshFlag byte = 0x08
  constant ackFlag (line 70) | ackFlag byte = 0x10
  type packetType (line 73) | type packetType
    method String (line 86) | func (p packetType) String() string {
  constant packetTypeSYN (line 76) | packetTypeSYN packetType = iota + 1
  constant packetTypeSYNACK (line 77) | packetTypeSYNACK
  constant packetTypeFIN (line 78) | packetTypeFIN
  constant packetTypeFINACK (line 79) | packetTypeFINACK
  constant packetTypeRST (line 80) | packetTypeRST
  constant packetTypeRSTACK (line 81) | packetTypeRSTACK
  constant packetTypePSHACK (line 82) | packetTypePSHACK
  constant packetTypeACK (line 83) | packetTypeACK
  type tcpHeader (line 109) | type tcpHeader struct
  function parseTCPHeader (line 131) | func parseTCPHeader(b []byte) (header tcpHeader, err error) {
  type options (line 197) | type options struct
  type optionTimestamps (line 204) | type optionTimestamps struct
  function parseTCPOptions (line 218) | func parseTCPOptions(b []byte) (parsed options, err error) {

FILE: internal/pmtud/tcp/tracker.go
  type tracker (line 14) | type tracker struct
    method constructKey (line 32) | func (t *tracker) constructKey(localPort, remotePort uint16) uint32 {
    method register (line 39) | func (t *tracker) register(localPort, remotePort uint16,
    method unregister (line 51) | func (t *tracker) unregister(localPort, remotePort uint16) {
    method listen (line 58) | func (t *tracker) listen(ctx context.Context) (err error) {
    method listenFD (line 87) | func (t *tracker) listenFD(ctx context.Context, fd fileDescriptor, ipv...
  type dispatch (line 20) | type dispatch struct
  function newTracker (line 25) | func newTracker(familyToFD map[int]fileDescriptor) *tracker {
  function pollSleep (line 147) | func pollSleep(ctx context.Context) {

FILE: internal/pmtud/test/mtu.go
  function MakeMTUsToTest (line 18) | func MakeMTUsToTest(minMTU, maxMTU uint32) (mtus []uint32) {

FILE: internal/pmtud/test/mtu_test.go
  function Test_MakeMTUsToTest (line 9) | func Test_MakeMTUsToTest(t *testing.T) {

FILE: internal/pmtud/vpn.go
  function MaxTheoreticalVPNMTU (line 15) | func MaxTheoreticalVPNMTU(vpnType, network string, vpnGateway netip.Addr...

FILE: internal/portforward/interfaces.go
  type Service (line 11) | type Service interface
  type Routing (line 18) | type Routing interface
  type PortAllower (line 23) | type PortAllower interface
  type Logger (line 30) | type Logger interface
  type Cmder (line 37) | type Cmder interface

FILE: internal/portforward/loop.go
  type Loop (line 14) | type Loop struct
    method String (line 62) | func (l *Loop) String() string {
    method Start (line 66) | func (l *Loop) Start(_ context.Context) (runError <-chan error, _ erro...
    method run (line 82) | func (l *Loop) run(runCtx context.Context, runDone chan<- struct{},
    method UpdateWith (line 138) | func (l *Loop) UpdateWith(partialUpdate Settings) (err error) {
    method Stop (line 153) | func (l *Loop) Stop() (err error) {
    method GetPortsForwarded (line 163) | func (l *Loop) GetPortsForwarded() (ports []uint16) {
    method SetPortsForwarded (line 172) | func (l *Loop) SetPortsForwarded(ports []uint16) (err error) {
  function NewLoop (line 37) | func NewLoop(settings settings.PortForwarding, routing Routing,
  function ptrTo (line 180) | func ptrTo[T any](value T) *T {

FILE: internal/portforward/service/command.go
  function runCommand (line 9) | func runCommand(ctx context.Context, cmder Cmder, logger Logger,

FILE: internal/portforward/service/command_test.go
  function Test_Service_runCommand (line 14) | func Test_Service_runCommand(t *testing.T) {

FILE: internal/portforward/service/fs.go
  method writePortForwardedFile (line 9) | func (s *Service) writePortForwardedFile(ports []uint16) (err error) {

FILE: internal/portforward/service/helpers.go
  function portsToString (line 8) | func portsToString(ports []uint16) (s string) {

FILE: internal/portforward/service/helpers_test.go
  function Test_portsToString (line 9) | func Test_portsToString(t *testing.T) {

FILE: internal/portforward/service/interfaces.go
  type PortAllower (line 11) | type PortAllower interface
  type Routing (line 18) | type Routing interface
  type Logger (line 23) | type Logger interface
  type PortForwarder (line 30) | type PortForwarder interface
  type Cmder (line 37) | type Cmder interface

FILE: internal/portforward/service/mocks_test.go
  type MockLogger (line 14) | type MockLogger struct
    method EXPECT (line 32) | func (m *MockLogger) EXPECT() *MockLoggerMockRecorder {
    method Debug (line 37) | func (m *MockLogger) Debug(arg0 string) {
    method Error (line 49) | func (m *MockLogger) Error(arg0 string) {
    method Info (line 61) | func (m *MockLogger) Info(arg0 string) {
    method Warn (line 73) | func (m *MockLogger) Warn(arg0 string) {
  type MockLoggerMockRecorder (line 20) | type MockLoggerMockRecorder struct
    method Debug (line 43) | func (mr *MockLoggerMockRecorder) Debug(arg0 interface{}) *gomock.Call {
    method Error (line 55) | func (mr *MockLoggerMockRecorder) Error(arg0 interface{}) *gomock.Call {
    method Info (line 67) | func (mr *MockLoggerMockRecorder) Info(arg0 interface{}) *gomock.Call {
    method Warn (line 79) | func (mr *MockLoggerMockRecorder) Warn(arg0 interface{}) *gomock.Call {
  function NewMockLogger (line 25) | func NewMockLogger(ctrl *gomock.Controller) *MockLogger {

FILE: internal/portforward/service/service.go
  type Service (line 11) | type Service struct
    method GetPortsForwarded (line 48) | func (s *Service) GetPortsForwarded() (ports []uint16) {
    method SetPortsForwarded (line 56) | func (s *Service) SetPortsForwarded(ctx context.Context, ports []uint1...
  function New (line 31) | func New(settings Settings, routing Routing, client *http.Client,

FILE: internal/portforward/service/settings.go
  type Settings (line 11) | type Settings struct
    method Copy (line 25) | func (s Settings) Copy() (copied Settings) {
    method OverrideWith (line 40) | func (s *Settings) OverrideWith(update Settings) {
    method Validate (line 63) | func (s *Settings) Validate(forStartup bool) (err error) {

FILE: internal/portforward/service/start.go
  method Start (line 12) | func (s *Service) Start(ctx context.Context) (runError <-chan error, err...
  method onNewPorts (line 89) | func (s *Service) onNewPorts(ctx context.Context, ports []uint16) (err e...

FILE: internal/portforward/service/stop.go
  method Stop (line 9) | func (s *Service) Stop() (err error) {
  method cleanup (line 29) | func (s *Service) cleanup() (err error) {

FILE: internal/portforward/settings.go
  type Settings (line 8) | type Settings struct
    method updateWith (line 20) | func (s Settings) updateWith(partialUpdate Settings,
    method copy (line 32) | func (s Settings) copy() (copied Settings) {
    method overrideWith (line 38) | func (s *Settings) overrideWith(update Settings) {
    method validate (line 43) | func (s Settings) validate(forStartup bool) (err error) {

FILE: internal/pprof/helpers_test.go
  function boolPtr (line 9) | func boolPtr(b bool) *bool { return &b }
  function intPtr (line 11) | func intPtr(n int) *int { return &n }
  type regexMatcher (line 15) | type regexMatcher struct
    method Matches (line 19) | func (r *regexMatcher) Matches(x interface{}) bool {
    method String (line 27) | func (r *regexMatcher) String() string {
  function newRegexMatcher (line 31) | func newRegexMatcher(regex string) *regexMatcher {

FILE: internal/pprof/logger_mock_test.go
  type MockLogger (line 14) | type MockLogger struct
    method EXPECT (line 32) | func (m *MockLogger) EXPECT() *MockLoggerMockRecorder {
    method Error (line 37) | func (m *MockLogger) Error(arg0 string) {
    method Info (line 49) | func (m *MockLogger) Info(arg0 string) {
    method Warn (line 61) | func (m *MockLogger) Warn(arg0 string) {
  type MockLoggerMockRecorder (line 20) | type MockLoggerMockRecorder struct
    method Error (line 43) | func (mr *MockLoggerMockRecorder) Error(arg0 interface{}) *gomock.Call {
    method Info (line 55) | func (mr *MockLoggerMockRecorder) Info(arg0 interface{}) *gomock.Call {
    method Warn (line 67) | func (mr *MockLoggerMockRecorder) Warn(arg0 interface{}) *gomock.Call {
  function NewMockLogger (line 25) | func NewMockLogger(ctrl *gomock.Controller) *MockLogger {

FILE: internal/pprof/server.go
  function New (line 15) | func New(settings Settings) (server *httpserver.Server, err error) {

FILE: internal/pprof/server_test.go
  function Test_Server (line 18) | func Test_Server(t *testing.T) {
  function Test_Server_BadSettings (line 113) | func Test_Server_BadSettings(t *testing.T) {

FILE: internal/pprof/settings.go
  type Settings (line 15) | type Settings struct
    method SetDefaults (line 30) | func (s *Settings) SetDefaults() {
    method Copy (line 38) | func (s Settings) Copy() (copied Settings) {
    method OverrideWith (line 47) | func (s *Settings) OverrideWith(other Settings) {
    method Validate (line 59) | func (s Settings) Validate() (err error) {
    method ToLinesNode (line 71) | func (s Settings) ToLinesNode() (node *gotree.Node) {
    method String (line 91) | func (s Settings) String() string {
    method Read (line 95) | func (s *Settings) Read(r *reader.Reader) (err error) {

FILE: internal/pprof/settings_test.go
  function Test_Settings_SetDefaults (line 13) | func Test_Settings_SetDefaults(t *testing.T) {
  function Test_Settings_Copy (line 68) | func Test_Settings_Copy(t *testing.T) {
  function Test_Settings_OverrideWith (line 109) | func Test_Settings_OverrideWith(t *testing.T) {
  function Test_Settings_Validate (line 193) | func Test_Settings_Validate(t *testing.T) {
  function Test_Settings_String (line 258) | func Test_Settings_String(t *testing.T) {

FILE: internal/provider/airvpn/connection.go
  method GetConnection (line 9) | func (p *Provider) GetConnection(selection settings.ServerSelection, ipv...

FILE: internal/provider/airvpn/openvpnconf.go
  method OpenVPNConfig (line 12) | func (p *Provider) OpenVPNConfig(connection models.Connection,

FILE: internal/provider/airvpn/provider.go
  type Provider (line 12) | type Provider struct
    method Name (line 28) | func (p *Provider) Name() string {
  function New (line 18) | func New(storage common.Storage, randSource rand.Source,

FILE: internal/provider/airvpn/updater/api.go
  type apiData (line 13) | type apiData struct
  type apiServer (line 17) | type apiServer struct
  function fetchAPI (line 34) | func fetchAPI(ctx context.Context, client *http.Client) (

FILE: internal/provider/airvpn/updater/servers.go
  method FetchServers (line 15) | func (u *Updater) FetchServers(ctx context.Context, minServers int) (

FILE: internal/provider/airvpn/updater/updater.go
  type Updater (line 7) | type Updater struct
  function New (line 11) | func New(client *http.Client) *Updater {

FILE: internal/provider/common/mocks.go
  type MockParallelResolver (line 19) | type MockParallelResolver struct
    method EXPECT (line 37) | func (m *MockParallelResolver) EXPECT() *MockParallelResolverMockRecor...
    method Resolve (line 42) | func (m *MockParallelResolver) Resolve(arg0 context.Context, arg1 reso...
  type MockParallelResolverMockRecorder (line 25) | type MockParallelResolverMockRecorder struct
    method Resolve (line 52) | func (mr *MockParallelResolverMockRecorder) Resolve(arg0, arg1 interfa...
  function NewMockParallelResolver (line 30) | func NewMockParallelResolver(ctrl *gomock.Controller) *MockParallelResol...
  type MockStorage (line 58) | type MockStorage struct
    method EXPECT (line 76) | func (m *MockStorage) EXPECT() *MockStorageMockRecorder {
    method FilterServers (line 81) | func (m *MockStorage) FilterServers(arg0 string, arg1 settings.ServerS...
  type MockStorageMockRecorder (line 64) | type MockStorageMockRecorder struct
    method FilterServers (line 90) | func (mr *MockStorageMockRecorder) FilterServers(arg0, arg1 interface{...
  function NewMockStorage (line 69) | func NewMockStorage(ctrl *gomock.Controller) *MockStorage {
  type MockUnzipper (line 96) | type MockUnzipper struct
    method EXPECT (line 114) | func (m *MockUnzipper) EXPECT() *MockUnzipperMockRecorder {
    method FetchAndExtract (line 119) | func (m *MockUnzipper) FetchAndExtract(arg0 context.Context, arg1 stri...
  type MockUnzipperMockRecorder (line 102) | type MockUnzipperMockRecorder struct
    method FetchAndExtract (line 128) | func (mr *MockUnzipperMockRecorder) FetchAndExtract(arg0, arg1 interfa...
  function NewMockUnzipper (line 107) | func NewMockUnzipper(ctrl *gomock.Controller) *MockUnzipper {
  type MockWarner (line 134) | type MockWarner struct
    method EXPECT (line 152) | func (m *MockWarner) EXPECT() *MockWarnerMockRecorder {
    method Warn (line 157) | func (m *MockWarner) Warn(arg0 string) {
  type MockWarnerMockRecorder (line 140) | type MockWarnerMockRecorder struct
    method Warn (line 163) | func (mr *MockWarnerMockRecorder) Warn(arg0 interface{}) *gomock.Call {
  function NewMockWarner (line 145) | func NewMockWarner(ctrl *gomock.Controller) *MockWarner {

FILE: internal/provider/common/storage.go
  type Storage (line 8) | type Storage interface

FILE: internal/provider/common/updater.go
  type Fetcher (line 19) | type Fetcher interface
  type ParallelResolver (line 23) | type ParallelResolver interface
  type Unzipper (line 28) | type Unzipper interface
  type Warner (line 33) | type Warner interface
  type IPFetcher (line 37) | type IPFetcher interface

FILE: internal/provider/custom/connection.go
  method GetConnection (line 16) | func (p *Provider) GetConnection(selection settings.ServerSelection, _ b...
  function getOpenVPNConnection (line 29) | func getOpenVPNConnection(extractor Extractor,
  function getWireguardConnection (line 54) | func getWireguardConnection(selection settings.ServerSelection) (

FILE: internal/provider/custom/interfaces.go
  type Extractor (line 5) | type Extractor interface

FILE: internal/provider/custom/openvpnconf.go
  method OpenVPNConfig (line 17) | func (p *Provider) OpenVPNConfig(connection models.Connection,
  function modifyConfig (line 33) | func modifyConfig(lines []string, connection models.Connection,
  function hasPrefixOneOf (line 107) | func hasPrefixOneOf(s string, prefixes ...string) bool {

FILE: internal/provider/custom/openvpnconf_test.go
  function intPtr (line 14) | func intPtr(n int) *int          { return &n }
  function uint16Ptr (line 15) | func uint16Ptr(n uint16) *uint16 { return &n }
  function stringPtr (line 16) | func stringPtr(s string) *string { return &s }
  function Test_modifyConfig (line 18) | func Test_modifyConfig(t *testing.T) {

FILE: internal/provider/custom/provider.go
  type Provider (line 9) | type Provider struct
    method Name (line 21) | func (p *Provider) Name() string {
  function New (line 14) | func New(extractor Extractor) *Provider {

FILE: internal/provider/cyberghost/connection.go
  method GetConnection (line 9) | func (p *Provider) GetConnection(selection settings.ServerSelection, ipv...

FILE: internal/provider/cyberghost/openvpnconf.go
  method OpenVPNConfig (line 10) | func (p *Provider) OpenVPNConfig(connection models.Connection,

FILE: internal/provider/cyberghost/provider.go
  type Provider (line 11) | type Provider struct
    method Name (line 27) | func (p *Provider) Name() string {
  function New (line 17) | func New(storage common.Storage, randSource rand.Source,

FILE: internal/provider/cyberghost/updater/constants.go
  function getGroupIDToProtocol (line 5) | func getGroupIDToProtocol() map[string]string {
  function getSubdomainToRegion (line 16) | func getSubdomainToRegion() map[string]string {

FILE: internal/provider/cyberghost/updater/countries.go
  function mergeCountryCodes (line 3) | func mergeCountryCodes(base, extend map[string]string) (merged map[strin...

FILE: internal/provider/cyberghost/updater/hosttoserver.go
  type hostToServer (line 11) | type hostToServer
    method hostsSlice (line 42) | func (hts hostToServer) hostsSlice() (hosts []string) {
    method adaptWithIPs (line 50) | func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]netip.Addr) {
    method toSlice (line 63) | func (hts hostToServer) toSlice() (servers []models.Server) {
  function getPossibleServers (line 13) | func getPossibleServers() (possibleServers hostToServer) {

FILE: internal/provider/cyberghost/updater/resolve.go
  function parallelResolverSettings (line 9) | func parallelResolverSettings(hosts []string) (settings resolver.Paralle...

FILE: internal/provider/cyberghost/updater/servers.go
  method FetchServers (line 13) | func (u *Updater) FetchServers(ctx context.Context, minServers int) (

FILE: internal/provider/cyberghost/updater/updater.go
  type Updater (line 7) | type Updater struct
  function New (line 12) | func New(parallelResolver common.ParallelResolver, warner common.Warner)...

FILE: internal/provider/example/connection.go
  method GetConnection (line 9) | func (p *Provider) GetConnection(selection settings.ServerSelection, ipv...

FILE: internal/provider/example/openvpnconf.go
  method OpenVPNConfig (line 10) | func (p *Provider) OpenVPNConfig(connection models.Connection,

FILE: internal/provider/example/provider.go
  type Provider (line 12) | type Provider struct
    method Name (line 30) | func (p *Provider) Name() string {
  function New (line 19) | func New(storage common.Storage, randSource rand.Source,

FILE: internal/provider/example/updater/api.go
  type apiData (line 13) | type apiData struct
  type apiServer (line 17) | type apiServer struct
  function fetchAPI (line 26) | func fetchAPI(ctx context.Context, client *http.Client) (

FILE: internal/provider/example/updater/resolve.go
  function parallelResolverSettings (line 11) | func parallelResolverSettings(hosts []string) (settings resolver.Paralle...

FILE: internal/provider/example/updater/servers.go
  method FetchServers (line 13) | func (u *Updater) FetchServers(ctx context.Context, minServers int) (

FILE: internal/provider/example/updater/updater.go
  type Updater (line 9) | type Updater struct
  function New (line 17) | func New(warner common.Warner, unzipper common.Unzipper,

FILE: internal/provider/expressvpn/connection.go
  method GetConnection (line 9) | func (p *Provider) GetConnection(selection settings.ServerSelection, ipv...

FILE: internal/provider/expressvpn/connection_test.go
  function Test_Provider_GetConnection (line 19) | func Test_Provider_GetConnection(t *testing.T) {

FILE: internal/provider/expressvpn/openvpnconf.go
  method OpenVPNConfig (line 10) | func (p *Provider) OpenVPNConfig(connection models.Connection,

FILE: internal/provider/expressvpn/provider.go
  type Provider (line 11) | type Provider struct
    method Name (line 28) | func (p *Provider) Name() string {
  function New (line 17) | func New(storage common.Storage, randSource rand.Source,

FILE: internal/provider/expressvpn/updater/hardcoded.go
  function hardcodedServers (line 7) | func hardcodedServers() (servers []models.Server) {

FILE: internal/provider/expressvpn/updater/resolve.go
  function parallelResolverSettings (line 9) | func parallelResolverSettings(hosts []string) (settings resolver.Paralle...

FILE: internal/provider/expressvpn/updater/servers.go
  method FetchServers (line 13) | func (u *Updater) FetchServers(ctx context.Context, minServers int) (

FILE: internal/provider/expressvpn/updater/updater.go
  type Updater (line 7) | type Updater struct
  function New (line 13) | func New(unzipper common.Unzipper, warner common.Warner,

FILE: internal/provider/fastestvpn/connection.go
  method GetConnection (line 9) | func (p *Provider) GetConnection(selection settings.ServerSelection, ipv...

FILE: internal/provider/fastestvpn/openvpnconf.go
  method OpenVPNConfig (line 10) | func (p *Provider) OpenVPNConfig(connection models.Connection,

FILE: internal/provider/fastestvpn/provider.go
  type Provider (line 12) | type Provider struct
    method Name (line 29) | func (p *Provider) Name() string {
  function New (line 18) | func New(storage common.Storage, randSource rand.Source,

FILE: internal/provider/fastestvpn/updater/api.go
  type apiServer (line 16) | type apiServer struct
  constant apiURL (line 24) | apiURL = "https://support.fastestvpn.com/wp-admin/admin-ajax.php"
  function fetchAPIServers (line 28) | func fetchAPIServers(ctx context.Context, client *http.Client, protocol ...
  function getNextTRBlock (line 105) | func getNextTRBlock(data []byte) (trBlock []byte) {
  function getNextTDBlock (line 110) | func getNextTDBlock(data []byte) (tdBlock []byte) {
  function getNextBlock (line 115) | func getNextBlock(data []byte, startToken, endToken string) (nextBlock [...

FILE: internal/provider/fastestvpn/updater/api_test.go
  type roundTripFunc (line 15) | type roundTripFunc
    method RoundTrip (line 17) | func (f roundTripFunc) RoundTrip(r *http.Request) (*http.Response, err...
  function Test_fechAPIServers (line 21) | func Test_fechAPIServers(t *testing.T) {
  function Test_getNextBlock (line 122) | func Test_getNextBlock(t *testing.T) {

FILE: internal/provider/fastestvpn/updater/hosttoserver.go
  type hostToServerData (line 10) | type hostToServerData
    method add (line 22) | func (hts hostToServerData) add(host, vpnType, country, city string, t...
    method toHostsSlice (line 48) | func (hts hostToServerData) toHostsSlice() (hosts []string) {
    method adaptWithIPs (line 56) | func (hts hostToServerData) adaptWithIPs(hostToIPs map[string][]netip....
    method toServersSlice (line 68) | func (hts hostToServerData) toServersSlice() (servers []models.Server) {
  type serverData (line 12) | type serverData struct

FILE: internal/provider/fastestvpn/updater/resolve.go
  function parallelResolverSettings (line 9) | func parallelResolverSettings(hosts []string) (settings resolver.Paralle...

FILE: internal/provider/fastestvpn/updater/servers.go
  method FetchServers (line 13) | func (u *Updater) FetchServers(ctx context.Context, minServers int) (

FILE: internal/provider/fastestvpn/updater/updater.go
  type Updater (line 9) | type Updater struct
  function New (line 15) | func New(client *http.Client, warner common.Warner,

FILE: internal/provider/giganews/connection.go
  method GetConnection (line 9) | func (p *Provider) GetConnection(selection settings.ServerSelection, ipv...

FILE: internal/provider/giganews/openvpnconf.go
  method OpenVPNConfig (line 10) | func (p *Provider) OpenVPNConfig(connection models.Connection,

FILE: internal/provider/giganews/provider.go
  type Provider (line 11) | type Provider struct
    method Name (line 28) | func (p *Provider) Name() string {
  function New (line 17) | func New(storage common.Storage, randSource rand.Source,

FILE: internal/provider/giganews/updater/filename.go
  function parseFilename (line 11) | func parseFilename(fileName string) (

FILE: internal/provider/giganews/updater/hosttoserver.go
  type hostToServer (line 10) | type hostToServer
    method add (line 12) | func (hts hostToServer) add(host, region string, tcp, udp bool) {
    method toHostsSlice (line 28) | func (hts hostToServer) toHostsSlice() (hosts []string) {
    method adaptWithIPs (line 36) | func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]netip.Addr) {
    method toServersSlice (line 49) | func (hts hostToServer) toServersSlice() (servers []models.Server) {

FILE: internal/provider/giganews/updater/resolve.go
  function parallelResolverSettings (line 9) | func parallelResolverSettings(hosts []string) (settings resolver.Paralle...

FILE: internal/provider/giganews/updater/servers.go
  method FetchServers (line 14) | func (u *Updater) FetchServers(ctx context.Context, minServers int) (

FILE: internal/provider/giganews/updater/updater.go
  type Updater (line 7) | type Updater struct
  function New (line 13) | func New(unzipper common.Unzipper, warner common.Warner,

FILE: internal/provider/hidemyass/connection.go
  method GetConnection (line 9) | func (p *Provider) GetConnection(selection settings.ServerSelection, ipv...

FILE: internal/provider/hidemyass/openvpnconf.go
  method OpenVPNConfig (line 10) | func (p *Provider) OpenVPNConfig(connection models.Connection,

FILE: internal/provider/hidemyass/provider.go
  type Provider (line 12) | type Provider struct
    method Name (line 29) | func (p *Provider) Name() string {
  function New (line 18) | func New(storage common.Storage, randSource rand.Source,

FILE: internal/provider/hidemyass/updater/hosts.go
  function getUniqueHosts (line 3) | func getUniqueHosts(tcpHostToURL, udpHostToURL map[string]string) (

FILE: internal/provider/hidemyass/updater/hosttourl.go
  function getAllHostToURL (line 11) | func getAllHostToURL(ctx context.Context, client *http.Client) (
  function getHostToURL (line 27) | func getHostToURL(ctx context.Context, client *http.Client, protocol str...

FILE: internal/provider/hidemyass/updater/index.go
  function fetchIndex (line 13) | func fetchIndex(ctx context.Context, client *http.Client, indexURL strin...

FILE: internal/provider/hidemyass/updater/resolve.go
  function parallelResolverSettings (line 9) | func parallelResolverSettings(hosts []string) (settings resolver.Paralle...

FILE: internal/provider/hidemyass/updater/servers.go
  method FetchServers (line 13) | func (u *Updater) FetchServers(ctx context.Context, minServers int) (

FILE: internal/provider/hidemyass/updater/updater.go
  type Updater (line 9) | type Updater struct
  function New (line 15) | func New(client *http.Client, warner common.Warner,

FILE: internal/provider/hidemyass/updater/url.go
  function parseOpenvpnURL (line 8) | func parseOpenvpnURL(url, protocol string) (country, region, city string) {
  function camelCaseToWords (line 39) | func camelCaseToWords(camelCase string) (words string) {
  function mutateSpecialCountryCases (line 51) | func mutateSpecialCountryCases(country string) string {

FILE: internal/provider/ipvanish/connection.go
  method GetConnection (line 9) | func (p *Provider) GetConnection(selection settings.ServerSelection, ipv...

FILE: internal/provider/ipvanish/openvpnconf.go
  method OpenVPNConfig (line 10) | func (p *Provider) OpenVPNConfig(connection models.Connection,

FILE: internal/provider/ipvanish/provider.go
  type Provider (line 11) | type Provider struct
    method Name (line 28) | func (p *Provider) Name() string {
  function New (line 17) | func New(storage common.Storage, randSource rand.Source,

FILE: internal/provider/ipvanish/updater/filename.go
  function parseFilename (line 14) | func parseFilename(fileName, hostname string, titleCaser cases.Caser) (

FILE: internal/provider/ipvanish/updater/filename_test.go
  function Test_parseFilename (line 13) | func Test_parseFilename(t *testing.T) {

FILE: internal/provider/ipvanish/updater/hosttoserver.go
  type hostToServer (line 11) | type hostToServer
    method add (line 13) | func (hts hostToServer) add(host, country, city string, tcp, udp bool) {
    method toHostsSlice (line 30) | func (hts hostToServer) toHostsSlice() (hosts []string) {
    method adaptWithIPs (line 41) | func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]netip.Addr) {
    method toServersSlice (line 54) | func (hts hostToServer) toServersSlice() (servers []models.Server) {

FILE: internal/provider/ipvanish/updater/hosttoserver_test.go
  function Test_hostToServer_add (line 12) | func Test_hostToServer_add(t *testing.T) {
  function Test_hostToServer_toHostsSlice (line 98) | func Test_hostToServer_toHostsSlice(t *testing.T) {
  function Test_hostToServer_adaptWithIPs (line 131) | func Test_hostToServer_adaptWithIPs(t *testing.T) {
  function Test_hostToServer_toServersSlice (line 184) | func Test_hostToServer_toServersSlice(t *testing.T) {

FILE: internal/provider/ipvanish/updater/resolve.go
  function parallelResolverSettings (line 9) | func parallelResolverSettings(hosts []string) (settings resolver.Paralle...

FILE: internal/provider/ipvanish/updater/servers.go
  method FetchServers (line 16) | func (u *Updater) FetchServers(ctx context.Context, minServers int) (

FILE: internal/provider/ipvanish/updater/servers_test.go
  function Test_Updater_GetServers (line 19) | func Test_Updater_GetServers(t *testing.T) {

FILE: internal/provider/ipvanish/updater/updater.go
  type Updater (line 7) | type Updater struct
  function New (line 13) | func New(unzipper common.Unzipper, warner common.Warner,

FILE: internal/provider/ivpn/connection.go
  method GetConnection (line 9) | func (p *Provider) GetConnection(selection settings.ServerSelection, ipv...

FILE: internal/provider/ivpn/connection_test.go
  function Test_Provider_GetConnection (line 20) | func Test_Provider_GetConnection(t *testing.T) {

FILE: internal/provider/ivpn/openvpnconf.go
  method OpenVPNConfig (line 10) | func (p *Provider) OpenVPNConfig(connection models.Connection,

FILE: internal/provider/ivpn/provider.go
  type Provider (line 12) | type Provider struct
    method Name (line 29) | func (p *Provider) Name() string {
  function New (line 18) | func New(storage common.Storage, randSource rand.Source,

FILE: internal/provider/ivpn/updater/api.go
  type apiData (line 13) | type apiData struct
  type apiServer (line 17) | type apiServer struct
  type apiHostnames (line 26) | type apiHostnames struct
  function fetchAPI (line 31) | func fetchAPI(ctx context.Context, client *http.Client) (

FILE: internal/provider/ivpn/updater/api_test.go
  function Test_fetchAPI (line 15) | func Test_fetchAPI(t *testing.T) {

FILE: internal/provider/ivpn/updater/resolve.go
  function parallelResolverSettings (line 9) | func parallelResolverSettings(hosts []string) (settings resolver.Paralle...

FILE: internal/provider/ivpn/updater/roundtrip_test.go
  type roundTripFunc (line 5) | type roundTripFunc
    method RoundTrip (line 7) | func (f roundTripFunc) RoundTrip(r *http.Request) (*http.Response, err...

FILE: internal/provider/ivpn/updater/servers.go
  method FetchServers (line 14) | func (u *Updater) FetchServers(ctx context.Context, minServers int) (
  function parseCity (line 104) | func parseCity(city string) (parsedCity, region string) {

FILE: internal/provider/ivpn/updater/servers_test.go
  function Test_Updater_GetServers (line 22) | func Test_Updater_GetServers(t *testing.T) {

FILE: internal/provider/ivpn/updater/updater.go
  type Updater (line 9) | type Updater struct
  function New (line 15) | func New(client *http.Client, warner common.Warner,

FILE: internal/provider/mullvad/connection.go
  method GetConnection (line 9) | func (p *Provider) GetConnection(selection settings.ServerSelection, ipv...

FILE: internal/provider/mullvad/connection_test.go
  function Test_Provider_GetConnection (line 20) | func Test_Provider_GetConnection(t *testing.T) {

FILE: internal/provider/mullvad/openvpnconf.go
  method OpenVPNConfig (line 8) | func (p *Provider) OpenVPNConfig(_ models.Connection, _ settings.OpenVPN...

FILE: internal/provider/mullvad/provider.go
  type Provider (line 12) | type Provider struct
    method Name (line 28) | func (p *Provider) Name() string {
  function New (line 18) | func New(storage common.Storage, randSource rand.Source,

FILE: internal/provider/mullvad/updater/api.go
  type serverData (line 16) | type serverData struct
  function fetchAPI (line 29) | func fetchAPI(ctx context.Context, client *http.Client) (data []serverDa...

FILE: internal/provider/mullvad/updater/hosttoserver.go
  type hostToServer (line 13) | type hostToServer
    method add (line 22) | func (hts hostToServer) add(data serverData) (err error) {
    method toServersSlice (line 78) | func (hts hostToServer) toServersSlice() (servers []models.Server) {

FILE: internal/provider/mullvad/updater/ips.go
  function uniqueSortedIPs (line 8) | func uniqueSortedIPs(ips []netip.Addr) []netip.Addr {

FILE: internal/provider/mullvad/updater/ips_test.go
  function Test_uniqueSortedIPs (line 10) | func Test_uniqueSortedIPs(t *testing.T) {

FILE: internal/provider/mullvad/updater/servers.go
  method FetchServers (line 12) | func (u *Updater) FetchServers(ctx context.Context, minServers int) (

FILE: internal/provider/mullvad/updater/updater.go
  type Updater (line 7) | type Updater struct
  function New (line 11) | func New(client *http.Client) *Updater {

FILE: internal/provider/nordvpn/connection.go
  method GetConnection (line 9) | func (p *Provider) GetConnection(selection settings.ServerSelection, ipv...

FILE: internal/provider/nordvpn/openvpnconf.go
  method OpenVPNConfig (line 10) | func (p *Provider) OpenVPNConfig(connection models.Connection,

FILE: internal/provider/nordvpn/provider.go
  type Provider (line 12) | type Provider struct
    method Name (line 28) | func (p *Provider) Name() string {
  function New (line 18) | func New(storage common.Storage, randSource rand.Source,

FILE: internal/provider/nordvpn/updater/api.go
  function fetchAPI (line 13) | func fetchAPI(ctx context.Context, client *http.Client,

FILE: internal/provider/nordvpn/updater/models.go
  type serversData (line 12) | type serversData struct
    method idToData (line 88) | func (s serversData) idToData() (
  type serverData (line 20) | type serverData struct
    method region (line 120) | func (s *serverData) region(groups map[uint32]groupData) (region strin...
    method hasVPNService (line 133) | func (s *serverData) hasVPNService(services map[uint32]serviceData) (o...
    method categories (line 147) | func (s *serverData) categories(groups map[uint32]groupData) (categori...
    method ips (line 160) | func (s *serverData) ips() (ips []netip.Addr) {
    method wireguardPublicKey (line 177) | func (s *serverData) wireguardPublicKey(technologies map[uint32]techno...
  type groupData (line 57) | type groupData struct
  type serviceData (line 65) | type serviceData struct
  type locationData (line 70) | type locationData struct
  type technologyData (line 80) | type technologyData struct

FILE: internal/provider/nordvpn/updater/name.go
  function parseServerName (line 15) | func parseServerName(serverName string) (number uint16, err error) {

FILE: internal/provider/nordvpn/updater/servers.go
  method FetchServers (line 16) | func (u *Updater) FetchServers(ctx context.Context, minServers int) (
  function extractServers (line 46) | func extractServers(jsonServer serverData, groups map[uint32]groupData,

FILE: internal/provider/nordvpn/updater/updater.go
  type Updater (line 9) | type Updater struct
  function New (line 14) | func New(client *http.Client, warner common.Warner) *Updater {

FILE: internal/provider/perfectprivacy/connection.go
  method GetConnection (line 9) | func (p *Provider) GetConnection(selection settings.ServerSelection, ipv...

FILE: internal/provider/perfectprivacy/openvpnconf.go
  method OpenVPNConfig (line 10) | func (p *Provider) OpenVPNConfig(connection models.Connection,

FILE: internal/provider/perfectprivacy/portforward.go
  method PortForward (line 11) | func (p *Provider) PortForward(_ context.Context,
  method KeepPortForward (line 21) | func (p *Provider) KeepPortForward(ctx context.Context,
  function internalIPToPorts (line 30) | func internalIPToPorts(internalIP netip.Addr) (ports []uint16) {

FILE: internal/provider/perfectprivacy/portforward_test.go
  function Test_internalIPToPorts (line 10) | func Test_internalIPToPorts(t *testing.T) {

FILE: internal/provider/perfectprivacy/provider.go
  type Provider (line 11) | type Provider struct
    method Name (line 27) | func (p *Provider) Name() string {
  function New (line 17) | func New(storage common.Storage, randSource rand.Source,

FILE: internal/provider/perfectprivacy/updater/citytoserver.go
  type cityToServer (line 10) | type cityToServer
    method add (line 12) | func (cts cityToServer) add(city string, ips []netip.Addr) {
    method toServersSlice (line 41) | func (cts cityToServer) toServersSlice() (servers []models.Server) {

FILE: internal/provider/perfectprivacy/updater/filename.go
  function parseFilename (line 8) | func parseFilename(fileName string) (city string) {

FILE: internal/provider/perfectprivacy/updater/servers.go
  method FetchServers (line 15) | func (u *Updater) FetchServers(ctx context.Context, minServers int) (
  function addServerFromOvpn (line 56) | func addServerFromOvpn(cts cityToServer,

FILE: internal/provider/perfectprivacy/updater/updater.go
  type Updater (line 7) | type Updater struct
  function New (line 12) | func New(unzipper common.Unzipper, warner common.Warner) *Updater {

FILE: internal/provider/privado/connection.go
  method GetConnection (line 9) | func (p *Provider) GetConnection(selection settings.ServerSelection, ipv...

FILE: internal/provider/privado/openvpnconf.go
  method OpenVPNConfig (line 10) | func (p *Provider) OpenVPNConfig(connection models.Connection,

FILE: internal/provider/privado/provider.go
  type Provider (line 12) | type Provider struct
    method Name (line 28) | func (p *Provider) Name() string {
  function New (line 18) | func New(storage common.Storage, randSource rand.Source,

FILE: internal/provider/privado/updater/servers.go
  method FetchServers (line 16) | func (u *Updater) FetchServers(ctx context.Context, minServers int) (

FILE: internal/provider/privado/updater/updater.go
  type Updater (line 9) | type Updater struct
  function New (line 14) | func New(client *http.Client, warner common.Warner) *Updater {

FILE: internal/provider/privateinternetaccess/connection.go
  method GetConnection (line 10) | func (p *Provider) GetConnection(selection settings.ServerSelection, ipv...

FILE: internal/provider/privateinternetaccess/httpclient.go
  function newHTTPClient (line 15) | func newHTTPClient(serverName string) (client *http.Client, err error) {

FILE: internal/provider/privateinternetaccess/httpclient_test.go
  function Test_newHTTPClient (line 14) | func Test_newHTTPClient(t *testing.T) {

FILE: internal/provider/privateinternetaccess/openvpnconf.go
  method OpenVPNConfig (line 11) | func (p *Provider) OpenVPNConfig(connection models.Connection,

FILE: internal/provider/privateinternetaccess/portforward.go
  method PortForward (line 27) | func (p *Provider) PortForward(ctx context.Context,
  method KeepPortForward (line 92) | func (p *Provider) KeepPortForward(ctx context.Context,
  function buildAPIIPAddress (line 143) | func buildAPIIPAddress(gateway netip.Addr) (api netip.Addr) {
  function refreshPIAPortForwardData (line 154) | func refreshPIAPortForwardData(ctx context.Context, client, privateIPCli...
  type piaPayload (line 174) | type piaPayload struct
  type piaPortForwardData (line 180) | type piaPortForwardData struct
  function readPIAPortForwardData (line 187) | func readPIAPortForwardData(portForwardPath string) (data piaPortForward...
  function writePIAPortForwardData (line 204) | func writePIAPortForwardData(portForwardPath string, data piaPortForward...
  function unpackPayload (line 221) | func unpackPayload(payload string) (port uint16, token string, expiratio...
  function packPayload (line 237) | func packPayload(port uint16, token string, expiration time.Time) (paylo...
  function fetchToken (line 255) | func fetchToken(ctx context.Context, client *http.Client,
  function fetchPortForwardData (line 308) | func fetchPortForwardData(ctx context.Context, client *http.Client, apiI...
  function bindPort (line 366) | func bindPort(ctx context.Context, client *http.Client, apiIPAddress net...
  function replaceInErr (line 425) | func replaceInErr(err error, substitutions map[string]string) error {
  function replaceInString (line 431) | func replaceInString(s string, substitutions map[string]string) string {
  function makeNOKStatusError (line 440) | func makeNOKStatusError(response *http.Response, substitutions map[strin...

FILE: internal/provider/privateinternetaccess/portforward_test.go
  function Test_unpackPayload (line 14) | func Test_unpackPayload(t *testing.T) {
  function makePIAPayload (line 64) | func makePIAPayload(t *testing.T, token string, port uint16, expiration ...
  function Test_replaceInString (line 79) | func Test_replaceInString(t *testing.T) {

FILE: internal/provider/privateinternetaccess/presets/presets.go
  constant None (line 4) | None   = "none"
  constant Normal (line 5) | Normal = "normal"
  constant Strong (line 6) | Strong = "strong"

FILE: internal/provider/privateinternetaccess/provider.go
  type Provider (line 13) | type Provider struct
    method Name (line 35) | func (p *Provider) Name() string {
  function New (line 22) | func New(storage common.Storage, randSource rand.Source,

FILE: internal/provider/privateinternetaccess/updater/api.go
  type apiData (line 16) | type apiData struct
  type regionData (line 20) | type regionData struct
  type serverData (line 31) | type serverData struct
  function fetchAPI (line 36) | func fetchAPI(ctx context.Context, client *http.Client) (

FILE: internal/provider/privateinternetaccess/updater/hosttoserver.go
  type nameToServer (line 10) | type nameToServer
    method add (line 12) | func (nts nameToServer) add(name, hostname, region string,
    method toServersSlice (line 52) | func (nts nameToServer) toServersSlice() (servers []models.Server) {

FILE: internal/provider/privateinternetaccess/updater/servers.go
  method FetchServers (line 13) | func (u *Updater) FetchServers(ctx context.Context, minServers int) (
  function addData (line 78) | func addData(regions []regionData, nts nameToServer) (change bool) {

FILE: internal/provider/privateinternetaccess/updater/updater.go
  type Updater (line 7) | type Updater struct
  function New (line 11) | func New(client *http.Client) *Updater {

FILE: internal/provider/privatevpn/connection.go
  method GetConnection (line 9) | func (p *Provider) GetConnection(selection settings.ServerSelection, ipv...

FILE: internal/provider/privatevpn/openvpnconf.go
  method OpenVPNConfig (line 10) | func (p *Provider) OpenVPNConfig(connection models.Connection,

FILE: internal/provider/privatevpn/portforward.go
  method PortForward (line 24) | func (p *Provider) PortForward(ctx context.Context, objects utils.PortFo...
  method KeepPortForward (line 81) | func (p *Provider) KeepPortForward(ctx context.Context,

FILE: internal/provider/privatevpn/portforward_test.go
  type roundTripFunc (line 16) | type roundTripFunc
    method RoundTrip (line 18) | func (s roundTripFunc) RoundTrip(r *http.Request) (*http.Response, err...
  function Test_Provider_PortForward (line 22) | func Test_Provider_PortForward(t *testing.T) {

FILE: internal/provider/privatevpn/provider.go
  type Provider (line 11) | type Provider struct
    method Name (line 28) | func (p *Provider) Name() string {
  function New (line 17) | func New(storage common.Storage, randSource rand.Source,

FILE: internal/provider/privatevpn/updater/countries.go
  function codeToCountry (line 5) | func codeToCountry(countryCode string, countryCodes map[string]string) (

FILE: internal/provider/privatevpn/updater/filename.go
  function parseFilename (line 18) | func parseFilename(fileName string) (

FILE: internal/provider/privatevpn/updater/hosttoserver.go
  type hostToServer (line 10) | type hostToServer
    method add (line 13) | func (hts hostToServer) add(host, country, city string) {
    method toHostsSlice (line 27) | func (hts hostToServer) toHostsSlice() (hosts []string) {
    method adaptWithIPs (line 35) | func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]netip.Addr) {
    method toServersSlice (line 48) | func (hts hostToServer) toServersSlice() (servers []models.Server) {

FILE: internal/provider/privatevpn/updater/resolve.go
  function parallelResolverSettings (line 9) | func parallelResolverSettings(hosts []string) (settings resolver.Paralle...

FILE: internal/provider/privatevpn/updater/servers.go
  method FetchServers (line 16) | func (u *Updater) FetchServers(ctx context.Context, minServers int) (

FILE: internal/provider/privatevpn/updater/updater.go
  type Updater (line 7) | type Updater struct
  function New (line 13) | func New(unzipper common.Unzipper, warner common.Warner,

FILE: internal/provider/protonvpn/connection.go
  method GetConnection (line 9) | func (p *Provider) GetConnection(selection settings.ServerSelection, ipv...

FILE: internal/provider/protonvpn/openvpnconf.go
  method OpenVPNConfig (line 10) | func (p *Provider) OpenVPNConfig(connection models.Connection,

FILE: internal/provider/protonvpn/portforward.go
  method PortForward (line 17) | func (p *Provider) PortForward(ctx context.Context, objects utils.PortFo...
  function checkLifetime (line 66) | func checkLifetime(logger utils.Logger, protocol string,
  function checkExternalPorts (line 76) | func checkExternalPorts(logger utils.Logger, udpPort, tcpPort uint16) {
  method KeepPortForward (line 85) | func (p *Provider) KeepPortForward(ctx context.Context,

FILE: internal/provider/protonvpn/provider.go
  type Provider (line 12) | type Provider struct
    method Name (line 30) | func (p *Provider) Name() string {
  function New (line 19) | func New(storage common.Storage, randSource rand.Source,

FILE: internal/provider/protonvpn/updater/api.go
  type apiClient (line 24) | type apiClient struct
    method setHeaders (line 69) | func (c *apiClient) setHeaders(request *http.Request, cookie cookie) {
    method authenticate (line 79) | func (c *apiClient) authenticate(ctx context.Context, email, password ...
    method getSessionID (line 131) | func (c *apiClient) getSessionID(ctx context.Context) (sessionID strin...
    method getUnauthSession (line 158) | func (c *apiClient) getUnauthSession(ctx context.Context, sessionID st...
    method cookieToken (line 219) | func (c *apiClient) cookieToken(ctx context.Context, sessionID, tokenT...
    method authInfo (line 305) | func (c *apiClient) authInfo(ctx context.Context, email string, unauth...
    method auth (line 410) | func (c *apiClient) auth(ctx context.Context, unauthCookie cookie,
    method fetchServers (line 583) | func (c *apiClient) fetchServers(ctx context.Context, cookie cookie) (
  function newAPIClient (line 34) | func newAPIClient(ctx context.Context, httpClient *http.Client) (client ...
  type cookie (line 381) | type cookie struct
    method String (line 387) | func (c *cookie) String() string {
  function generateLettersDigits (line 531) | func generateLettersDigits(rng *rand.ChaCha8, length uint) string {
  function generateFromCharset (line 536) | func generateFromCharset(rng *rand.ChaCha8, length uint, charset string)...
  function httpHeadersToString (line 546) | func httpHeadersToString(headers http.Header) string {
  type apiData (line 561) | type apiData struct
  type logicalServer (line 565) | type logicalServer struct
  type physicalServer (line 575) | type physicalServer struct
  function buildError (line 614) | func buildError(httpCode int, body []byte) error {

FILE: internal/provider/protonvpn/updater/countries.go
  function codeToCountry (line 5) | func codeToCountry(countryCode string, countryCodes map[string]string) (

FILE: internal/provider/protonvpn/updater/iptoserver.go
  type ipToServers (line 10) | type ipToServers
    method add (line 19) | func (its ipToServers) add(country, region, city, name, hostname, wgPu...
    method toServersSlice (line 54) | func (its ipToServers) toServersSlice() (serversSlice []models.Server) {
    method numberOfServers (line 63) | func (its ipToServers) numberOfServers() int {
  type features (line 12) | type features struct

FILE: internal/provider/protonvpn/updater/servers.go
  method FetchServers (line 13) | func (u *Updater) FetchServers(ctx context.Context, minServers int) (
  function getStringValue (line 112) | func getStringValue(ptr *string) string {

FILE: internal/provider/protonvpn/updater/updater.go
  type Updater (line 9) | type Updater struct
  function New (line 16) | func New(client *http.Client, warner common.Warner, email, password stri...

FILE: internal/provider/protonvpn/updater/version.go
  function getMostRecentStableTag (line 18) | func getMostRecentStableTag(ctx context.Context, client *http.Client) (v...

FILE: internal/provider/provider.go
  type Provider (line 11) | type Provider interface

FILE: internal/provider/providers.go
  type Providers (line 39) | type Providers struct
    method Get (line 100) | func (p *Providers) Get(providerName string) (provider Provider) { //n...
  type Storage (line 43) | type Storage interface
  type Extractor (line 48) | type Extractor interface
  function NewProviders (line 53) | func NewProviders(storage Storage, timeNow func() time.Time,

FILE: internal/provider/purevpn/connection.go
  method GetConnection (line 9) | func (p *Provider) GetConnection(selection settings.ServerSelection, ipv...

FILE: internal/provider/purevpn/openvpnconf.go
  method OpenVPNConfig (line 10) | func (p *Provider) OpenVPNConfig(connection models.Connection,

FILE: internal/provider/purevpn/provider.go
  type Provider (line 11) | type Provider struct
    method Name (line 28) | func (p *Provider) Name() string {
  function New (line 17) | func New(storage common.Storage, randSource rand.Source,

FILE: internal/provider/purevpn/updater/compare.go
  function comparePlaceNames (line 14) | func comparePlaceNames(a, b string) bool {
  function normalize (line 21) | func normalize(s string) string {
  function levenshteinDistance (line 32) | func levenshteinDistance(a, b string) int {

FILE: internal/provider/purevpn/updater/compare_test.go
  function Test_comparePlaceNames (line 9) | func Test_comparePlaceNames(t *testing.T) {

FILE: internal/provider/purevpn/updater/hosttoserver.go
  type hostToServer (line 10) | type hostToServer
    method add (line 12) | func (hts hostToServer) add(host string, tcp, udp bool) {
    method toHostsSlice (line 27) | func (hts hostToServer) toHostsSlice() (hosts []string) {
    method adaptWithIPs (line 35) | func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]netip.Addr) {
    method toServersSlice (line 48) | func (hts hostToServer) toServersSlice() (servers []models.Server) {

FILE: internal/provider/purevpn/updater/parse.go
  function parseHostname (line 35) | func parseHostname(hostname string) (country, city string, warnings []st...

FILE: internal/provider/purevpn/updater/resolve.go
  function parallelResolverSettings (line 9) | func parallelResolverSettings(hosts []string) (settings resolver.Paralle...

FILE: internal/provider/purevpn/updater/servers.go
  method FetchServers (line 16) | func (u *Updater) FetchServers(ctx context.Context, minServers int) (

FILE: internal/provider/purevpn/updater/updater.go
  type Updater (line 7) | type Updater struct
  function New (line 14) | func New(ipFetcher common.IPFetcher, unzipper common.Unzipper,

FILE: internal/provider/slickvpn/connection.go
  method GetConnection (line 9) | func (p *Provider) GetConnection(selection settings.ServerSelection, ipv...

FILE: internal/provider/slickvpn/openvpnconf.go
  method OpenVPNConfig (line 10) | func (p *Provider) OpenVPNConfig(connection models.Connection,

FILE: internal/provider/slickvpn/provider.go
  type Provider (line 12) | type Provider struct
    method Name (line 29) | func (p *Provider) Name() string {
  function New (line 18) | func New(storage common.Storage, randSource rand.Source,

FILE: internal/provider/slickvpn/updater/resolve.go
  function parallelResolverSettings (line 9) | func parallelResolverSettings(hosts []string) (settings resolver.Paralle...

FILE: internal/provider/slickvpn/updater/servers.go
  method FetchServers (line 13) | func (u *Updater) FetchServers(ctx context.Context, minServers int) (

FILE: internal/provider/slickvpn/updater/updater.go
  type Updater (line 9) | type Updater struct
  function New (line 15) | func New(client *http.Client, warner common.Warner,

FILE: internal/provider/surfshark/connection.go
  method GetConnection (line 9) | func (p *Provider) GetConnection(selection settings.ServerSelection, ipv...

FILE: internal/provider/surfshark/openvpnconf.go
  method OpenVPNConfig (line 10) | func (p *Provider) OpenVPNConfig(connection models.Connection,

FILE: internal/provider/surfshark/provider.go
  type Provider (line 12) | type Provider struct
    method Name (line 29) | func (p *Provider) Name() string {
  function New (line 18) | func New(storage common.Storage, randSource rand.Source,

FILE: internal/provider/surfshark/servers/locationdata.go
  type ServerLocation (line 5) | type ServerLocation struct
  function LocationData (line 15) | func LocationData() (data []ServerLocation) {

FILE: internal/provider/surfshark/updater/api.go
  function addServersFromAPI (line 13) | func addServersFromAPI(ctx context.Context, client *http.Client,
  type serverData (line 43) | type serverData struct
  function fetchAPI (line 51) | func fetchAPI(ctx context.Context, client *http.Client) (

FILE: internal/provider/surfshark/updater/api_test.go
  type httpExchange (line 17) | type httpExchange struct
  function Test_addServersFromAPI (line 23) | func Test_addServersFromAPI(t *testing.T) {
  function Test_fetchAPI (line 129) | func Test_fetchAPI(t *testing.T) {

FILE: internal/provider/surfshark/updater/hosttoserver.go
  type hostToServers (line 10) | type hostToServers
    method addOpenVPN (line 12) | func (hts hostToServers) addOpenVPN(host, region, country, city,
    method addWireguard (line 46) | func (hts hostToServers) addWireguard(host, region, country, city, ret...
    method toHostsSlice (line 71) | func (hts hostToServers) toHostsSlice() (hosts []string) {
    method adaptWithIPs (line 80) | func (hts hostToServers) adaptWithIPs(hostToIPs map[string][]netip.Add...
    method toServersSlice (line 95) | func (hts hostToServers) toServersSlice() (servers []models.Server) {

FILE: internal/provider/surfshark/updater/location.go
  function getHostInformation (line 12) | func getHostInformation(host string, hostnameToLocation map[string]serve...
  function hostToLocation (line 23) | func hostToLocation(locationData []servers.ServerLocation) (

FILE: internal/provider/surfshark/updater/remaining.go
  function getRemainingServers (line 8) | func getRemainingServers(hts hostToServers) {

FILE: internal/provider/surfshark/updater/resolve.go
  function parallelResolverSettings (line 9) | func parallelResolverSettings(hosts []string) (settings resolver.Paralle...

FILE: internal/provider/surfshark/updater/roundtrip_test.go
  type roundTripFunc (line 5) | type roundTripFunc
    method RoundTrip (line 7) | func (f roundTripFunc) RoundTrip(r *http.Request) (*http.Response, err...

FILE: internal/provider/surfshark/updater/servers.go
  method FetchServers (line 12) | func (u *Updater) FetchServers(ctx context.Context, minServers int) (

FILE: internal/provider/surfshark/updater/updater.go
  type Updater (line 9) | type Updater struct
  function New (line 16) | func New(client *http.Client, unzipper common.Unzipper,

FILE: internal/provider/surfshark/updater/zip.go
  function addOpenVPNServersFromZip (line 12) | func addOpenVPNServersFromZip(ctx context.Context,

FILE: internal/provider/torguard/connection.go
  method GetConnection (line 9) | func (p *Provider) GetConnection(selection settings.ServerSelection, ipv...

FILE: internal/provider/torguard/openvpnconf.go
  method OpenVPNConfig (line 10) | func (p *Provider) OpenVPNConfig(connection models.Connection,

FILE: internal/provider/torguard/provider.go
  type Provider (line 11) | type Provider struct
    method Name (line 28) | func (p *Provider) Name() string {
  function New (line 17) | func New(storage common.Storage, randSource rand.Source,

FILE: internal/provider/torguard/updater/filename.go
  function parseFilename (line 9) | func parseFilename(fileName string, titleCaser cases.Caser) (country, ci...

FILE: internal/provider/torguard/updater/hosttoserver.go
  type hostToServer (line 10) | type hostToServer
    method add (line 12) | func (hts hostToServer) add(host, country, city string,
    method toHostsSlice (line 35) | func (hts hostToServer) toHostsSlice() (hosts []string) {
    method adaptWithIPs (line 43) | func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]netip.Addr) {
    method toServersSlice (line 56) | func (hts hostToServer) toServersSlice() (servers []models.Server) {

FILE: internal/provider/torguard/updater/resolve.go
  function parallelResolverSettings (line 9) | func parallelResolverSettings(hosts []string) (settings resolver.Paralle...

FILE: internal/provider/torguard/updater/servers.go
  method FetchServers (line 16) | func (u *Updater) FetchServers(ctx context.Context, minServers int) (
  function addServerFromOvpn (line 78) | func addServerFromOvpn(fileName string, content []byte,
  method warnWarnings (line 110) | func (u *Updater) warnWarnings(warnings []string) {

FILE: internal/provider/torguard/updater/updater.go
  type Updater (line 7) | type Updater struct
  function New (line 13) | func New(unzipper common.Unzipper, warner common.Warner,

FILE: internal/provider/utils/cipher.go
  function CipherLines (line 7) | func CipherLines(ciphers []string) (lines []string) {

FILE: internal/provider/utils/cipher_test.go
  function Test_CipherLines (line 9) | func Test_CipherLines(t *testing.T) {

FILE: internal/provider/utils/connection.go
  type ConnectionDefaults (line 12) | type ConnectionDefaults struct
  function NewConnectionDefaults (line 18) | func NewConnectionDefaults(openvpnTCPPort, openvpnUDPPort,
  type Storage (line 28) | type Storage interface
  function GetConnection (line 33) | func GetConnection(provider string,

FILE: internal/provider/utils/connection_test.go
  function Test_GetConnection (line 19) | func Test_GetConnection(t *testing.T) {

FILE: internal/provider/utils/filtering.go
  function filterServers (line 11) | func filterServers(servers []models.Server,
  function filterServer (line 25) | func filterServer(server models.Server,
  function filterByPossibilities (line 107) | func filterByPossibilities[T string | uint16](value T, possibilities []T...
  function filterAnyByPossibilities (line 119) | func filterAnyByPossibilities(values, possibilities []string) (filtered ...

FILE: internal/provider/utils/filtering_test.go
  function Test_FilterServers (line 14) | func Test_FilterServers(t *testing.T) {
  function Test_filterByPossibilities (line 287) | func Test_filterByPossibilities(t *testing.T) {

FILE: internal/provider/utils/logger.go
  type Logger (line 3) | type Logger interface

FILE: internal/provider/utils/nofetcher.go
  type NoFetcher (line 11) | type NoFetcher struct
    method FetchServers (line 23) | func (n *NoFetcher) FetchServers(context.Context, int) (
  function NewNoFetcher (line 15) | func NewNoFetcher(providerName string) *NoFetcher {

FILE: internal/provider/utils/openvpn.go
  type OpenVPNProviderSettings (line 14) | type OpenVPNProviderSettings struct
  function OpenVPNConfig (line 53) | func OpenVPNConfig(provider OpenVPNProviderSettings,
  type openvpnConfigLines (line 231) | type openvpnConfigLines
    method add (line 233) | func (o *openvpnConfigLines) add(words ...string) {
    method addLines (line 237) | func (o *openvpnConfigLines) addLines(lines []string) {
  function defaultString (line 243) | func defaultString(value, defaultValue string) string {
  function defaultUint16 (line 250) | func defaultUint16(value, defaultValue uint16) uint16 {
  function defaultStringSlice (line 257) | func defaultStringSlice(value, defaultValue []string) (
  function WrapOpenvpnCA (line 270) | func WrapOpenvpnCA(certificate string) (lines []string) {
  function WrapOpenvpnCert (line 280) | func WrapOpenvpnCert(clientCertificate string) (lines []string) {
  function WrapOpenvpnCRLVerify (line 290) | func WrapOpenvpnCRLVerify(x509CRL string) (lines []string) {
  function WrapOpenvpnKey (line 300) | func WrapOpenvpnKey(clientKey string) (lines []string) {
  function WrapOpenvpnEncryptedKey (line 310) | func WrapOpenvpnEncryptedKey(encryptedKey string) (lines []string) {
  function WrapOpenvpnRSAKey (line 320) | func WrapOpenvpnRSAKey(rsaPrivateKey string) (lines []string) {
  function WrapOpenvpnTLSAuth (line 330) | func WrapOpenvpnTLSAuth(staticKeyV1 string) (lines []string) {
  function WrapOpenvpnTLSCrypt (line 340) | func WrapOpenvpnTLSCrypt(staticKeyV1 string) (lines []string) {

FILE: internal/provider/utils/pick.go
  function pickConnection (line 21) | func pickConnection(connections []models.Connection,
  function pickRandomConnection (line 53) | func pickRandomConnection(connections []models.Connection,
  function getTargetIPConnection (line 61) | func getTargetIPConnection(connections []models.Connection,

FILE: internal/provider/utils/pick_test.go
  function Test_pickRandomConnection (line 11) | func Test_pickRandomConnection(t *testing.T) {

FILE: internal/provider/utils/port.go
  function getPort (line 11) | func getPort(selection settings.ServerSelection,
  function checkDefined (line 37) | func checkDefined(portName string, port uint16) {

FILE: internal/provider/utils/port_test.go
  function boolPtr (line 12) | func boolPtr(b bool) *bool       { return &b }
  function uint16Ptr (line 13) | func uint16Ptr(n uint16) *uint16 { return &n }
  function Test_GetPort (line 15) | func Test_GetPort(t *testing.T) {

FILE: internal/provider/utils/portforward.go
  type PortForwardObjects (line 10) | type PortForwardObjects struct
  type Routing (line 30) | type Routing interface

FILE: internal/provider/utils/protocol.go
  function getProtocol (line 9) | func getProtocol(selection settings.ServerSelection) (protocol string) {
  function filterByProtocol (line 16) | func filterByProtocol(selection settings.ServerSelection,

FILE: internal/provider/utils/protocol_test.go
  function Test_getProtocol (line 12) | func Test_getProtocol(t *testing.T) {
  function Test_filterByProtocol (line 59) | func Test_filterByProtocol(t *testing.T) {

FILE: internal/provider/vpnsecure/connection.go
  method GetConnection (line 9) | func (p *Provider) GetConnection(selection settings.ServerSelection, ipv...

FILE: internal/provider/vpnsecure/openvpnconf.go
  method OpenVPNConfig (line 10) | func (p *Provider) OpenVPNConfig(connection models.Connection,

FILE: internal/provider/vpnsecure/provider.go
  type Provider (line 12) | type Provider struct
    method Name (line 29) | func (p *Provider) Name() string {
  function New (line 18) | func New(storage common.Storage, randSource rand.Source,

FILE: internal/provider/vpnsecure/updater/helpers_test.go
  function parseTestHTML (line 12) | func parseTestHTML(t *testing.T, htmlString string) *html.Node {
  function parseTestDataIndexHTML (line 19) | func parseTestDataIndexHTML(t *testing.T) *html.Node {

FILE: internal/provider/vpnsecure/updater/hosttoserver.go
  type hostToServer (line 9) | type hostToServer
    method toHostsSlice (line 11) | func (hts hostToServer) toHostsSlice() (hosts []string) {
    method adaptWithIPs (line 19) | func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]netip.Addr) {
    method toServersSlice (line 32) | func (hts hostToServer) toServersSlice() (servers []models.Server) {

FILE: internal/provider/vpnsecure/updater/resolve.go
  function parallelResolverSettings (line 9) | func parallelResolverSettings(hosts []string) (settings resolver.Paralle...

FILE: internal/provider/vpnsecure/updater/servers.go
  method FetchServers (line 13) | func (u *Updater) FetchServers(ctx context.Context, minServers int) (

FILE: internal/provider/vpnsecure/updater/updater.go
  type Updater (line 9) | type Updater struct
  function New (line 15) | func New(client *http.Client, warner common.Warner,

FILE: internal/provider/vpnsecure/updater/website.go
  function fetchServers (line 16) | func fetchServers(ctx context.Context, client *http.Client,
  constant divString (line 38) | divString = "div"
  function parseHTML (line 40) | func parseHTML(rootNode *html.Node) (servers []models.Server,
  function parseHTMLGridItem (line 88) | func parseHTMLGridItem(gridItem *html.Node) (
  function naToEmpty (line 143) | func naToEmpty(current string) (output string) {
  function findCountry (line 150) | func findCountry(countryNode *html.Node) (country string) {
  function findServersDiv (line 165) | func findServersDiv(rootNode *html.Node) (serversDiv *html.Node) {
  function findHost (line 174) | func findHost(gridItemDT *html.Node) (host string) {
  function matchText (line 179) | func matchText(node *html.Node) (match bool) {
  function findStatus (line 187) | func findStatus(gridItemDT *html.Node) (status string) {
  function matchServersDiv (line 192) | func matchServersDiv(node *html.Node) (match bool) {
  function matchLocationsListDiv (line 197) | func matchLocationsListDiv(node *html.Node) (match bool) {
  function matchGridDiv (line 202) | func matchGridDiv(node *html.Node) (match bool) {
  function matchGridItem (line 207) | func matchGridItem(node *html.Node) (match bool) {
  function matchDT (line 212) | func matchDT(node *html.Node) (match bool) {
  function matchDD (line 216) | func matchDD(node *html.Node) (match bool) {
  function matchStatusSpan (line 220) | func matchStatusSpan(node *html.Node) (match bool) {
  function findSpanStrong (line 224) | func findSpanStrong(gridItemDD *html.Node, spanData string) (

FILE: internal/provider/vpnsecure/updater/website_test.go
  type roundTripFunc (line 17) | type roundTripFunc
    method RoundTrip (line 19) | func (f roundTripFunc) RoundTrip(r *http.Request) (*http.Response, err...
  function Test_fetchServers (line 23) | func Test_fetchServers(t *testing.T) {
  function Test_parseHTML (line 117) | func Test_parseHTML(t *testing.T) {

FILE: internal/provider/vpnunlimited/connection.go
  method GetConnection (line 9) | func (p *Provider) GetConnection(selection settings.ServerSelection, ipv...

FILE: internal/provider/vpnunlimited/openvpnconf.go
  method OpenVPNConfig (line 10) | func (p *Provider) OpenVPNConfig(connection models.Connection,

FILE: internal/provider/vpnunlimited/provider.go
  type Provider (line 11) | type Provider struct
    method Name (line 28) | func (p *Provider) Name() string {
  function New (line 17) | func New(storage common.Storage, randSource rand.Source,

FILE: internal/provider/vpnunlimited/updater/constants.go
  function getHostToServer (line 11) | func getHostToServer() (hts hostToServer, warnings []string) {

FILE: internal/provider/vpnunlimited/updater/hosttoserver.go
  type hostToServer (line 9) | type hostToServer
    method toHostsSlice (line 11) | func (hts hostToServer) toHostsSlice() (hosts []string) {
    method adaptWithIPs (line 19) | func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]netip.Addr) {
    method toServersSlice (line 32) | func (hts hostToServer) toServersSlice() (servers []models.Server) {

FILE: internal/provider/vpnunlimited/updater/resolve.go
  function parallelResolverSettings (line 9) | func parallelResolverSettings(hosts []string) (settings resolver.Paralle...

FILE: internal/provider/vpnunlimited/updater/servers.go
  method FetchServers (line 12) | func (u *Updater) FetchServers(ctx context.Context, minServers int) (

FILE: internal/provider/vpnunlimited/updater/updater.go
  type Updater (line 7) | type Updater struct
  function New (line 13) | func New(unzipper common.Unzipper, warner common.Warner,

FILE: internal/provider/vyprvpn/connection.go
  method GetConnection (line 9) | func (p *Provider) GetConnection(selection settings.ServerSelection, ipv...

FILE: internal/provider/vyprvpn/openvpnconf.go
  method OpenVPNConfig (line 10) | func (p *Provider) OpenVPNConfig(connection models.Connection,

FILE: internal/provider/vyprvpn/provider.go
  type Provider (line 11) | type Provider struct
    method Name (line 28) | func (p *Provider) Name() string {
  function New (line 17) | func New(storage common.Storage, randSource rand.Source,

FILE: internal/provider/vyprvpn/updater/filename.go
  function parseFilename (line 11) | func parseFilename(fileName string) (

FILE: internal/provider/vyprvpn/updater/hosttoserver.go
  type hostToServer (line 10) | type hostToServer
    method add (line 12) | func (hts hostToServer) add(host, region string, tcp, udp bool) {
    method toHostsSlice (line 28) | func (hts hostToServer) toHostsSlice() (hosts []string) {
    method adaptWithIPs (line 36) | func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]netip.Addr) {
    method toServersSlice (line 49) | func (hts hostToServer) toServersSlice() (servers []models.Server) {

FILE: internal/provider/vyprvpn/updater/resolve.go
  function parallelResolverSettings (line 9) | func parallelResolverSettings(hosts []string) (settings resolver.Paralle...

FILE: internal/provider/vyprvpn/updater/servers.go
  method FetchServers (line 14) | func (u *Updater) FetchServers(ctx context.Context, minServers int) (

FILE: internal/provider/vyprvpn/updater/updater.go
  type Updater (line 7) | type Updater struct
  function New (line 13) | func New(unzipper common.Unzipper, warner common.Warner,

FILE: internal/provider/windscribe/connection.go
  method GetConnection (line 9) | func (p *Provider) GetConnection(selection settings.ServerSelection, ipv...

FILE: internal/provider/windscribe/connection_test.go
  function Test_Provider_GetConnection (line 20) | func Test_Provider_GetConnection(t *testing.T) {

FILE: internal/provider/windscribe/openvpnconf.go
  method OpenVPNConfig (line 10) | func (p *Provider) OpenVPNConfig(connection models.Connection,

FILE: internal/provider/windscribe/provider.go
  type Provider (line 12) | type Provider struct
    method Name (line 28) | func (p *Provider) Name() string {
  function New (line 18) | func New(storage common.Storage, randSource rand.Source,

FILE: internal/provider/windscribe/updater/api.go
  type apiData (line 16) | type apiData struct
  type regionData (line 20) | type regionData struct
  type groupData (line 25) | type groupData struct
  type serverData (line 32) | type serverData struct
  function fetchAPI (line 39) | func fetchAPI(ctx context.Context, client *http.Client) (

FILE: internal/provider/windscribe/updater/servers.go
  method FetchServers (line 17) | func (u *Updater) FetchServers(ctx context.Context, minServers int) (

FILE: internal/provider/windscribe/updater/updater.go
  type Updater (line 9) | type Updater struct
  function New (line 14) | func New(client *http.Client, warner common.Warner) *Updater {

FILE: internal/publicip/api/api.go
  type Provider (line 14) | type Provider
  constant Cloudflare (line 17) | Cloudflare  Provider = "cloudflare"
  constant IfConfigCo (line 18) | IfConfigCo  Provider = "ifconfigco"
  constant IPInfo (line 19) | IPInfo      Provider = "ipinfo"
  constant IP2Location (line 20) | IP2Location Provider = "ip2location"
  constant echoipPrefix (line 23) | echoipPrefix = "echoip#"
  type NameToken (line 25) | type NameToken struct
  function New (line 30) | func New(nameTokenPairs []NameToken, client *http.Client) (
  function ParseProvider (line 63) | func ParseProvider(s string) (provider Provider, err error) {
  function checkCustomURL (line 106) | func checkCustomURL(s, prefix string, regex *regexp.Regexp) (match bool,...
  function orStrings (line 124) | func orStrings(strings []string) (result string) {
  function joinStrings (line 128) | func joinStrings(strings []string, lastJoin string) (result string) {

FILE: internal/publicip/api/api_test.go
  function Test_ParseProvider (line 9) | func Test_ParseProvider(t *testing.T) {

FILE: internal/publicip/api/cloudflare.go
  type cloudflare (line 16) | type cloudflare struct
    method String (line 26) | func (c *cloudflare) String() string {
    method CanFetchAnyIP (line 30) | func (c *cloudflare) CanFetchAnyIP() bool {
    method Token (line 34) | func (c *cloudflare) Token() (token string) {
    method FetchInfo (line 41) | func (c *cloudflare) FetchInfo(ctx context.Context, ip netip.Addr) (
  function newCloudflare (line 20) | func newCloudflare(client *http.Client) *cloudflare {

FILE: internal/publicip/api/echoip.go
  type echoip (line 15) | type echoip struct
    method String (line 27) | func (e *echoip) String() string {
    method CanFetchAnyIP (line 34) | func (e *echoip) CanFetchAnyIP() bool {
    method Token (line 38) | func (e *echoip) Token() string {
    method FetchInfo (line 45) | func (e *echoip) FetchInfo(ctx context.Context, ip netip.Addr) (
  function newEchoip (line 20) | func newEchoip(client *http.Client, url string) *echoip {

FILE: internal/publicip/api/interfaces.go
  type Fetcher (line 10) | type Fetcher interface
  type InfoFetcher (line 17) | type InfoFetcher interface
  type Warner (line 22) | type Warner interface

FILE: internal/publicip/api/ip2location.go
  type ip2Location (line 16) | type ip2Location struct
    method String (line 30) | func (i *ip2Location) String() string {
    method CanFetchAnyIP (line 34) | func (i *ip2Location) CanFetchAnyIP() bool {
    method Token (line 38) | func (i *ip2Location) Token() string {
    method FetchInfo (line 45) | func (i *ip2Location) FetchInfo(ctx context.Context, ip netip.Addr) (
  function newIP2Location (line 22) | func newIP2Location(client *http.Client, token string) *ip2Location {

FILE: internal/publicip/api/ipinfo.go
  type ipInfo (line 16) | type ipInfo struct
    method String (line 28) | func (i *ipInfo) String() string {
    method CanFetchAnyIP (line 32) | func (i *ipInfo) CanFetchAnyIP() bool {
    method Token (line 36) | func (i *ipInfo) Token() string {
    method FetchInfo (line 43) | func (i *ipInfo) FetchInfo(ctx context.Context, ip netip.Addr) (
  function newIPInfo (line 21) | func newIPInfo(client *http.Client, token string) *ipInfo {

FILE: internal/publicip/api/multi.go
  function FetchMultiInfo (line 16) | func FetchMultiInfo(ctx context.Context, fetcher InfoFetcher, ips []neti...

FILE: internal/publicip/api/resilient.go
  type ResilientFetcher (line 24) | type ResilientFetcher struct
    method setBanned (line 43) | func (r *ResilientFetcher) setBanned(fetcher Fetcher) {
    method isBanned (line 49) | func (r *ResilientFetcher) isBanned(fetcher Fetcher) (banned bool) {
    method String (line 66) | func (r *ResilientFetcher) String() string {
    method Token (line 82) | func (r *ResilientFetcher) Token() string {
    method CanFetchAnyIP (line 88) | func (r *ResilientFetcher) CanFetchAnyIP() bool {
    method FetchInfo (line 106) | func (r *ResilientFetcher) FetchInfo(ctx context.Context, ip netip.Add...
    method UpdateFetchers (line 258) | func (r *ResilientFetcher) UpdateFetchers(fetchers []Fetcher) {
  function NewResilient (line 34) | func NewResilient(fetchers []Fetcher, logger Warner) *ResilientFetcher {
  function getMostPopularResult (line 170) | func getMostPopularResult(results []models.PublicIP) models.PublicIP {
  function filterInPlace (line 202) | func filterInPlace(results []models.PublicIP, indices []int) []models.Pu...
  function getMostPopularString (line 211) | func getMostPopularString(values []string) (winnerIdx int, memberIdxs []...
  function normalize (line 285) | func normalize(s string) string {
  function levenshteinDistance (line 300) | func levenshteinDistance(a, b string) int {

FILE: internal/publicip/api/resilient_test.go
  function Test_GetMostPopularResult (line 10) | func Test_GetMostPopularResult(t *testing.T) {

FILE: internal/publicip/data.go
  method GetData (line 7) | func (l *Loop) GetData() (data models.PublicIP) {
  method ClearData (line 15) | func (l *Loop) ClearData() (err error) {

FILE: internal/publicip/fs.go
  function persistPublicIP (line 8) | func persistPublicIP(path string, content string, puid, pgid int) error {

FILE: internal/publicip/interfaces.go
  type Logger (line 3) | type Logger interface

FILE: internal/publicip/loop.go
  type Loop (line 16) | type Loop struct
    method String (line 73) | func (l *Loop) String() string {
    method Start (line 77) | func (l *Loop) Start(_ context.Context) (_ <-chan error, err error) {
    method run (line 95) | func (l *Loop) run(runCtx context.Context, runDone chan<- struct{},
    method RunOnce (line 148) | func (l *Loop) RunOnce(ctx context.Context) (err error) {
    method UpdateWith (line 168) | func (l *Loop) UpdateWith(partialUpdate settings.PublicIP) (err error) {
    method Stop (line 183) | func (l *Loop) Stop() (err error) {
    method Fetcher (line 189) | func (l *Loop) Fetcher() (fetcher *api.ResilientFetcher) {
  function NewLoop (line 43) | func NewLoop(settings settings.PublicIP, puid, pgid int,
  function makeNameTokenPairs (line 62) | func makeNameTokenPairs(apis []settings.PublicIPAPI) (nameTokenPairs []a...

FILE: internal/publicip/update.go
  method update (line 12) | func (l *Loop) update(partialUpdate settings.PublicIP) (err error) {

FILE: internal/routing/conversion.go
  function netIPToNetipAddress (line 9) | func netIPToNetipAddress(ip net.IP) (address netip.Addr) {

FILE: internal/routing/conversion_test.go
  function Test_netIPToNetipAddress (line 11) | func Test_netIPToNetipAddress(t *testing.T) {

FILE: internal/routing/default.go
  type DefaultRoute (line 13) | type DefaultRoute struct
    method String (line 20) | func (d DefaultRoute) String() string {
  method DefaultRoutes (line 25) | func (r *Routing) DefaultRoutes() (defaultRoutes []DefaultRoute, err err...
  function DefaultRoutesInterfaces (line 69) | func DefaultRoutesInterfaces(defaultRoutes []DefaultRoute) (interfaces [...

FILE: internal/routing/enable.go
  method Setup (line 7) | func (r *Routing) Setup() (err error) {
  method TearDown (line 39) | func (r *Routing) TearDown() error {

FILE: internal/routing/inbound.go
  constant inboundTable (line 11) | inboundTable    uint32 = 200
  constant inboundPriority (line 12) | inboundPriority uint32 = 100
  method routeInboundFromDefault (line 15) | func (r *Routing) routeInboundFromDefault(defaultRoutes []DefaultRoute) ...
  method unrouteInboundFromDefault (line 39) | func (r *Routing) unrouteInboundFromDefault(defaultRoutes []DefaultRoute...
  method addRuleInboundFromDefault (line 63) | func (r *Routing) addRuleInboundFromDefault(table uint32, defaultRoutes ...
  method delRuleInboundFromDefault (line 81) | func (r *Routing) delRuleInboundFromDefault(table uint32, defaultRoutes ...

FILE: internal/routing/ip.go
  function ipIsPrivate (line 12) | func ipIsPrivate(ip netip.Addr) bool {
  function ipMatchesFamily (line 19) | func ipMatchesFamily(ip netip.Addr, family uint8) bool {
  method AssignedIP (line 24) | func (r *Routing) AssignedIP(interfaceName string, family uint8) (ip net...

FILE: internal/routing/ip_test.go
  function Test_ipIsPrivate (line 11) | func Test_ipIsPrivate(t *testing.T) {

FILE: internal/routing/local.go
  type LocalNetwork (line 17) | type LocalNetwork struct
  method LocalNetworks (line 23) | func (r *Routing) LocalNetworks() (localNetworks []LocalNetwork, err err...
  method AddLocalRules (line 91) | func (r *Routing) AddLocalRules(subnets []LocalNetwork) (err error) {

FILE: internal/routing/logger.go
  type Logger (line 3) | type Logger interface

FILE: internal/routing/mocks_test.go
  type MockNetLinker (line 16) | type MockNetLinker struct
    method EXPECT (line 34) | func (m *MockNetLinker) EXPECT() *MockNetLinkerMockRecorder {
    method AddrList (line 39) | func (m *MockNetLinker) AddrList(arg0 uint32, arg1 byte) ([]netip.Pref...
    method AddrReplace (line 54) | func (m *MockNetLinker) AddrReplace(arg0 uint32, arg1 netip.Prefix) er...
    method LinkAdd (line 68) | func (m *MockNetLinker) LinkAdd(arg0 netlink.Link) (uint32, error) {
    method LinkByIndex (line 83) | func (m *MockNetLinker) LinkByIndex(arg0 uint32) (netlink.Link, error) {
    method LinkByName (line 98) | func (m *MockNetLinker) LinkByName(arg0 string) (netlink.Link, error) {
    method LinkDel (line 113) | func (m *MockNetLinker) LinkDel(arg0 uint32) error {
    method LinkList (line 127) | func (m *MockNetLinker) LinkList() ([]netlink.Link, error) {
    method LinkSetDown (line 142) | func (m *MockNetLinker) LinkSetDown(arg0 uint32) error {
    method LinkSetUp (line 156) | func (m *MockNetLinker) LinkSetUp(arg0 uint32) error {
    method RouteAdd (line 170) | func (m *MockNetLinker) RouteAdd(arg0 netlink.Route) error {
    method RouteDel (line 184) | func (m *MockNetLinker) RouteDel(arg0 netlink.Route) error {
    method RouteList (line 198) | func (m *MockNetLinker) RouteList(arg0 byte) ([]netlink.Route, error) {
    method RouteReplace (line 213) | func (m *MockNetLinker) RouteReplace(arg0 netlink.Route) error {
    method RuleAdd (line 227) | func (m *MockNetLinker) RuleAdd(arg0 netlink.Rule) error {
    method RuleDel (line 241) | func (m *MockNetLinker) RuleDel(arg0 netlink.Rule) error {
    method RuleList (line 255) | func (m *MockNetLinker) RuleList(arg0 byte) ([]netlink.Rule, error) {
  type MockNetLinkerMockRecorder (line 22) | type MockNetLinkerMockRecorder struct
    method AddrList (line 48) | func (mr *MockNetLinkerMockRecorder) AddrList(arg0, arg1 interface{}) ...
    method AddrReplace (line 62) | func (mr *MockNetLinkerMockRecorder) AddrReplace(arg0, arg1 interface{...
    method LinkAdd (line 77) | func (mr *MockNetLinkerMockRecorder) LinkAdd(arg0 interface{}) *gomock...
    method LinkByIndex (line 92) | func (mr *MockNetLinkerMockRecorder) LinkByIndex(arg0 interface{}) *go...
    method LinkByName (line 107) | func (mr *MockNetLinkerMockRecorder) LinkByName(arg0 interface{}) *gom...
    method LinkDel (line 121) | func (mr *MockNetLinkerMockRecorder) LinkDel(arg0 interface{}) *gomock...
    method LinkList (line 136) | func (mr *MockNetLinkerMockRecorder) LinkList() *gomock.Call {
    method LinkSetDown (line 150) | func (mr *MockNetLinkerMockRecorder) LinkSetDown(arg0 interface{}) *go...
    method LinkSetUp (line 164) | func (mr *MockNetLinkerMockRecorder) LinkSetUp(arg0 interface{}) *gomo...
    method RouteAdd (line 178) | func (mr *MockNetLinkerMockRecorder) RouteAdd(arg0 interface{}) *gomoc...
    method RouteDel (line 192) | func (mr *MockNetLinkerMockRecorder) RouteDel(arg0 interface{}) *gomoc...
    method RouteList (line 207) | func (mr *MockNetLinkerMockRecorder) RouteList(arg0 interface{}) *gomo...
    method RouteReplace (line 221) | func (mr *MockNetLinkerMockRecorder) RouteReplace(arg0 interface{}) *g...
    method RuleAdd (line 235) | func (mr *MockNetLinkerMockRecorder) RuleAdd(arg0 interface{}) *gomock...
    method RuleDel (line 249) | func (mr *MockNetLinkerMockRecorder) RuleDel(arg0 interface{}) *gomock...
    method RuleList (line 264) | func (mr *MockNetLinkerMockRecorder) RuleList(arg0 interface{}) *gomoc...
  function NewMockNetLinker (line 27) | func NewMockNetLinker(ctrl *gomock.Controller) *MockNetLinker {

FILE: internal/routing/outbound.go
  constant outboundTable (line 12) | outboundTable    uint32 = 199
  constant outboundPriority (line 13) | outboundPriority uint32 = 99
  method SetOutboundRoutes (line 16) | func (r *Routing) SetOutboundRoutes(outboundSubnets []netip.Prefix) 
Copy disabled (too large) Download .json
Condensed preview — 796 files, each showing path, character count, and a content snippet. Download the .json file for the full structured content (11,001K chars).
[
  {
    "path": ".devcontainer/.dockerignore",
    "chars": 53,
    "preview": ".dockerignore\ndevcontainer.json\nDockerfile\nREADME.md\n"
  },
  {
    "path": ".devcontainer/Dockerfile",
    "chars": 105,
    "preview": "FROM ghcr.io/qdm12/godevcontainer:v0.21-alpine\nRUN apk add wireguard-tools htop openssl tcpdump iptables\n"
  },
  {
    "path": ".devcontainer/README.md",
    "chars": 1784,
    "preview": "# Development container\n\nDevelopment container that can be used with VSCode.\n\nIt works on Linux, Windows (WSL2) and OSX."
  },
  {
    "path": ".devcontainer/devcontainer.json",
    "chars": 4016,
    "preview": "{\n    \"name\": \"gluetun-dev\",\n    // User defined settings\n    \"containerEnv\": {\n        \"TZ\": \"\"\n    },\n    // Fixed set"
  },
  {
    "path": ".dockerignore",
    "chars": 89,
    "preview": ".devcontainer\n.git\n.github\ndoc\ndocker-compose.yml\nDockerfile\nLICENSE\nREADME.md\ntitle.svg\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "chars": 6,
    "preview": "@qdm12"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "chars": 903,
    "preview": "# Contributing\n\nContributions are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-u"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 16,
    "preview": "github: [qdm12]\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug.yml",
    "chars": 3135,
    "preview": "name: Bug\ndescription: Report a bug\ntitle: \"Bug: \"\nlabels: [\":bug: bug\"]\nbody:\n  - type: markdown\n    attributes:\n      "
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 490,
    "preview": "blank_issues_enabled: false\ncontact_links:\n  - name: Report a Wiki issue\n    url: https://github.com/qdm12/gluetun-wiki/"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "chars": 580,
    "preview": "name: Feature request\ndescription: Suggest a feature to add to Gluetun\ntitle: \"Feature request: \"\nlabels: [\":bulb: featu"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/provider.md",
    "chars": 2021,
    "preview": "---\nname: Support a VPN provider\nabout: Suggest a VPN provider to be supported\ntitle: 'VPN provider support: NAME OF THE"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 331,
    "preview": "version: 2\nupdates:\n  # Maintain dependencies for GitHub Actions\n  - package-ecosystem: \"github-actions\"\n    directory: "
  },
  {
    "path": ".github/labels.yml",
    "chars": 3931,
    "preview": "- name: \"Status: 🗯️ Waiting for feedback\"\n  color: \"f7d692\"\n- name: \"Status: 🔴 Blocked\"\n  color: \"f7d692\"\n  description:"
  },
  {
    "path": ".github/pull_request_template.md",
    "chars": 509,
    "preview": "# Description\n\n<!-- Please describe the reason for the changes being proposed. -->\n\n# Issue\n\n<!-- Please link to the iss"
  },
  {
    "path": ".github/workflows/ci-skip.yml",
    "chars": 655,
    "preview": "name: No trigger file paths\non:\n  push:\n    branches:\n      - master\n    paths-ignore:\n      - .github/workflows/ci.yml\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 5651,
    "preview": "name: CI\non:\n  release:\n    types:\n      - published\n  push:\n    branches:\n      - master\n    paths:\n      - .github/wor"
  },
  {
    "path": ".github/workflows/closed-issue.yml",
    "chars": 1049,
    "preview": "name: Closed issue\non:\n  issues:\n    types: [closed]\n\njobs:\n  comment:\n    permissions:\n      issues: write\n    runs-on:"
  },
  {
    "path": ".github/workflows/configs/mlc-config.json",
    "chars": 224,
    "preview": "{\n  \"ignorePatterns\": [\n    {\n      \"pattern\": \"^https://console.substack.com/p/console-72$\"\n    }\n  ],\n  \"timeout\": \"20"
  },
  {
    "path": ".github/workflows/labels.yml",
    "chars": 354,
    "preview": "name: labels\non:\n  push:\n    branches: [master]\n    paths:\n      - .github/labels.yml\n      - .github/workflows/labels.y"
  },
  {
    "path": ".github/workflows/markdown-skip.yml",
    "chars": 404,
    "preview": "name: Markdown\non:\n  push:\n    branches:\n      - master\n    paths-ignore:\n      - \"**.md\"\n      - .github/workflows/mark"
  },
  {
    "path": ".github/workflows/markdown.yml",
    "chars": 1208,
    "preview": "name: Markdown\non:\n  push:\n    branches:\n      - master\n    paths:\n      - \"**.md\"\n      - .github/workflows/markdown.ym"
  },
  {
    "path": ".github/workflows/opened-issue.yml",
    "chars": 800,
    "preview": "name: Opened issue\non:\n  issues:\n    types: [opened]\n\njobs:\n  comment:\n    permissions:\n      issues: write\n    runs-on:"
  },
  {
    "path": ".github/workflows/update-servers-list.yml",
    "chars": 2900,
    "preview": "name: Update servers list\non:\n  workflow_dispatch:\n    inputs:\n      provider:\n        description: \"VPN Provider to upd"
  },
  {
    "path": ".gitignore",
    "chars": 22,
    "preview": "scratch.txt\n.DS_Store\n"
  },
  {
    "path": ".golangci.yml",
    "chars": 2790,
    "preview": "version: \"2\"\n\nformatters:\n  enable:\n    - gci\n    - gofumpt\n    - goimports\n  exclusions:\n    generated: lax\n    paths:\n"
  },
  {
    "path": ".markdownlint-cli2.jsonc",
    "chars": 121,
    "preview": "{\n  \"config\": {\n    \"default\": true,\n    \"MD013\": false,\n  },\n  \"ignores\": [\n    \".github/pull_request_template.md\"\n  ]\n"
  },
  {
    "path": ".vscode/extensions.json",
    "chars": 168,
    "preview": "{\n  // This list should be kept to the strict minimum\n  // to develop this project.\n  \"recommendations\": [\n    \"golang.g"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 638,
    "preview": "{\n  // The settings should be kept to the strict minimum\n  // to develop this project.\n  \"files.eol\": \"\\n\",\n  \"editor.fo"
  },
  {
    "path": ".vscode/tasks.json",
    "chars": 1397,
    "preview": "{\n    \"version\": \"2.0.0\",\n    \"tasks\": [\n        {\n            \"label\": \"Update a VPN provider servers data\",\n          "
  },
  {
    "path": "Dockerfile",
    "chars": 10604,
    "preview": "ARG ALPINE_VERSION=3.23\nARG GO_ALPINE_VERSION=3.23\nARG GO_VERSION=1.25\nARG XCPUTRANSLATE_VERSION=v0.9.0\nARG GOLANGCI_LIN"
  },
  {
    "path": "LICENSE",
    "chars": 1069,
    "preview": "MIT License\n\nCopyright (c) 2018 Quentin McGaw\n\nPermission is hereby granted, free of charge, to any person obtaining a c"
  },
  {
    "path": "README.md",
    "chars": 8773,
    "preview": "# Gluetun VPN client\n\n⚠️ This and [gluetun-wiki](https://github.com/qdm12/gluetun-wiki) are the only websites for Gluetu"
  },
  {
    "path": "ci/cmd/main.go",
    "chars": 669,
    "preview": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/signal\"\n\n\t\"github.com/qdm12/gluetun/ci/internal\"\n\t\"github.com/qdm12/"
  },
  {
    "path": "ci/go.mod",
    "chars": 1609,
    "preview": "module github.com/qdm12/gluetun/ci\n\ngo 1.25.0\n\nrequire (\n\tgithub.com/docker/docker v28.5.1+incompatible\n\tgithub.com/open"
  },
  {
    "path": "ci/go.sum",
    "chars": 9899,
    "preview": "github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=\ngithub.c"
  },
  {
    "path": "ci/internal/mullvad.go",
    "chars": 565,
    "preview": "package internal\n\nimport (\n\t\"context\"\n\t\"fmt\"\n)\n\nfunc MullvadTest(ctx context.Context, logger Logger) error {\n\texpectedSe"
  },
  {
    "path": "ci/internal/protonvpn.go",
    "chars": 517,
    "preview": "package internal\n\nimport (\n\t\"context\"\n\t\"fmt\"\n)\n\nfunc ProtonVPNTest(ctx context.Context, logger Logger) error {\n\texpected"
  },
  {
    "path": "ci/internal/secrets.go",
    "chars": 1095,
    "preview": "package internal\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n)\n\ntype Logger interface {\n\tInfo(msg string)\n\tInf"
  },
  {
    "path": "ci/internal/simple.go",
    "chars": 3739,
    "preview": "package internal\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"regexp\"\n\t\"time\"\n\n\t\"github.com/docker/docker/api/types/cont"
  },
  {
    "path": "cmd/gluetun/main.go",
    "chars": 22436,
    "preview": "package main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"net/http\"\n\t\"net/netip\"\n\t\"os\"\n\t\"os/exec\"\n\t\"os/signal\"\n\t\"str"
  },
  {
    "path": "go.mod",
    "chars": 2824,
    "preview": "module github.com/qdm12/gluetun\n\ngo 1.25.0\n\nrequire (\n\tgithub.com/ProtonMail/go-srp v0.0.7\n\tgithub.com/amnezia-vpn/amnez"
  },
  {
    "path": "go.sum",
    "chars": 18829,
    "preview": "github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57/go.mod h1:HecWFHognK8GfRDGnFQbW/LiV7A3MX3gZVs45vk5h8I=\ng"
  },
  {
    "path": "internal/alpine/alpine.go",
    "chars": 425,
    "preview": "package alpine\n\nimport (\n\t\"os/user\"\n)\n\ntype Alpine struct {\n\talpineReleasePath string\n\tpasswdPath        string\n\tlookupI"
  },
  {
    "path": "internal/alpine/users.go",
    "chars": 1168,
    "preview": "package alpine\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"os\"\n\t\"os/user\"\n\t\"strconv\"\n)\n\nvar ErrUserAlreadyExists = errors.New("
  },
  {
    "path": "internal/alpine/version.go",
    "chars": 434,
    "preview": "package alpine\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n)\n\nfunc (a *Alpine) Version(context.Context) (version string,"
  },
  {
    "path": "internal/amneziawg/constructor.go",
    "chars": 376,
    "preview": "package amneziawg\n\ntype Amneziawg struct {\n\tlogger   Logger\n\tsettings Settings\n\tnetlink  NetLinker\n}\n\nfunc New(settings "
  },
  {
    "path": "internal/amneziawg/constructor_test.go",
    "chars": 2081,
    "preview": "package amneziawg\n\nimport (\n\t\"net/netip\"\n\t\"testing\"\n\n\t\"github.com/qdm12/gluetun/internal/wireguard\"\n\t\"github.com/stretch"
  },
  {
    "path": "internal/amneziawg/helpers_test.go",
    "chars": 60,
    "preview": "package amneziawg\n\nfunc ptrTo[T any](v T) *T {\n\treturn &v\n}\n"
  },
  {
    "path": "internal/amneziawg/log.go",
    "chars": 264,
    "preview": "package amneziawg\n\n//go:generate mockgen -destination=log_mock_test.go -package amneziawg . Logger\n\ntype Logger interfac"
  },
  {
    "path": "internal/amneziawg/log_mock_test.go",
    "chars": 3182,
    "preview": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/qdm12/gluetun/internal/amneziawg (interfaces: Logger)\n\n"
  },
  {
    "path": "internal/amneziawg/netlinker.go",
    "chars": 844,
    "preview": "package amneziawg\n\nimport (\n\t\"net/netip\"\n\n\t\"github.com/qdm12/gluetun/internal/netlink\"\n)\n\n//go:generate mockgen -destina"
  },
  {
    "path": "internal/amneziawg/netlinker_mock_test.go",
    "chars": 6998,
    "preview": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/qdm12/gluetun/internal/amneziawg (interfaces: NetLinker"
  },
  {
    "path": "internal/amneziawg/run.go",
    "chars": 3962,
    "preview": "package amneziawg\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\n\tamneziaconn \"github.com/amnezia-vpn/amneziawg-go/conn\"\n"
  },
  {
    "path": "internal/amneziawg/settings.go",
    "chars": 1439,
    "preview": "package amneziawg\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/qdm12/gluetun/internal/wireguard\"\n)\n\ntype Settings struct {\n"
  },
  {
    "path": "internal/boringpoll/boringpoll.go",
    "chars": 5718,
    "preview": "package boringpoll\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"math/rand\"\n\t\"net/http\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/qdm12/gluetu"
  },
  {
    "path": "internal/boringpoll/interfaces.go",
    "chars": 117,
    "preview": "package boringpoll\n\ntype Logger interface {\n\tInfof(format string, args ...any)\n\tDebugf(format string, args ...any)\n}\n"
  },
  {
    "path": "internal/cleanup/cleanup.go",
    "chars": 1299,
    "preview": "package cleanup\n\nimport \"sort\"\n\ntype Cleanups []cleanup\n\ntype cleanup struct {\n\toperation  string\n\torderIndex uint\n\tclea"
  },
  {
    "path": "internal/cleanup/cleanup_test.go",
    "chars": 1284,
    "preview": "package cleanup\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/golang/mock/gomock\"\n\t\"github.com/stretchr/testify/assert\"\n)"
  },
  {
    "path": "internal/cleanup/interfaces.go",
    "chars": 73,
    "preview": "package cleanup\n\ntype Logger interface {\n\tDebug(string)\n\tError(string)\n}\n"
  },
  {
    "path": "internal/cleanup/mocks_generate_test.go",
    "chars": 95,
    "preview": "package cleanup\n\n//go:generate mockgen -destination=mocks_test.go -package=$GOPACKAGE . Logger\n"
  },
  {
    "path": "internal/cleanup/mocks_test.go",
    "chars": 1632,
    "preview": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/qdm12/gluetun/internal/cleanup (interfaces: Logger)\n\n//"
  },
  {
    "path": "internal/cli/ci.go",
    "chars": 87,
    "preview": "package cli\n\nimport \"context\"\n\nfunc (c *CLI) CI(context.Context) error {\n\treturn nil\n}\n"
  },
  {
    "path": "internal/cli/cli.go",
    "chars": 149,
    "preview": "package cli\n\ntype CLI struct {\n\trepoServersPath string\n}\n\nfunc New() *CLI {\n\treturn &CLI{\n\t\trepoServersPath: \"./internal"
  },
  {
    "path": "internal/cli/clientkey.go",
    "chars": 857,
    "preview": "package cli\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n)\n\nfunc (c *CLI) ClientKey(args []string) error {\n\tflagSet :"
  },
  {
    "path": "internal/cli/formatservers.go",
    "chars": 3169,
    "preview": "package cli\n\nimport (\n\t\"errors\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/qdm12/gluetun/in"
  },
  {
    "path": "internal/cli/genkey.go",
    "chars": 1392,
    "preview": "package cli\n\nimport (\n\t\"crypto/rand\"\n\t\"flag\"\n\t\"fmt\"\n)\n\nfunc (c *CLI) GenKey(args []string) (err error) {\n\tflagSet := fla"
  },
  {
    "path": "internal/cli/healthcheck.go",
    "chars": 886,
    "preview": "package cli\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/qdm12/gluetun/internal/configuration/settings\""
  },
  {
    "path": "internal/cli/interfaces.go",
    "chars": 221,
    "preview": "package cli\n\nimport \"github.com/qdm12/gluetun/internal/configuration/settings\"\n\ntype Source interface {\n\tRead() (setting"
  },
  {
    "path": "internal/cli/nooplogger.go",
    "chars": 175,
    "preview": "package cli\n\ntype noopLogger struct{}\n\nfunc newNoopLogger() *noopLogger {\n\treturn new(noopLogger)\n}\n\nfunc (l *noopLogger"
  },
  {
    "path": "internal/cli/openvpnconfig.go",
    "chars": 2416,
    "preview": "package cli\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/netip\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/qdm12/gluetun/internal"
  },
  {
    "path": "internal/cli/update.go",
    "chars": 5234,
    "preview": "package cli\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"flag\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"slices\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/qdm12/d"
  },
  {
    "path": "internal/cli/warner.go",
    "chars": 55,
    "preview": "package cli\n\ntype Warner interface {\n\tWarn(s string)\n}\n"
  },
  {
    "path": "internal/command/cmder.go",
    "chars": 148,
    "preview": "package command\n\n// Cmder handles running subprograms synchronously and asynchronously.\ntype Cmder struct{}\n\nfunc New() "
  },
  {
    "path": "internal/command/interfaces.go",
    "chars": 76,
    "preview": "package command\n\ntype Logger interface {\n\tInfo(s string)\n\tError(s string)\n}\n"
  },
  {
    "path": "internal/command/interfaces_local.go",
    "chars": 194,
    "preview": "package command\n\nimport \"io\"\n\ntype execCmd interface {\n\tCombinedOutput() ([]byte, error)\n\tStdoutPipe() (io.ReadCloser, e"
  },
  {
    "path": "internal/command/mocks_generate_test.go",
    "chars": 120,
    "preview": "package command\n\n//go:generate mockgen -destination=mocks_local_test.go -package=$GOPACKAGE -source=interfaces_local.go\n"
  },
  {
    "path": "internal/command/mocks_local_test.go",
    "chars": 3148,
    "preview": "// Code generated by MockGen. DO NOT EDIT.\n// Source: interfaces_local.go\n\n// Package command is a generated GoMock pack"
  },
  {
    "path": "internal/command/run.go",
    "chars": 712,
    "preview": "package command\n\nimport (\n\t\"os/exec\"\n\t\"strings\"\n)\n\n// Run runs a command in a blocking manner, returning its output and\n"
  },
  {
    "path": "internal/command/run_test.go",
    "chars": 986,
    "preview": "package command\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\tgomock \"github.com/golang/mock/gomock\"\n\t\"github.com/stretchr/testify/as"
  },
  {
    "path": "internal/command/split.go",
    "chars": 4786,
    "preview": "package command\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\t\"unicode/utf8\"\n)\n\nvar (\n\terrCommandEmpty            = er"
  },
  {
    "path": "internal/command/split_test.go",
    "chars": 2833,
    "preview": "package command\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc Test_split(t *testing.T) {\n\tt.Parall"
  },
  {
    "path": "internal/command/start.go",
    "chars": 2259,
    "preview": "package command\n\nimport (\n\t\"bufio\"\n\t\"errors\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n)\n\n// Start launches a command and streams stdout an"
  },
  {
    "path": "internal/command/start_test.go",
    "chars": 2626,
    "preview": "package command\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"io\"\n\t\"strings\"\n\t\"testing\"\n\n\tgomock \"github.com/golang/mock/gomock\"\n\t\"gith"
  },
  {
    "path": "internal/command/startnlog.go",
    "chars": 929,
    "preview": "package command\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os/exec\"\n)\n\nfunc (c *Cmder) RunAndLog(ctx context.Context, command string,"
  },
  {
    "path": "internal/configuration/settings/amneziawg.go",
    "chars": 8764,
    "preview": "package settings\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/qdm12/gosettings\"\n\t\"github.com/qdm12/gos"
  },
  {
    "path": "internal/configuration/settings/boringpoll.go",
    "chars": 998,
    "preview": "package settings\n\nimport (\n\t\"github.com/qdm12/gosettings\"\n\t\"github.com/qdm12/gosettings/reader\"\n\t\"github.com/qdm12/gotre"
  },
  {
    "path": "internal/configuration/settings/deprecated.go",
    "chars": 1317,
    "preview": "package settings\n\nimport (\n\t\"slices\"\n\n\t\"github.com/qdm12/gosettings/reader\"\n\t\"golang.org/x/exp/maps\"\n)\n\nfunc readObsolet"
  },
  {
    "path": "internal/configuration/settings/dns.go",
    "chars": 9560,
    "preview": "package settings\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"slices\"\n\t\"time\"\n\n\t\"github.com/qdm12/dns/v2/pkg/provider\"\n\t\"gi"
  },
  {
    "path": "internal/configuration/settings/dns_test.go",
    "chars": 514,
    "preview": "package settings\n\nimport (\n\t\"testing\"\n\n\t\"github.com/qdm12/dns/v2/pkg/provider\"\n\t\"github.com/stretchr/testify/require\"\n)\n"
  },
  {
    "path": "internal/configuration/settings/dnsblacklist.go",
    "chars": 7682,
    "preview": "package settings\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/netip\"\n\t\"regexp\"\n\n\t\"github.com/qdm12/dns/v2/pkg/blockbuild"
  },
  {
    "path": "internal/configuration/settings/errors.go",
    "chars": 4266,
    "preview": "package settings\n\nimport \"errors\"\n\nvar (\n\tErrValueUnknown                    = errors.New(\"value is unknown\")\n\tErrCityNo"
  },
  {
    "path": "internal/configuration/settings/firewall.go",
    "chars": 3523,
    "preview": "package settings\n\nimport (\n\t\"fmt\"\n\t\"net/netip\"\n\n\t\"github.com/qdm12/gosettings\"\n\t\"github.com/qdm12/gosettings/reader\"\n\t\"g"
  },
  {
    "path": "internal/configuration/settings/firewall_test.go",
    "chars": 1872,
    "preview": "package settings\n\nimport (\n\t\"net/netip\"\n\t\"testing\"\n\n\t\"github.com/qdm12/log\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfun"
  },
  {
    "path": "internal/configuration/settings/health.go",
    "chars": 5212,
    "preview": "package settings\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"os\"\n\n\t\"github.com/qdm12/gosettings\"\n\t\"github.com/qdm12/gosett"
  },
  {
    "path": "internal/configuration/settings/helpers/belong.go",
    "chars": 174,
    "preview": "package helpers\n\nfunc IsOneOf[T comparable](value T, choices ...T) (ok bool) {\n\tfor _, choice := range choices {\n\t\tif va"
  },
  {
    "path": "internal/configuration/settings/helpers.go",
    "chars": 67,
    "preview": "package settings\n\nfunc ptrTo[T any](value T) *T {\n\treturn &value\n}\n"
  },
  {
    "path": "internal/configuration/settings/helpers_test.go",
    "chars": 882,
    "preview": "package settings\n\nimport gomock \"github.com/golang/mock/gomock\"\n\ntype sourceKeyValue struct {\n\tkey   string\n\tvalue strin"
  },
  {
    "path": "internal/configuration/settings/httpproxy.go",
    "chars": 6010,
    "preview": "package settings\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/qdm12/gosettings\"\n\t\"github.com/qdm12/gosettings"
  },
  {
    "path": "internal/configuration/settings/interfaces.go",
    "chars": 66,
    "preview": "package settings\n\ntype Warner interface {\n\tWarn(message string)\n}\n"
  },
  {
    "path": "internal/configuration/settings/iptables.go",
    "chars": 1672,
    "preview": "package settings\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/qdm12/gosettings\"\n\t\"github.com/qdm12/gosettings/reader\"\n\t\"github.com/qdm"
  },
  {
    "path": "internal/configuration/settings/log.go",
    "chars": 1184,
    "preview": "package settings\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/qdm12/gosettings\"\n\t\"github.com/qdm12/gosettings/reader\"\n\t\"github.com/qdm"
  },
  {
    "path": "internal/configuration/settings/mocks_generate_test.go",
    "chars": 214,
    "preview": "package settings\n\n//go:generate mockgen -destination=mocks_test.go -package=$GOPACKAGE . Warner\n//go:generate mockgen -d"
  },
  {
    "path": "internal/configuration/settings/mocks_reader_test.go",
    "chars": 2223,
    "preview": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/qdm12/gosettings/reader (interfaces: Source)\n\n// Packag"
  },
  {
    "path": "internal/configuration/settings/mocks_test.go",
    "chars": 1259,
    "preview": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/qdm12/gluetun/internal/configuration/settings (interfac"
  },
  {
    "path": "internal/configuration/settings/nordvpn_retro.go",
    "chars": 1305,
    "preview": "package settings\n\n// Retro-compatibility because SERVER_REGIONS changed to SERVER_COUNTRIES\n// and SERVER_REGIONS is now"
  },
  {
    "path": "internal/configuration/settings/openvpn.go",
    "chars": 13949,
    "preview": "package settings\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/qdm12/gluetun/internal/constants"
  },
  {
    "path": "internal/configuration/settings/openvpn_test.go",
    "chars": 806,
    "preview": "package settings\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc Test_ivpnAccountID(t *testing.T) {\n"
  },
  {
    "path": "internal/configuration/settings/openvpnselection.go",
    "chars": 7562,
    "preview": "package settings\n\nimport (\n\t\"fmt\"\n\t\"net/netip\"\n\t\"strings\"\n\n\t\"github.com/qdm12/gluetun/internal/configuration/settings/he"
  },
  {
    "path": "internal/configuration/settings/pmtud.go",
    "chars": 3358,
    "preview": "package settings\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net/netip\"\n\n\t\"github.com/qdm12/gosettings\"\n\t\"github.com/qdm12/gosettings/r"
  },
  {
    "path": "internal/configuration/settings/portforward.go",
    "chars": 6940,
    "preview": "package settings\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\n\t\"github.com/qdm12/gluetun/internal/constants/providers\"\n\t\"github.co"
  },
  {
    "path": "internal/configuration/settings/portforward_test.go",
    "chars": 248,
    "preview": "package settings\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc Test_PortForwarding_String(t *testi"
  },
  {
    "path": "internal/configuration/settings/provider.go",
    "chars": 3979,
    "preview": "package settings\n\nimport (\n\t\"fmt\"\n\t\"slices\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/qdm12/gluetun/internal/constants/providers\""
  },
  {
    "path": "internal/configuration/settings/publicip.go",
    "chars": 5095,
    "preview": "package settings\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\n\t\"github.com/qdm12/gluetun/internal/publicip/api\"\n\t\"github.com/qdm12"
  },
  {
    "path": "internal/configuration/settings/publicip_test.go",
    "chars": 4228,
    "preview": "package settings\n\nimport (\n\t\"testing\"\n\n\t\"github.com/golang/mock/gomock\"\n\t\"github.com/qdm12/gosettings/reader\"\n\t\"github.c"
  },
  {
    "path": "internal/configuration/settings/server.go",
    "chars": 4121,
    "preview": "package settings\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"strconv\"\n\n\t\"github.com/qdm12/gluetun/internal"
  },
  {
    "path": "internal/configuration/settings/serverselection.go",
    "chars": 18174,
    "preview": "package settings\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/qdm12/gluetun/internal/configuration/settings/helpe"
  },
  {
    "path": "internal/configuration/settings/settings.go",
    "chars": 7198,
    "preview": "package settings\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/qdm12/gluetun/internal/configuration/settings/helpers\"\n\t\"github.com/qdm1"
  },
  {
    "path": "internal/configuration/settings/settings_test.go",
    "chars": 2982,
    "preview": "package settings\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc Test_Settings_String(t *testing.T) "
  },
  {
    "path": "internal/configuration/settings/shadowsocks.go",
    "chars": 2631,
    "preview": "package settings\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/qdm12/gosettings\"\n\t\"github.com/qdm12/gosettings/reader\"\n\t\"github.com/qdm"
  },
  {
    "path": "internal/configuration/settings/storage.go",
    "chars": 1382,
    "preview": "package settings\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\n\t\"github.com/qdm12/gosettings\"\n\t\"github.com/qdm12/gosettings/reader\""
  },
  {
    "path": "internal/configuration/settings/surfshark_retro.go",
    "chars": 1440,
    "preview": "package settings\n\nimport (\n\t\"strings\"\n\n\t\"github.com/qdm12/gluetun/internal/provider/surfshark/servers\"\n)\n\nfunc surfshark"
  },
  {
    "path": "internal/configuration/settings/system.go",
    "chars": 1578,
    "preview": "package settings\n\nimport (\n\t\"github.com/qdm12/gosettings\"\n\t\"github.com/qdm12/gosettings/reader\"\n\t\"github.com/qdm12/gotre"
  },
  {
    "path": "internal/configuration/settings/updater.go",
    "chars": 4621,
    "preview": "package settings\n\nimport (\n\t\"fmt\"\n\t\"slices\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/qdm12/gluetun/internal/constants/providers\""
  },
  {
    "path": "internal/configuration/settings/validation/servers.go",
    "chars": 3148,
    "preview": "package validation\n\nimport (\n\t\"sort\"\n\n\t\"github.com/qdm12/gluetun/internal/models\"\n)\n\nfunc sortedInsert(ss []string, s st"
  },
  {
    "path": "internal/configuration/settings/validation/surfshark.go",
    "chars": 555,
    "preview": "package validation\n\nimport (\n\t\"github.com/qdm12/gluetun/internal/provider/surfshark/servers\"\n)\n\n// TODO remove in v4.\nfu"
  },
  {
    "path": "internal/configuration/settings/version.go",
    "chars": 1223,
    "preview": "package settings\n\nimport (\n\t\"github.com/qdm12/gosettings\"\n\t\"github.com/qdm12/gosettings/reader\"\n\t\"github.com/qdm12/gotre"
  },
  {
    "path": "internal/configuration/settings/vpn.go",
    "chars": 4870,
    "preview": "package settings\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/qdm12/gluetun/internal/constants/vpn\"\n\t\"github.com/qdm12/gosettings\"\n\t\"g"
  },
  {
    "path": "internal/configuration/settings/wireguard.go",
    "chars": 9327,
    "preview": "package settings\n\nimport (\n\t\"fmt\"\n\t\"net/netip\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/qdm12/gluetun/internal/constan"
  },
  {
    "path": "internal/configuration/settings/wireguardselection.go",
    "chars": 5391,
    "preview": "package settings\n\nimport (\n\t\"fmt\"\n\t\"net/netip\"\n\n\t\"github.com/qdm12/gluetun/internal/constants/providers\"\n\t\"github.com/qd"
  },
  {
    "path": "internal/configuration/sources/files/amneziawg.go",
    "chars": 2371,
    "preview": "package files\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"gopkg.in/ini.v1\"\n)\n\nfunc (s *Source) lazyLoadAmneziaw"
  },
  {
    "path": "internal/configuration/sources/files/amneziawg_test.go",
    "chars": 1957,
    "preview": "package files\n\nimport (\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/s"
  },
  {
    "path": "internal/configuration/sources/files/helpers.go",
    "chars": 1270,
    "preview": "package files\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/qdm12/gluetun/internal/openvpn/extract\"\n)\n\n// ReadFr"
  },
  {
    "path": "internal/configuration/sources/files/interfaces.go",
    "chars": 81,
    "preview": "package files\n\ntype Warner interface {\n\tWarnf(format string, a ...interface{})\n}\n"
  },
  {
    "path": "internal/configuration/sources/files/reader.go",
    "chars": 4970,
    "preview": "package files\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n)\n\ntype Source struct {\n\trootDirectory string\n\tenviron       m"
  },
  {
    "path": "internal/configuration/sources/files/wireguard.go",
    "chars": 3091,
    "preview": "package files\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"gopkg.in/ini.v1\"\n)\n\nfunc (s *Sou"
  },
  {
    "path": "internal/configuration/sources/files/wireguard_test.go",
    "chars": 5099,
    "preview": "package files\n\nimport (\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/s"
  },
  {
    "path": "internal/configuration/sources/secrets/amneziawg.go",
    "chars": 603,
    "preview": "package secrets\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/qdm12/gluetun/internal/configuration/sources/files\"\n)\n\nfu"
  },
  {
    "path": "internal/configuration/sources/secrets/helpers.go",
    "chars": 140,
    "preview": "package secrets\n\nfunc strPtrToStringIsSet(ptr *string) (s string, isSet bool) {\n\tif ptr == nil {\n\t\treturn \"\", false\n\t}\n\t"
  },
  {
    "path": "internal/configuration/sources/secrets/interfaces.go",
    "chars": 83,
    "preview": "package secrets\n\ntype Warner interface {\n\tWarnf(format string, a ...interface{})\n}\n"
  },
  {
    "path": "internal/configuration/sources/secrets/reader.go",
    "chars": 5396,
    "preview": "package secrets\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/qdm12/gluetun/internal/configuration/sources/f"
  },
  {
    "path": "internal/configuration/sources/secrets/reader_test.go",
    "chars": 2534,
    "preview": "package secrets\n\nimport (\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com"
  },
  {
    "path": "internal/configuration/sources/secrets/wireguard.go",
    "chars": 589,
    "preview": "package secrets\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/qdm12/gluetun/internal/configuration/sources/files\"\n)\n\nfu"
  },
  {
    "path": "internal/constants/colors.go",
    "chars": 126,
    "preview": "package constants\n\nimport \"github.com/fatih/color\"\n\nfunc ColorOpenvpn() *color.Color {\n\treturn color.New(color.FgHiMagen"
  },
  {
    "path": "internal/constants/countries.go",
    "chars": 5842,
    "preview": "package constants\n\nfunc CountryCodes() map[string]string {\n\treturn map[string]string{\n\t\t\"af\":  \"Afghanistan\",\n\t\t\"ax\":  \""
  },
  {
    "path": "internal/constants/openvpn/auth.go",
    "chars": 82,
    "preview": "package openvpn\n\nconst (\n\tSHA1   = \"sha1\"\n\tSHA256 = \"sha256\"\n\tSHA512 = \"sha512\"\n)\n"
  },
  {
    "path": "internal/constants/openvpn/ciphers.go",
    "chars": 271,
    "preview": "package openvpn\n\nconst (\n\tAES128cbc        = \"aes-128-cbc\"\n\tAES192cbc        = \"aes-192-cbc\"\n\tAES256cbc        = \"aes-25"
  },
  {
    "path": "internal/constants/openvpn/paths.go",
    "chars": 301,
    "preview": "package openvpn\n\nconst (\n\t// AuthConf is the file path to the OpenVPN auth file.\n\tAuthConf = \"/etc/openvpn/auth.conf\"\n\t/"
  },
  {
    "path": "internal/constants/openvpn/versions.go",
    "chars": 65,
    "preview": "package openvpn\n\nconst (\n\tOpenvpn25 = \"2.5\"\n\tOpenvpn26 = \"2.6\"\n)\n"
  },
  {
    "path": "internal/constants/paths.go",
    "chars": 120,
    "preview": "package constants\n\nconst (\n\t// ServersData is the server information filepath.\n\tServersData = \"/gluetun/servers.json\"\n)\n"
  },
  {
    "path": "internal/constants/protocol.go",
    "chars": 195,
    "preview": "package constants\n\nconst (\n\t// TCP is a network protocol (reliable and slower than UDP).\n\tTCP string = \"tcp\"\n\t// UDP is "
  },
  {
    "path": "internal/constants/providers/providers.go",
    "chars": 1696,
    "preview": "package providers\n\nconst (\n\t// Custom is the VPN provider name for custom\n\t// VPN configurations.\n\tAirvpn               "
  },
  {
    "path": "internal/constants/providers/providers_test.go",
    "chars": 352,
    "preview": "package providers\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc Test_All(t *testing.T) {\n\tt.Parall"
  },
  {
    "path": "internal/constants/status.go",
    "chars": 335,
    "preview": "package constants\n\nimport (\n\t\"github.com/qdm12/gluetun/internal/models\"\n)\n\nconst (\n\tStarting  models.LoopStatus = \"start"
  },
  {
    "path": "internal/constants/vpn/protocol.go",
    "chars": 96,
    "preview": "package vpn\n\nconst (\n\tAmneziaWg = \"amneziawg\"\n\tOpenVPN   = \"openvpn\"\n\tWireguard = \"wireguard\"\n)\n"
  },
  {
    "path": "internal/dns/leak.go",
    "chars": 3024,
    "preview": "package dns\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"math/rand/v2\"\n\t\"net/http\"\n\t\"sort\"\n\t\"strings"
  },
  {
    "path": "internal/dns/leak_test.go",
    "chars": 394,
    "preview": "package dns\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_lea"
  },
  {
    "path": "internal/dns/logger.go",
    "chars": 175,
    "preview": "package dns\n\ntype Logger interface {\n\tDebug(s string)\n\tInfo(s string)\n\tInfof(format string, args ...any)\n\tWarn(s string)"
  },
  {
    "path": "internal/dns/loop.go",
    "chars": 2970,
    "preview": "package dns\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/netip\"\n\t\"time\"\n\n\t\"github.com/qdm12/dns/v2/pkg/middlewares/filt"
  },
  {
    "path": "internal/dns/plaintext.go",
    "chars": 884,
    "preview": "package dns\n\nimport (\n\t\"net/netip\"\n\t\"time\"\n\n\t\"github.com/qdm12/dns/v2/pkg/nameserver\"\n)\n\nfunc (l *Loop) useUnencryptedDN"
  },
  {
    "path": "internal/dns/run.go",
    "chars": 2395,
    "preview": "package dns\n\nimport (\n\t\"context\"\n\n\t\"github.com/qdm12/dns/v2/pkg/nameserver\"\n\t\"github.com/qdm12/gluetun/internal/configur"
  },
  {
    "path": "internal/dns/settings.go",
    "chars": 5121,
    "preview": "package dns\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"slices\"\n\n\t\"github.com/qdm12/dns/v2/pkg/doh\"\n\t\"github.com/qdm12/dn"
  },
  {
    "path": "internal/dns/setup.go",
    "chars": 1462,
    "preview": "package dns\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/qdm12/dns/v2/pkg/check\"\n\t\"github.com/qdm12/dns/v2/pkg/middlewares/"
  },
  {
    "path": "internal/dns/state/settings.go",
    "chars": 1053,
    "preview": "package state\n\nimport (\n\t\"context\"\n\t\"reflect\"\n\n\t\"github.com/qdm12/gluetun/internal/configuration/settings\"\n\t\"github.com/"
  },
  {
    "path": "internal/dns/state/state.go",
    "chars": 625,
    "preview": "package state\n\nimport (\n\t\"context\"\n\t\"sync\"\n\n\t\"github.com/qdm12/gluetun/internal/configuration/settings\"\n\t\"github.com/qdm"
  },
  {
    "path": "internal/dns/status.go",
    "chars": 335,
    "preview": "package dns\n\nimport (\n\t\"context\"\n\n\t\"github.com/qdm12/gluetun/internal/models\"\n)\n\nfunc (l *Loop) GetStatus() (status mode"
  },
  {
    "path": "internal/dns/ticker.go",
    "chars": 1281,
    "preview": "package dns\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/qdm12/gluetun/internal/constants\"\n)\n\nfunc (l *Loop) RunRestartTic"
  },
  {
    "path": "internal/dns/update.go",
    "chars": 1065,
    "preview": "package dns\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/qdm12/dns/v2/pkg/blockbuilder\"\n\t\"github.com/qdm12/dns/v2/pkg/middl"
  },
  {
    "path": "internal/firewall/enable.go",
    "chars": 4754,
    "preview": "package firewall\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/qdm12/gluetun/internal/netlink\"\n)\n\nfunc (c *Config) SetEnable"
  },
  {
    "path": "internal/firewall/firewall.go",
    "chars": 1507,
    "preview": "package firewall\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"sync\"\n\n\t\"github.com/qdm12/gluetun/internal/firewall/iptables"
  },
  {
    "path": "internal/firewall/interfaces.go",
    "chars": 1596,
    "preview": "package firewall\n\nimport (\n\t\"context\"\n\t\"net/netip\"\n\t\"os/exec\"\n\n\t\"github.com/qdm12/gluetun/internal/models\"\n)\n\ntype CmdRu"
  },
  {
    "path": "internal/firewall/iptables/atomic.go",
    "chars": 2610,
    "preview": "package iptables\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os/exec\"\n\t\"strings\"\n)\n\n// SaveAndRestore saves the current iptables and i"
  },
  {
    "path": "internal/firewall/iptables/cmd_matcher_test.go",
    "chars": 1067,
    "preview": "package iptables\n\nimport (\n\t\"fmt\"\n\t\"os/exec\"\n\t\"regexp\"\n\n\t\"github.com/golang/mock/gomock\"\n)\n\nvar _ gomock.Matcher = (*cmd"
  },
  {
    "path": "internal/firewall/iptables/delete.go",
    "chars": 2836,
    "preview": "package iptables\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os/exec\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// isDeleteMatchInstruction returns tru"
  },
  {
    "path": "internal/firewall/iptables/delete_test.go",
    "chars": 7230,
    "preview": "package iptables\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/golang/mock/gomock\"\n\t\"github.com/stretchr/testi"
  },
  {
    "path": "internal/firewall/iptables/firewall.go",
    "chars": 659,
    "preview": "package iptables\n\nimport (\n\t\"context\"\n\t\"sync\"\n)\n\ntype Config struct {\n\trunner         CmdRunner\n\tlogger         Logger\n\t"
  },
  {
    "path": "internal/firewall/iptables/interfaces.go",
    "chars": 172,
    "preview": "package iptables\n\nimport \"os/exec\"\n\ntype CmdRunner interface {\n\tRun(cmd *exec.Cmd) (output string, err error)\n}\n\ntype Lo"
  },
  {
    "path": "internal/firewall/iptables/ip6tables.go",
    "chars": 2554,
    "preview": "package iptables\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os/exec\"\n\t\"strings\"\n)\n\n// findIP6tablesSupported checks for mul"
  },
  {
    "path": "internal/firewall/iptables/iptables.go",
    "chars": 11491,
    "preview": "package iptables\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/netip\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\n\t\"github.com/qdm1"
  },
  {
    "path": "internal/firewall/iptables/iptablesmix.go",
    "chars": 1145,
    "preview": "package iptables\n\nimport (\n\t\"context\"\n)\n\nfunc (c *Config) runMixedIptablesInstructions(ctx context.Context, instructions"
  },
  {
    "path": "internal/firewall/iptables/list.go",
    "chars": 14446,
    "preview": "package iptables\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n)\n\ntype chain struct {\n\tname   "
  },
  {
    "path": "internal/firewall/iptables/list_test.go",
    "chars": 4604,
    "preview": "package iptables\n\nimport (\n\t\"net/netip\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc Test_parseChain(t *tes"
  },
  {
    "path": "internal/firewall/iptables/mocks_generate_test.go",
    "chars": 106,
    "preview": "package iptables\n\n//go:generate mockgen -destination=mocks_test.go -package $GOPACKAGE . CmdRunner,Logger\n"
  },
  {
    "path": "internal/firewall/iptables/mocks_test.go",
    "chars": 2794,
    "preview": "// Code generated by MockGen. DO NOT EDIT.\n// Source: github.com/qdm12/gluetun/internal/firewall/iptables (interfaces: C"
  },
  {
    "path": "internal/firewall/iptables/parse.go",
    "chars": 7839,
    "preview": "package iptables\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n)\n\ntype iptablesInstruction str"
  },
  {
    "path": "internal/firewall/iptables/parse_test.go",
    "chars": 3475,
    "preview": "package iptables\n\nimport (\n\t\"net/netip\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc Test_parseIptablesInst"
  },
  {
    "path": "internal/firewall/iptables/support.go",
    "chars": 4974,
    "preview": "package iptables\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"os/exec\"\n\t\"sort\"\n\t\"strings\"\n)\n\nvar (\n\tErrNetAdminM"
  },
  {
    "path": "internal/firewall/iptables/support_test.go",
    "chars": 10818,
    "preview": "package iptables\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/golang/mock/gomock\"\n\t\"github.com/stretchr/testi"
  },
  {
    "path": "internal/firewall/iptables/tcp.go",
    "chars": 2541,
    "preview": "package iptables\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"os\"\n)\n\ntype tcpFlags struct {\n\tmask       []tcpFla"
  },
  {
    "path": "internal/firewall/logger.go",
    "chars": 226,
    "preview": "package firewall\n\nimport (\n\t\"fmt\"\n\t\"net/netip\"\n)\n\nfunc (c *Config) logIgnoredSubnetFamily(subnet netip.Prefix) {\n\tc.logg"
  },
  {
    "path": "internal/firewall/outboundsubnets.go",
    "chars": 2517,
    "preview": "package firewall\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/netip\"\n\n\t\"github.com/qdm12/gluetun/internal/netlink\"\n\t\"github.com/qdm"
  },
  {
    "path": "internal/firewall/ports.go",
    "chars": 2009,
    "preview": "package firewall\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strconv\"\n)\n\nfunc (c *Config) SetAllowedPort(ctx context.Context, port uin"
  },
  {
    "path": "internal/firewall/redirect.go",
    "chars": 3265,
    "preview": "package firewall\n\nimport (\n\t\"context\"\n\t\"fmt\"\n)\n\n// RedirectPort redirects a source port to a destination port on the int"
  },
  {
    "path": "internal/firewall/vpn.go",
    "chars": 1566,
    "preview": "package firewall\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/qdm12/gluetun/internal/models\"\n)\n\nfunc (c *Config) SetVPNConn"
  },
  {
    "path": "internal/firewall/wrappers.go",
    "chars": 743,
    "preview": "package firewall\n\nimport (\n\t\"context\"\n\t\"net/netip\"\n)\n\nfunc (c *Config) Version(ctx context.Context) (version string, err"
  },
  {
    "path": "internal/format/duration.go",
    "chars": 922,
    "preview": "package format\n\nimport (\n\t\"fmt\"\n\t\"time\"\n)\n\n// FriendlyDuration formats a duration in an approximate, human friendly dura"
  },
  {
    "path": "internal/format/duration_test.go",
    "chars": 1128,
    "preview": "package format\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc Test_FriendlyDuration(t *test"
  },
  {
    "path": "internal/healthcheck/checker.go",
    "chars": 10308,
    "preview": "package healthcheck\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n"
  },
  {
    "path": "internal/healthcheck/checker_test.go",
    "chars": 2213,
    "preview": "package healthcheck\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"githu"
  },
  {
    "path": "internal/healthcheck/client.go",
    "chars": 892,
    "preview": "package healthcheck\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"time\"\n)\n\nvar ErrHTTPStatusNotOK = errors.N"
  }
]

// ... and 596 more files (download for full content)

About this extraction

This page contains the full source code of the qdm12/gluetun GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 796 files (9.3 MB), approximately 2.5M tokens, and a symbol index with 2510 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!