This commit is contained in:
Quentin McGaw
2020-06-05 19:32:12 -04:00
parent 0fb065eb61
commit e33a6a8503
7 changed files with 88 additions and 53 deletions

View File

@@ -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 | | `PROTOCOL` | `udp` | `udp` or `tcp` | Network protocol to use |
| `OPENVPN_VERBOSITY` | `1` | `0` to `6` | Openvpn verbosity level | | `OPENVPN_VERBOSITY` | `1` | `0` to `6` | Openvpn verbosity level |
| `OPENVPN_ROOT` | `no` | `yes` or `no` | Run OpenVPN as root | | `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_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 | | `OPENVPN_AUTH` | | i.e. `sha256` | Specify a custom auth algorithm to use |

View File

@@ -25,6 +25,7 @@ import (
"github.com/qdm12/private-internet-access-docker/internal/openvpn" "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/params"
"github.com/qdm12/private-internet-access-docker/internal/pia" "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/routing"
"github.com/qdm12/private-internet-access-docker/internal/server" "github.com/qdm12/private-internet-access-docker/internal/server"
"github.com/qdm12/private-internet-access-docker/internal/settings" "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) 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 { if err != nil {
logger.Error(err) logger.Error(err)
} else { } 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( err = fileManager.WriteLinesToFile(
string(allSettings.System.IPStatusFilepath), string(allSettings.System.IPStatusFilepath),
[]string{ip.String()}, []string{publicIP.String()},
files.Ownership(allSettings.System.UID, allSettings.System.GID), files.Ownership(allSettings.System.UID, allSettings.System.GID),
files.Permissions(0400)) files.Permissions(0400))
if err != nil { if err != nil {

View File

@@ -2,60 +2,35 @@ package cli
import ( import (
"fmt" "fmt"
"math/rand" "net"
"net/http"
"strings"
"time" "time"
"github.com/qdm12/golibs/files" "github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/network" "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 { func HealthCheck() error {
// Get all VPN ip addresses from openvpn configuration file paramsReader := params.NewReader(nil)
fileManager := files.NewFileManager() ipStatusFilepath, err := paramsReader.GetIPStatusFilepath()
b, err := fileManager.ReadFile(string(constants.OpenVPNConf))
if err != nil { if err != nil {
return err 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 // Get all VPN ip addresses from openvpn configuration file
urls := []string{ fileManager := files.NewFileManager()
"http://ip1.dynupdate.no-ip.com:8245", b, err := fileManager.ReadFile(string(ipStatusFilepath))
"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())
if err != nil { if err != nil {
return err 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", "") savedPublicIP := net.ParseIP(string(b))
match := false publicIP, err := publicip.NewIPGetter(network.NewClient(3 * time.Second)).Get()
for _, vpnIP := range vpnIPs { if err != nil {
if publicIP == vpnIP { return err
match = true
break
} }
} if !publicIP.Equal(savedPublicIP) {
if !match { return fmt.Errorf("Public IP address is %s instead of initial vpn IP address %s", publicIP, savedPublicIP)
return fmt.Errorf("Public IP address %s does not match any of the VPN ip addresses %s", publicIP, strings.Join(vpnIPs, ", "))
} }
return nil return nil
} }

View File

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

View File

@@ -61,14 +61,14 @@ func (r *routing) routeExists(subnet net.IPNet) (exists bool, err error) {
return false, nil 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)) data, err := r.fileManager.ReadFile(string(constants.NetRoute))
if err != nil { 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) entries, err := parseRoutingTable(data)
if err != nil { 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 { for _, entry := range entries {
if entry.iface == defaultInterface && if entry.iface == defaultInterface &&
@@ -77,7 +77,7 @@ func (r *routing) CurrentPublicIP(defaultInterface string) (ip net.IP, err error
return entry.destination, nil 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 { func ipIsPrivate(ip net.IP) bool {

View File

@@ -238,17 +238,17 @@ eth0 0002A8C0 0100000A 0003 0 0 0 00FFFFFF
err error err error
}{ }{
"no data": { "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": { "read error": {
readErr: fmt.Errorf("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": { "parse error": {
data: []byte(`Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT data: []byte(`Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT
eth0 x 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": { "found eth0": {
defaultInterface: "eth0", defaultInterface: "eth0",
@@ -258,7 +258,7 @@ eth0 x
"not found tun0": { "not found tun0": {
defaultInterface: "tun0", defaultInterface: "tun0",
data: []byte(exampleRouteData), 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 { for name, tc := range tests {
@@ -271,7 +271,7 @@ eth0 x
filemanager.EXPECT().ReadFile(string(constants.NetRoute)). filemanager.EXPECT().ReadFile(string(constants.NetRoute)).
Return(tc.data, tc.readErr).Times(1) Return(tc.data, tc.readErr).Times(1)
r := &routing{fileManager: filemanager} r := &routing{fileManager: filemanager}
ip, err := r.CurrentPublicIP(tc.defaultInterface) ip, err := r.VPNGatewayIP(tc.defaultInterface)
if tc.err != nil { if tc.err != nil {
require.Error(t, err) require.Error(t, err)
assert.Equal(t, tc.err.Error(), err.Error()) assert.Equal(t, tc.err.Error(), err.Error())

View File

@@ -12,7 +12,7 @@ import (
type Routing interface { type Routing interface {
AddRoutesVia(ctx context.Context, subnets []net.IPNet, defaultGateway net.IP, defaultInterface string) error AddRoutesVia(ctx context.Context, subnets []net.IPNet, defaultGateway net.IP, defaultInterface string) error
DefaultRoute() (defaultInterface string, defaultGateway net.IP, defaultSubnet net.IPNet, err 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 { type routing struct {