From e33a6a85036ab1f25f35dbd0df4dae0ea676449c Mon Sep 17 00:00:00 2001 From: Quentin McGaw Date: Fri, 5 Jun 2020 19:32:12 -0400 Subject: [PATCH] Fix #170 --- README.md | 2 +- cmd/gluetun/main.go | 13 ++++++-- internal/cli/cli.go | 53 +++++++++------------------------ internal/publicip/publicip.go | 53 +++++++++++++++++++++++++++++++++ internal/routing/reader.go | 8 ++--- internal/routing/reader_test.go | 10 +++---- internal/routing/routing.go | 2 +- 7 files changed, 88 insertions(+), 53 deletions(-) create mode 100644 internal/publicip/publicip.go diff --git a/README.md b/README.md index fe28e6ac..a12cfd62 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ Want more testing? ▶ [see the Wiki](https://github.com/qdm12/private-internet- | `PROTOCOL` | `udp` | `udp` or `tcp` | Network protocol to use | | `OPENVPN_VERBOSITY` | `1` | `0` to `6` | Openvpn verbosity level | | `OPENVPN_ROOT` | `no` | `yes` or `no` | Run OpenVPN as root | -| `OPENVPN_TARGET_IP` | | Valid IP address | Specify a target VPN server IP address to use | +| `OPENVPN_TARGET_IP` | | Valid IP address | Specify a target VPN server (or gateway) IP address to use | | `OPENVPN_CIPHER` | | i.e. `aes-256-gcm` | Specify a custom cipher to use. It will also set `ncp-disable` if using AES GCM for PIA | | `OPENVPN_AUTH` | | i.e. `sha256` | Specify a custom auth algorithm to use | diff --git a/cmd/gluetun/main.go b/cmd/gluetun/main.go index 2adb1490..f3de9514 100644 --- a/cmd/gluetun/main.go +++ b/cmd/gluetun/main.go @@ -25,6 +25,7 @@ import ( "github.com/qdm12/private-internet-access-docker/internal/openvpn" "github.com/qdm12/private-internet-access-docker/internal/params" "github.com/qdm12/private-internet-access-docker/internal/pia" + "github.com/qdm12/private-internet-access-docker/internal/publicip" "github.com/qdm12/private-internet-access-docker/internal/routing" "github.com/qdm12/private-internet-access-docker/internal/server" "github.com/qdm12/private-internet-access-docker/internal/settings" @@ -434,14 +435,20 @@ func onConnected(ctx context.Context, allSettings settings.Settings, go unboundRunLoop(ctx, logger, dnsConf, allSettings.DNS, allSettings.System.UID, allSettings.System.GID, waiter, streamMerger, httpServer) } - ip, err := routingConf.CurrentPublicIP(defaultInterface) + vpnGatewayIP, err := routingConf.VPNGatewayIP(defaultInterface) + if err != nil { + logger.Warn(err) + } else { + logger.Info("Gateway VPN IP address: %s", vpnGatewayIP) + } + publicIP, err := publicip.NewIPGetter(network.NewClient(3 * time.Second)).Get() if err != nil { logger.Error(err) } else { - logger.Info("Tunnel IP is %s, see more information at https://ipinfo.io/%s", ip, ip) + logger.Info("Public IP address is %s", publicIP) err = fileManager.WriteLinesToFile( string(allSettings.System.IPStatusFilepath), - []string{ip.String()}, + []string{publicIP.String()}, files.Ownership(allSettings.System.UID, allSettings.System.GID), files.Permissions(0400)) if err != nil { diff --git a/internal/cli/cli.go b/internal/cli/cli.go index f801b979..4e9b1aa3 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -2,60 +2,35 @@ package cli import ( "fmt" - "math/rand" - "net/http" - "strings" + "net" "time" "github.com/qdm12/golibs/files" "github.com/qdm12/golibs/network" - "github.com/qdm12/private-internet-access-docker/internal/constants" + "github.com/qdm12/private-internet-access-docker/internal/params" + "github.com/qdm12/private-internet-access-docker/internal/publicip" ) func HealthCheck() error { - // Get all VPN ip addresses from openvpn configuration file - fileManager := files.NewFileManager() - b, err := fileManager.ReadFile(string(constants.OpenVPNConf)) + paramsReader := params.NewReader(nil) + ipStatusFilepath, err := paramsReader.GetIPStatusFilepath() if err != nil { return err } - var vpnIPs []string - for _, line := range strings.Split(string(b), "\n") { - if strings.HasPrefix(line, "remote ") { - fields := strings.Fields(line) - vpnIPs = append(vpnIPs, fields[1]) - } - } - // Get public IP address from one of the following urls - urls := []string{ - "http://ip1.dynupdate.no-ip.com:8245", - "http://ip1.dynupdate.no-ip.com", - "https://api.ipify.org", - "https://diagnostic.opendns.com/myip", - "https://domains.google.com/checkip", - "https://ifconfig.io/ip", - "https://ip4.ddnss.de/meineip.php", - "https://ipinfo.io/ip", - } - url := urls[rand.Intn(len(urls))] - client := network.NewClient(3 * time.Second) - content, status, err := client.GetContent(url, network.UseRandomUserAgent()) + // Get all VPN ip addresses from openvpn configuration file + fileManager := files.NewFileManager() + b, err := fileManager.ReadFile(string(ipStatusFilepath)) if err != nil { return err - } else if status != http.StatusOK { - return fmt.Errorf("Received unexpected status code %d from %s", status, url) } - publicIP := strings.ReplaceAll(string(content), "\n", "") - match := false - for _, vpnIP := range vpnIPs { - if publicIP == vpnIP { - match = true - break - } + savedPublicIP := net.ParseIP(string(b)) + publicIP, err := publicip.NewIPGetter(network.NewClient(3 * time.Second)).Get() + if err != nil { + return err } - if !match { - return fmt.Errorf("Public IP address %s does not match any of the VPN ip addresses %s", publicIP, strings.Join(vpnIPs, ", ")) + if !publicIP.Equal(savedPublicIP) { + return fmt.Errorf("Public IP address is %s instead of initial vpn IP address %s", publicIP, savedPublicIP) } return nil } diff --git a/internal/publicip/publicip.go b/internal/publicip/publicip.go new file mode 100644 index 00000000..3bc1b82d --- /dev/null +++ b/internal/publicip/publicip.go @@ -0,0 +1,53 @@ +package publicip + +import ( + "fmt" + "math/rand" + "net" + "net/http" + "strings" + + "github.com/qdm12/golibs/network" +) + +type IPGetter interface { + Get() (ip net.IP, err error) +} + +type ipGetter struct { + client network.Client + randIntn func(n int) int +} + +func NewIPGetter(client network.Client) IPGetter { + return &ipGetter{ + client: client, + randIntn: rand.Intn, + } +} + +func (i *ipGetter) Get() (ip net.IP, err error) { + urls := []string{ + "http://ip1.dynupdate.no-ip.com:8245", + "http://ip1.dynupdate.no-ip.com", + "https://api.ipify.org", + "https://diagnostic.opendns.com/myip", + "https://domains.google.com/checkip", + "https://ifconfig.io/ip", + "https://ip4.ddnss.de/meineip.php", + "https://ipinfo.io/ip", + } + url := urls[i.randIntn(len(urls))] + content, status, err := i.client.GetContent(url, network.UseRandomUserAgent()) + if err != nil { + return nil, err + } else if status != http.StatusOK { + return nil, fmt.Errorf("received unexpected status code %d from %s", status, url) + } + s := strings.ReplaceAll(string(content), "\n", "") + ip = net.ParseIP(s) + if ip == nil { + return nil, fmt.Errorf("cannot parse IP address from %q", s) + } + return ip, nil +} diff --git a/internal/routing/reader.go b/internal/routing/reader.go index 6136545c..3f4a21a3 100644 --- a/internal/routing/reader.go +++ b/internal/routing/reader.go @@ -61,14 +61,14 @@ func (r *routing) routeExists(subnet net.IPNet) (exists bool, err error) { return false, nil } -func (r *routing) CurrentPublicIP(defaultInterface string) (ip net.IP, err error) { +func (r *routing) VPNGatewayIP(defaultInterface string) (ip net.IP, err error) { data, err := r.fileManager.ReadFile(string(constants.NetRoute)) if err != nil { - return nil, fmt.Errorf("cannot find current IP address: %w", err) + return nil, fmt.Errorf("cannot find VPN gateway IP address: %w", err) } entries, err := parseRoutingTable(data) if err != nil { - return nil, fmt.Errorf("cannot find current IP address: %w", err) + return nil, fmt.Errorf("cannot find VPN gateway IP address: %w", err) } for _, entry := range entries { if entry.iface == defaultInterface && @@ -77,7 +77,7 @@ func (r *routing) CurrentPublicIP(defaultInterface string) (ip net.IP, err error return entry.destination, nil } } - return nil, fmt.Errorf("cannot find current IP address from ip routes") + return nil, fmt.Errorf("cannot find VPN gateway IP address from ip routes") } func ipIsPrivate(ip net.IP) bool { diff --git a/internal/routing/reader_test.go b/internal/routing/reader_test.go index 5ca038ca..ce0f5560 100644 --- a/internal/routing/reader_test.go +++ b/internal/routing/reader_test.go @@ -238,17 +238,17 @@ eth0 0002A8C0 0100000A 0003 0 0 0 00FFFFFF err error }{ "no data": { - err: fmt.Errorf("cannot find current IP address from ip routes"), + err: fmt.Errorf("cannot find VPN gateway IP address from ip routes"), }, "read error": { readErr: fmt.Errorf("error"), - err: fmt.Errorf("cannot find current IP address: error"), + err: fmt.Errorf("cannot find VPN gateway IP address: error"), }, "parse error": { data: []byte(`Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT eth0 x `), - err: fmt.Errorf("cannot find current IP address: line 1 in /proc/net/route: line \"eth0 x\": not enough fields"), + err: fmt.Errorf("cannot find VPN gateway IP address: line 1 in /proc/net/route: line \"eth0 x\": not enough fields"), }, "found eth0": { defaultInterface: "eth0", @@ -258,7 +258,7 @@ eth0 x "not found tun0": { defaultInterface: "tun0", data: []byte(exampleRouteData), - err: fmt.Errorf("cannot find current IP address from ip routes"), + err: fmt.Errorf("cannot find VPN gateway IP address from ip routes"), }, } for name, tc := range tests { @@ -271,7 +271,7 @@ eth0 x filemanager.EXPECT().ReadFile(string(constants.NetRoute)). Return(tc.data, tc.readErr).Times(1) r := &routing{fileManager: filemanager} - ip, err := r.CurrentPublicIP(tc.defaultInterface) + ip, err := r.VPNGatewayIP(tc.defaultInterface) if tc.err != nil { require.Error(t, err) assert.Equal(t, tc.err.Error(), err.Error()) diff --git a/internal/routing/routing.go b/internal/routing/routing.go index 7a02e2a7..a363bbf0 100644 --- a/internal/routing/routing.go +++ b/internal/routing/routing.go @@ -12,7 +12,7 @@ import ( type Routing interface { AddRoutesVia(ctx context.Context, subnets []net.IPNet, defaultGateway net.IP, defaultInterface string) error DefaultRoute() (defaultInterface string, defaultGateway net.IP, defaultSubnet net.IPNet, err error) - CurrentPublicIP(defaultInterface string) (ip net.IP, err error) + VPNGatewayIP(defaultInterface string) (ip net.IP, err error) } type routing struct {