2020-10-12 10:55:08 -04:00
package provider
import (
"context"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
2020-10-12 15:29:58 -04:00
"math/rand"
2020-10-12 10:55:08 -04:00
"net"
"net/http"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/firewall"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
2020-10-17 21:54:09 +00:00
"golang.org/x/net/context/ctxhttp"
2020-10-12 10:55:08 -04:00
)
type piaV3 struct {
2020-10-12 15:29:58 -04:00
servers [ ] models . PIAOldServer
randSource rand . Source
2020-10-12 10:55:08 -04:00
}
2020-10-12 15:29:58 -04:00
func newPrivateInternetAccessV3 ( servers [ ] models . PIAOldServer , timeNow timeNowFunc ) * piaV3 {
2020-10-12 10:55:08 -04:00
return & piaV3 {
2020-10-12 15:29:58 -04:00
servers : servers ,
randSource : rand . NewSource ( timeNow ( ) . UnixNano ( ) ) ,
2020-10-12 10:55:08 -04:00
}
}
2020-10-12 15:29:58 -04:00
func ( p * piaV3 ) GetOpenVPNConnection ( selection models . ServerSelection ) ( connection models . OpenVPNConnection , err error ) {
var port uint16
switch selection . Protocol {
case constants . TCP :
switch selection . EncryptionPreset {
case constants . PIAEncryptionPresetNormal :
port = 502
case constants . PIAEncryptionPresetStrong :
port = 501
}
case constants . UDP :
switch selection . EncryptionPreset {
case constants . PIAEncryptionPresetNormal :
port = 1198
case constants . PIAEncryptionPresetStrong :
port = 1197
}
}
if port == 0 {
return connection , fmt . Errorf ( "combination of protocol %q and encryption %q does not yield any port number" , selection . Protocol , selection . EncryptionPreset )
}
2020-10-12 20:21:26 +00:00
if selection . TargetIP != nil {
return models . OpenVPNConnection { IP : selection . TargetIP , Port : port , Protocol : selection . Protocol } , nil
}
2020-10-18 17:15:42 -04:00
servers := filterPIAOldServers ( p . servers , selection . Regions )
2020-10-12 20:21:26 +00:00
if len ( servers ) == 0 {
2020-10-18 17:15:42 -04:00
return connection , fmt . Errorf ( "no server found for regions %s" , commaJoin ( selection . Regions ) )
2020-10-12 20:21:26 +00:00
}
2020-10-12 15:29:58 -04:00
var connections [ ] models . OpenVPNConnection
for _ , server := range servers {
for _ , IP := range server . IPs {
2020-10-12 20:21:26 +00:00
connections = append ( connections , models . OpenVPNConnection { IP : IP , Port : port , Protocol : selection . Protocol } )
2020-10-12 15:29:58 -04:00
}
}
return pickRandomConnection ( connections , p . randSource ) , nil
2020-10-12 10:55:08 -04:00
}
2020-10-12 15:29:58 -04:00
func ( p * piaV3 ) BuildConf ( connection models . OpenVPNConnection , verbosity , uid , gid int , root bool , cipher , auth string , extras models . ExtraConfigOptions ) ( lines [ ] string ) {
return buildPIAConf ( connection , verbosity , root , cipher , auth , extras )
2020-10-12 10:55:08 -04:00
}
func ( p * piaV3 ) PortForward ( ctx context . Context , client * http . Client ,
fileManager files . FileManager , pfLogger logging . Logger , gateway net . IP , fw firewall . Configurator ,
syncState func ( port uint16 ) ( pfFilepath models . Filepath ) ) {
2020-10-12 15:29:58 -04:00
b := make ( [ ] byte , 32 )
n , err := rand . New ( p . randSource ) . Read ( b ) //nolint:gosec
2020-10-12 10:55:08 -04:00
if err != nil {
pfLogger . Error ( err )
return
2020-10-12 15:29:58 -04:00
} else if n != 32 {
pfLogger . Error ( "only read %d bytes instead of 32" , n )
return
2020-10-12 10:55:08 -04:00
}
clientID := hex . EncodeToString ( b )
url := fmt . Sprintf ( "%s/?client_id=%s" , constants . PIAPortForwardURL , clientID )
2020-10-17 21:54:09 +00:00
response , err := ctxhttp . Get ( ctx , client , url )
2020-10-12 10:55:08 -04:00
if err != nil {
pfLogger . Error ( err )
return
}
defer response . Body . Close ( )
if response . StatusCode != http . StatusOK {
pfLogger . Error ( fmt . Errorf ( "%s for %s; does your PIA server support port forwarding?" , response . Status , url ) )
return
}
b , err = ioutil . ReadAll ( response . Body )
if err != nil {
pfLogger . Error ( err )
return
} else if len ( b ) == 0 {
pfLogger . Error ( fmt . Errorf ( "port forwarding is already activated on this connection, has expired, or you are not connected to a PIA region that supports port forwarding" ) )
return
}
body := struct {
Port uint16 ` json:"port" `
} { }
if err := json . Unmarshal ( b , & body ) ; err != nil {
pfLogger . Error ( fmt . Errorf ( "port forwarding response: %w" , err ) )
return
}
port := body . Port
filepath := syncState ( port )
pfLogger . Info ( "Writing port to %s" , filepath )
if err := fileManager . WriteToFile (
string ( filepath ) , [ ] byte ( fmt . Sprintf ( "%d" , port ) ) ,
files . Permissions ( 0666 ) ,
) ; err != nil {
pfLogger . Error ( err )
}
if err := fw . SetAllowedPort ( ctx , port , string ( constants . TUN ) ) ; err != nil {
pfLogger . Error ( err )
}
<- ctx . Done ( )
if err := fw . RemoveAllowedPort ( ctx , port ) ; err != nil {
pfLogger . Error ( err )
}
}
2020-10-12 13:57:45 -04:00
2020-10-18 17:15:42 -04:00
func filterPIAOldServers ( servers [ ] models . PIAOldServer , regions [ ] string ) ( filtered [ ] models . PIAOldServer ) {
2020-10-12 13:57:45 -04:00
for _ , server := range servers {
2020-10-18 17:15:42 -04:00
switch {
case filterByPossibilities ( server . Region , regions ) :
default :
2020-10-18 23:44:16 +00:00
filtered = append ( filtered , server )
2020-10-12 13:57:45 -04:00
}
}
2020-10-18 23:44:16 +00:00
return filtered
2020-10-12 13:57:45 -04:00
}