summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRuben Pollan <meskio@sindominio.net>2019-06-19 10:26:19 +0200
committerRuben Pollan <meskio@sindominio.net>2019-08-16 22:58:45 +0200
commit35aaba1e0da53aed44a5741ca9a3a1e2de21baf5 (patch)
treea019f70b8e76a8cd845be960fa7d9f8d21f16e64
parent78ee21c4a87d2e13c4f682162f15aa6411bea592 (diff)
[refactor] bonafide to parse eip-service.json v3
-rw-r--r--pkg/standalone/bonafide.go280
-rw-r--r--pkg/standalone/bonafide/bonafide.go158
-rw-r--r--pkg/standalone/bonafide/bonafide_integration_test.go (renamed from pkg/standalone/bonafide_integration_test.go)10
-rw-r--r--pkg/standalone/bonafide/bonafide_test.go220
-rw-r--r--pkg/standalone/bonafide/eip_service.go250
-rw-r--r--pkg/standalone/bonafide/testdata/cert (renamed from pkg/standalone/testdata/cert)0
-rw-r--r--pkg/standalone/bonafide/testdata/eip-service.json (renamed from pkg/standalone/testdata/eip-service.json)0
-rw-r--r--pkg/standalone/bonafide/testdata/eip-service3.json173
-rw-r--r--pkg/standalone/bonafide_test.go100
-rw-r--r--pkg/standalone/launcher_linux.go3
-rw-r--r--pkg/standalone/main.go5
-rw-r--r--pkg/standalone/vpn.go12
12 files changed, 817 insertions, 394 deletions
diff --git a/pkg/standalone/bonafide.go b/pkg/standalone/bonafide.go
deleted file mode 100644
index a3c0b22..0000000
--- a/pkg/standalone/bonafide.go
+++ /dev/null
@@ -1,280 +0,0 @@
-// Copyright (C) 2018 LEAP
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-package standalone
-
-import (
- "crypto/tls"
- "crypto/x509"
- "encoding/json"
- "fmt"
- "io"
- "io/ioutil"
- "log"
- "math/rand"
- "net/http"
- "sort"
- "strconv"
- "strings"
- "time"
-
- "0xacab.org/leap/bitmask-vpn/pkg/config"
-)
-
-const (
- certAPI = config.APIURL + "1/cert"
- eipAPI = config.APIURL + "1/config/eip-service.json"
- secondsPerHour = 60 * 60
- retryFetchJSONSeconds = 15
-)
-
-type bonafide struct {
- client httpClient
- tzOffsetHours int
- eip *eipService
- defaultGateway string
-}
-
-type httpClient interface {
- Post(url, contentType string, body io.Reader) (resp *http.Response, err error)
-}
-
-type eipService struct {
- Gateways []gateway
- Locations map[string]struct {
- CountryCode string
- Hemisphere string
- Name string
- Timezone string
- }
- OpenvpnConfiguration map[string]interface{} `json:"openvpn_configuration"`
-}
-
-type gateway struct {
- Capabilities struct {
- Ports []string
- Protocols []string
- }
- Host string
- IPAddress string `json:"ip_address"`
- Location string
-}
-
-type gatewayDistance struct {
- gateway gateway
- distance int
-}
-
-type geoLocation struct {
- IPAddress string `json:"ip"`
- Country string `json:"cc"`
- City string `json:"city"`
- Latitude float64 `json:"lat"`
- Longitude float64 `json:"lon"`
- SortedGateways []string `json:"gateways"`
-}
-
-func newBonafide() *bonafide {
- certs := x509.NewCertPool()
- certs.AppendCertsFromPEM(config.CaCert)
- client := &http.Client{
- Transport: &http.Transport{
- TLSClientConfig: &tls.Config{
- RootCAs: certs,
- },
- },
- }
- _, tzOffsetSeconds := time.Now().Zone()
- tzOffsetHours := tzOffsetSeconds / secondsPerHour
-
- return &bonafide{
- client: client,
- tzOffsetHours: tzOffsetHours,
- eip: nil,
- defaultGateway: "",
- }
-}
-
-func (b *bonafide) getCertPem() ([]byte, error) {
- resp, err := b.client.Post(certAPI, "", nil)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
- if resp.StatusCode != 200 {
- return nil, fmt.Errorf("get vpn cert has failed with status: %s", resp.Status)
- }
-
- return ioutil.ReadAll(resp.Body)
-}
-
-func (b *bonafide) getGateways() ([]gateway, error) {
- if b.eip == nil {
- err := b.fetchEipJSON()
- if err != nil {
- return nil, err
- }
- }
-
- return b.eip.Gateways, nil
-}
-
-func (b *bonafide) setDefaultGateway(name string) {
- b.defaultGateway = name
- b.sortGateways()
-}
-
-func (b *bonafide) getOpenvpnArgs() ([]string, error) {
- if b.eip == nil {
- err := b.fetchEipJSON()
- if err != nil {
- return nil, err
- }
- }
-
- args := []string{}
- for arg, value := range b.eip.OpenvpnConfiguration {
- switch v := value.(type) {
- case string:
- args = append(args, "--"+arg)
- args = append(args, strings.Split(v, " ")...)
- case bool:
- if v {
- args = append(args, "--"+arg)
- }
- default:
- log.Printf("Unknown openvpn argument type: %s - %v", arg, value)
- }
- }
- return args, nil
-}
-
-func (b *bonafide) fetchGeolocation() ([]string, error) {
- resp, err := b.client.Post(config.GeolocationAPI, "", nil)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
- if resp.StatusCode != 200 {
- return nil, fmt.Errorf("get geolocation failed with status: %s", resp.Status)
- }
-
- geo := &geoLocation{}
- dataJSON, err := ioutil.ReadAll(resp.Body)
- err = json.Unmarshal(dataJSON, &geo)
- if err != nil {
- _ = fmt.Errorf("get vpn cert has failed with status: %s", resp.Status)
- return nil, err
- }
-
- return geo.SortedGateways, nil
-
-}
-
-func (b *bonafide) fetchEipJSON() error {
- resp, err := b.client.Post(eipAPI, "", nil)
- for err != nil {
- log.Printf("Error fetching eip json: %v", err)
- time.Sleep(retryFetchJSONSeconds * time.Second)
- resp, err = b.client.Post(eipAPI, "", nil)
- }
- defer resp.Body.Close()
- if resp.StatusCode != 200 {
- return fmt.Errorf("get eip json has failed with status: %s", resp.Status)
- }
-
- var eip eipService
- decoder := json.NewDecoder(resp.Body)
- err = decoder.Decode(&eip)
- if err != nil {
- return err
- }
-
- b.eip = &eip
- b.sortGateways()
- return nil
-}
-
-func (b *bonafide) sortGatewaysByGeolocation(geolocatedGateways []string) []gatewayDistance {
- gws := []gatewayDistance{}
-
- for i, host := range geolocatedGateways {
- for _, gw := range b.eip.Gateways {
- if gw.Host == host {
- gws = append(gws, gatewayDistance{gw, i})
- }
- }
- }
- return gws
-}
-
-func (b *bonafide) sortGatewaysByTimezone() []gatewayDistance {
- gws := []gatewayDistance{}
-
- for _, gw := range b.eip.Gateways {
- distance := 13
- if gw.Location == b.defaultGateway {
- distance = -1
- } else {
- gwOffset, err := strconv.Atoi(b.eip.Locations[gw.Location].Timezone)
- if err != nil {
- log.Printf("Error sorting gateways: %v", err)
- } else {
- distance = tzDistance(b.tzOffsetHours, gwOffset)
- }
- }
- gws = append(gws, gatewayDistance{gw, distance})
- }
- rand.Seed(time.Now().UnixNano())
- cmp := func(i, j int) bool {
- if gws[i].distance == gws[j].distance {
- return rand.Intn(2) == 1
- }
- return gws[i].distance < gws[j].distance
- }
- sort.Slice(gws, cmp)
- return gws
-}
-
-func (b *bonafide) sortGateways() {
- gws := []gatewayDistance{}
-
- geolocatedGateways, _ := b.fetchGeolocation()
-
- if len(geolocatedGateways) > 0 {
- gws = b.sortGatewaysByGeolocation(geolocatedGateways)
- } else {
- log.Printf("Falling back to timezone heuristic for gateway selection")
- gws = b.sortGatewaysByTimezone()
- }
-
- for i, gw := range gws {
- b.eip.Gateways[i] = gw.gateway
- }
-}
-
-func tzDistance(offset1, offset2 int) int {
- abs := func(x int) int {
- if x < 0 {
- return -x
- }
- return x
- }
- distance := abs(offset1 - offset2)
- if distance > 12 {
- distance = 24 - distance
- }
- return distance
-}
diff --git a/pkg/standalone/bonafide/bonafide.go b/pkg/standalone/bonafide/bonafide.go
new file mode 100644
index 0000000..bdf6fff
--- /dev/null
+++ b/pkg/standalone/bonafide/bonafide.go
@@ -0,0 +1,158 @@
+// Copyright (C) 2018 LEAP
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package bonafide
+
+import (
+ "crypto/tls"
+ "crypto/x509"
+ "encoding/json"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "time"
+
+ "0xacab.org/leap/bitmask-vpn/pkg/config"
+)
+
+const (
+ certAPI = config.APIURL + "1/cert"
+ secondsPerHour = 60 * 60
+ retryFetchJSONSeconds = 15
+)
+
+type Bonafide struct {
+ client httpClient
+ eip *eipService
+ tzOffsetHours int
+}
+
+type Gateway struct {
+ Host string
+ IPAddress string
+ Location string
+ Ports []string
+ Protocols []string
+ Options map[string]string
+}
+
+type openvpnConfig map[string]interface{}
+
+type httpClient interface {
+ Post(url, contentType string, body io.Reader) (resp *http.Response, err error)
+}
+
+type geoLocation struct {
+ IPAddress string `json:"ip"`
+ Country string `json:"cc"`
+ City string `json:"city"`
+ Latitude float64 `json:"lat"`
+ Longitude float64 `json:"lon"`
+ SortedGateways []string `json:"gateways"`
+}
+
+func New() *Bonafide {
+ certs := x509.NewCertPool()
+ certs.AppendCertsFromPEM(config.CaCert)
+ client := &http.Client{
+ Transport: &http.Transport{
+ TLSClientConfig: &tls.Config{
+ RootCAs: certs,
+ },
+ },
+ }
+ _, tzOffsetSeconds := time.Now().Zone()
+ tzOffsetHours := tzOffsetSeconds / secondsPerHour
+
+ return &Bonafide{
+ client: client,
+ eip: nil,
+ tzOffsetHours: tzOffsetHours,
+ }
+}
+
+func (b *Bonafide) GetCertPem() ([]byte, error) {
+ resp, err := b.client.Post(certAPI, "", nil)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != 200 {
+ return nil, fmt.Errorf("get vpn cert has failed with status: %s", resp.Status)
+ }
+
+ return ioutil.ReadAll(resp.Body)
+}
+
+func (b *Bonafide) GetGateways(transport string) ([]Gateway, error) {
+ if b.eip == nil {
+ err := b.fetchEipJSON()
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return b.eip.getGateways(transport), nil
+}
+
+func (b *Bonafide) SetDefaultGateway(name string) {
+ b.eip.setDefaultGateway(name)
+ b.sortGateways()
+}
+
+func (b *Bonafide) GetOpenvpnArgs() ([]string, error) {
+ if b.eip == nil {
+ err := b.fetchEipJSON()
+ if err != nil {
+ return nil, err
+ }
+ }
+ return b.eip.getOpenvpnArgs(), nil
+}
+
+func (b *Bonafide) fetchGeolocation() ([]string, error) {
+ resp, err := b.client.Post(config.GeolocationAPI, "", nil)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != 200 {
+ return nil, fmt.Errorf("get geolocation failed with status: %s", resp.Status)
+ }
+
+ geo := &geoLocation{}
+ dataJSON, err := ioutil.ReadAll(resp.Body)
+ err = json.Unmarshal(dataJSON, &geo)
+ if err != nil {
+ _ = fmt.Errorf("get vpn cert has failed with status: %s", resp.Status)
+ return nil, err
+ }
+
+ return geo.SortedGateways, nil
+
+}
+
+func (b *Bonafide) sortGateways() {
+ geolocatedGateways, _ := b.fetchGeolocation()
+
+ if len(geolocatedGateways) > 0 {
+ b.eip.sortGatewaysByGeolocation(geolocatedGateways)
+ } else {
+ log.Printf("Falling back to timezone heuristic for gateway selection")
+ b.eip.sortGatewaysByTimezone(b.tzOffsetHours)
+ }
+}
diff --git a/pkg/standalone/bonafide_integration_test.go b/pkg/standalone/bonafide/bonafide_integration_test.go
index 5beb8aa..14fca05 100644
--- a/pkg/standalone/bonafide_integration_test.go
+++ b/pkg/standalone/bonafide/bonafide_integration_test.go
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
-package standalone
+package bonafide
import (
"bytes"
@@ -27,8 +27,8 @@ var (
)
func TestIntegrationGetCert(t *testing.T) {
- b := newBonafide()
- cert, err := b.getCertPem()
+ b := New()
+ cert, err := b.GetCertPem()
if err != nil {
t.Fatal("getCert returned an error: ", err)
}
@@ -43,8 +43,8 @@ func TestIntegrationGetCert(t *testing.T) {
}
func TestGetGateways(t *testing.T) {
- b := newBonafide()
- gateways, err := b.getGateways()
+ b := New()
+ gateways, err := b.GetGateways("openvpn")
if err != nil {
t.Fatal("getGateways returned an error: ", err)
}
diff --git a/pkg/standalone/bonafide/bonafide_test.go b/pkg/standalone/bonafide/bonafide_test.go
new file mode 100644
index 0000000..8fb7f72
--- /dev/null
+++ b/pkg/standalone/bonafide/bonafide_test.go
@@ -0,0 +1,220 @@
+// Copyright (C) 2018 LEAP
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package bonafide
+
+import (
+ "io"
+ "io/ioutil"
+ "net/http"
+ "os"
+ "reflect"
+ "strconv"
+ "strings"
+ "testing"
+)
+
+const (
+ certPath = "testdata/cert"
+ eip1Path = "testdata/eip-service.json"
+ eipPath = "testdata/eip-service3.json"
+)
+
+type client struct {
+ path string
+}
+
+func (c client) Post(url, contentType string, body io.Reader) (resp *http.Response, err error) {
+ f, err := os.Open(c.path)
+ return &http.Response{
+ Body: f,
+ StatusCode: 200,
+ }, err
+}
+
+func TestGetCert(t *testing.T) {
+ b := Bonafide{client: client{certPath}}
+ cert, err := b.GetCertPem()
+ if err != nil {
+ t.Fatal("getCert returned an error: ", err)
+ }
+
+ f, err := os.Open(certPath)
+ if err != nil {
+ t.Fatal("Can't open ", certPath, ": ", err)
+ }
+ defer f.Close()
+
+ certData, err := ioutil.ReadAll(f)
+ if err != nil {
+ t.Fatal("Can't read all: ", err)
+ }
+ if !reflect.DeepEqual(certData, cert) {
+ t.Errorf("cert doesn't match")
+ }
+}
+
+func TestGatewayTzLocation(t *testing.T) {
+ // tzOffset -> location
+ values := map[int]string{
+ -12: "c",
+ -10: "a",
+ -5: "a",
+ -3: "a",
+ -1: "b",
+ 0: "b",
+ 2: "b",
+ 5: "c",
+ 8: "c",
+ 12: "c",
+ }
+
+ for tzOffset, location := range values {
+ b := Bonafide{
+ client: client{eipPath},
+ tzOffsetHours: tzOffset,
+ }
+ gateways, err := b.GetGateways("openvpn")
+ if err != nil {
+ t.Errorf("getGateways returned an error: %v", err)
+ continue
+ }
+ if len(gateways) < 4 {
+ t.Errorf("Wrong number of gateways: %d", len(gateways))
+ continue
+
+ }
+ if gateways[0].Location != location {
+ t.Errorf("Wrong location for tz %d: %s, expected: %s", tzOffset, gateways[0].Location, location)
+ }
+ }
+}
+
+func TestOpenvpnGateways(t *testing.T) {
+ b := Bonafide{
+ client: client{eipPath},
+ }
+ gateways, err := b.GetGateways("openvpn")
+ if err != nil {
+ t.Fatalf("getGateways returned an error: %v", err)
+ }
+ if len(gateways) == 0 {
+ t.Fatalf("No obfs4 gateways found")
+ }
+
+ present := make([]bool, 6)
+ for _, g := range gateways {
+ i, err := strconv.Atoi(g.Host[0:1])
+ if err != nil {
+ t.Fatalf("unkonwn host %s: %v", g.Host, err)
+ }
+ present[i] = true
+ }
+ for i, p := range present {
+ switch i {
+ case 0:
+ continue
+ case 5:
+ if p {
+ t.Errorf("Host %d should not have obfs4 transport", i)
+ }
+ default:
+ if !p {
+ t.Errorf("Host %d should have obfs4 transport", i)
+ }
+ }
+ }
+}
+
+func TestObfs4Gateways(t *testing.T) {
+ b := Bonafide{
+ client: client{eipPath},
+ }
+ gateways, err := b.GetGateways("obfs4")
+ if err != nil {
+ t.Fatalf("getGateways returned an error: %v", err)
+ }
+ if len(gateways) == 0 {
+ t.Fatalf("No obfs4 gateways found")
+ }
+
+ present := make([]bool, 6)
+ for _, g := range gateways {
+ i, err := strconv.Atoi(g.Host[0:1])
+ if err != nil {
+ t.Fatalf("unkonwn host %s: %v", g.Host, err)
+ }
+ present[i] = true
+
+ cert, ok := g.Options["cert"]
+ if !ok {
+ t.Fatalf("No cert in options (%s): %v", g.Host, g.Options)
+ }
+ if cert != "obfs-cert" {
+ t.Errorf("No valid options given (%s): %v", g.Host, g.Options)
+ }
+ }
+ for i, p := range present {
+ switch i {
+ case 0:
+ continue
+ case 2, 4:
+ if p {
+ t.Errorf("Host %d should not have obfs4 transport", i)
+ }
+ default:
+ if !p {
+ t.Errorf("Host %d should have obfs4 transport", i)
+ }
+ }
+ }
+}
+
+type fallClient struct {
+ path string
+}
+
+func (c fallClient) Post(url, contentType string, body io.Reader) (resp *http.Response, err error) {
+ statusCode := 200
+ if strings.Contains(url, "3/config/eip-service.json") {
+ statusCode = 404
+ }
+ f, err := os.Open(c.path)
+ return &http.Response{
+ Body: f,
+ StatusCode: statusCode,
+ }, err
+}
+
+func TestEipServiceV1Fallback(t *testing.T) {
+ b := Bonafide{
+ client: fallClient{eip1Path},
+ }
+ gateways, err := b.GetGateways("obfs4")
+ if err != nil {
+ t.Fatalf("getGateways obfs4 returned an error: %v", err)
+ }
+ if len(gateways) != 0 {
+ t.Fatalf("It found some obfs4 gateways: %v", gateways)
+ }
+
+ gateways, err = b.GetGateways("openvpn")
+ if err != nil {
+ t.Fatalf("getGateways openvpn returned an error: %v", err)
+ }
+ if len(gateways) != 4 {
+ t.Fatalf("It not right number of gateways: %v", gateways)
+ }
+}
diff --git a/pkg/standalone/bonafide/eip_service.go b/pkg/standalone/bonafide/eip_service.go
new file mode 100644
index 0000000..94e303d
--- /dev/null
+++ b/pkg/standalone/bonafide/eip_service.go
@@ -0,0 +1,250 @@
+package bonafide
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "log"
+ "math/rand"
+ "sort"
+ "strconv"
+ "strings"
+ "time"
+
+ "0xacab.org/leap/bitmask-vpn/pkg/config"
+)
+
+const (
+ eip1API = config.APIURL + "1/config/eip-service.json"
+ eip3API = config.APIURL + "3/config/eip-service.json"
+)
+
+type eipService struct {
+ Gateways []gatewayV3
+ Locations map[string]location
+ OpenvpnConfiguration openvpnConfig `json:"openvpn_configuration"`
+ defaultGateway string
+}
+
+type eipServiceV1 struct {
+ Gateways []gatewayV1
+ Locations map[string]location
+ OpenvpnConfiguration openvpnConfig `json:"openvpn_configuration"`
+}
+
+type location struct {
+ CountryCode string
+ Hemisphere string
+ Name string
+ Timezone string
+}
+
+type gatewayV1 struct {
+ Capabilities struct {
+ Ports []string
+ Protocols []string
+ }
+ Host string
+ IPAddress string `json:"ip_address"`
+ Location string
+}
+
+type gatewayV3 struct {
+ Capabilities struct {
+ Transport []transportV3
+ }
+ Host string
+ IPAddress string `json:"ip_address"`
+ Location string
+}
+
+type transportV3 struct {
+ Type string
+ Protocols []string
+ Ports []string
+ Options map[string]string
+}
+
+func (b *Bonafide) fetchEipJSON() error {
+ resp, err := b.client.Post(eip3API, "", nil)
+ for err != nil {
+ log.Printf("Error fetching eip v3 json: %v", err)
+ time.Sleep(retryFetchJSONSeconds * time.Second)
+ resp, err = b.client.Post(eip3API, "", nil)
+ }
+ defer resp.Body.Close()
+
+ switch resp.StatusCode {
+ case 200:
+ b.eip, err = decodeEIP3(resp.Body)
+ case 404:
+ resp, err = b.client.Post(eip1API, "", nil)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ b.eip, err = decodeEIP1(resp.Body)
+ default:
+ return fmt.Errorf("get eip json has failed with status: %s", resp.Status)
+ }
+ if err != nil {
+ return err
+ }
+
+ b.sortGateways()
+ return nil
+}
+
+func decodeEIP3(body io.Reader) (*eipService, error) {
+ var eip eipService
+ decoder := json.NewDecoder(body)
+ err := decoder.Decode(&eip)
+ return &eip, err
+}
+
+func decodeEIP1(body io.Reader) (*eipService, error) {
+ var eip1 eipServiceV1
+ decoder := json.NewDecoder(body)
+ err := decoder.Decode(&eip1)
+ if err != nil {
+ return nil, err
+ }
+
+ eip3 := eipService{
+ Gateways: make([]gatewayV3, len(eip1.Gateways)),
+ Locations: eip1.Locations,
+ OpenvpnConfiguration: eip1.OpenvpnConfiguration,
+ }
+ for _, g := range eip1.Gateways {
+ gateway := gatewayV3{
+ Host: g.Host,
+ IPAddress: g.IPAddress,
+ Location: g.Location,
+ }
+ gateway.Capabilities.Transport = []transportV3{
+ transportV3{
+ Type: "openvpn",
+ Ports: g.Capabilities.Ports,
+ Protocols: g.Capabilities.Protocols,
+ },
+ }
+ eip3.Gateways = append(eip3.Gateways, gateway)
+ }
+ return &eip3, nil
+}
+
+func (eip eipService) getGateways(transport string) []Gateway {
+ gws := []Gateway{}
+ for _, g := range eip.Gateways {
+ for _, t := range g.Capabilities.Transport {
+ if t.Type != transport {
+ continue
+ }
+
+ gateway := Gateway{
+ Host: g.Host,
+ IPAddress: g.IPAddress,
+ Location: g.Location,
+ Ports: t.Ports,
+ Protocols: t.Protocols,
+ Options: t.Options,
+ }
+ gws = append(gws, gateway)
+ }
+ }
+ return gws
+}
+
+func (eip *eipService) setDefaultGateway(name string) {
+ eip.defaultGateway = name
+}
+
+func (eip eipService) getOpenvpnArgs() []string {
+ args := []string{}
+ for arg, value := range eip.OpenvpnConfiguration {
+ switch v := value.(type) {
+ case string:
+ args = append(args, "--"+arg)
+ args = append(args, strings.Split(v, " ")...)
+ case bool:
+ if v {
+ args = append(args, "--"+arg)
+ }
+ default:
+ log.Printf("Unknown openvpn argument type: %s - %v", arg, value)
+ }
+ }
+ return args
+}
+
+func (eip *eipService) sortGatewaysByGeolocation(geolocatedGateways []string) {
+ gws := make([]gatewayV3, len(eip.Gateways))
+
+ if eip.defaultGateway != "" {
+ for _, gw := range eip.Gateways {
+ if gw.Location == eip.defaultGateway {
+ gws = append(gws, gw)
+ break
+ }
+ }
+ }
+ for _, host := range geolocatedGateways {
+ for _, gw := range eip.Gateways {
+ if gw.Host == host {
+ gws = append(gws, gw)
+ }
+ }
+ }
+ eip.Gateways = gws
+}
+
+type gatewayDistance struct {
+ gateway gatewayV3
+ distance int
+}
+
+func (eip *eipService) sortGatewaysByTimezone(tzOffsetHours int) {
+ gws := []gatewayDistance{}
+
+ for _, gw := range eip.Gateways {
+ distance := 13
+ if gw.Location == eip.defaultGateway {
+ distance = -1
+ } else {
+ gwOffset, err := strconv.Atoi(eip.Locations[gw.Location].Timezone)
+ if err != nil {
+ log.Printf("Error sorting gateways: %v", err)
+ } else {
+ distance = tzDistance(tzOffsetHours, gwOffset)
+ }
+ }
+ gws = append(gws, gatewayDistance{gw, distance})
+ }
+ rand.Seed(time.Now().UnixNano())
+ cmp := func(i, j int) bool {
+ if gws[i].distance == gws[j].distance {
+ return rand.Intn(2) == 1
+ }
+ return gws[i].distance < gws[j].distance
+ }
+ sort.Slice(gws, cmp)
+
+ for i, gw := range gws {
+ eip.Gateways[i] = gw.gateway
+ }
+}
+
+func tzDistance(offset1, offset2 int) int {
+ abs := func(x int) int {
+ if x < 0 {
+ return -x
+ }
+ return x
+ }
+ distance := abs(offset1 - offset2)
+ if distance > 12 {
+ distance = 24 - distance
+ }
+ return distance
+}
diff --git a/pkg/standalone/testdata/cert b/pkg/standalone/bonafide/testdata/cert
index 4968b3f..4968b3f 100644
--- a/pkg/standalone/testdata/cert
+++ b/pkg/standalone/bonafide/testdata/cert
diff --git a/pkg/standalone/testdata/eip-service.json b/pkg/standalone/bonafide/testdata/eip-service.json
index d5f2413..d5f2413 100644
--- a/pkg/standalone/testdata/eip-service.json
+++ b/pkg/standalone/bonafide/testdata/eip-service.json
diff --git a/pkg/standalone/bonafide/testdata/eip-service3.json b/pkg/standalone/bonafide/testdata/eip-service3.json
new file mode 100644
index 0000000..cefd6c0
--- /dev/null
+++ b/pkg/standalone/bonafide/testdata/eip-service3.json
@@ -0,0 +1,173 @@
+{
+ "gateways": [
+ {
+ "capabilities": {
+ "adblock": false,
+ "filter_dns": false,
+ "limited": false,
+ "transport": [
+ {
+ "type": "openvpn",
+ "ports": [
+ "443"
+ ],
+ "protocols": [
+ "tcp"
+ ]
+ },
+ {
+ "type": "obfs4",
+ "ports": [
+ "2345"
+ ],
+ "protocols": [
+ "tcp"
+ ],
+ "options": {
+ "cert": "obfs-cert",
+ "iat-mode": "0"
+ }
+ }
+ ],
+ "user_ips": false
+ },
+ "host": "1.example.com",
+ "ip_address": "1.1.1.1",
+ "location": "a"
+ },
+ {
+ "capabilities": {
+ "adblock": false,
+ "filter_dns": false,
+ "limited": false,
+ "transport": [
+ {
+ "type": "openvpn",
+ "ports": [
+ "443"
+ ],
+ "protocols": [
+ "tcp"
+ ]
+ }
+ ],
+ "user_ips": false
+ },
+ "host": "2.example.com",
+ "ip_address": "2.2.2.2",
+ "location": "b"
+ },
+ {
+ "capabilities": {
+ "adblock": false,
+ "filter_dns": false,
+ "limited": false,
+ "transport": [
+ {
+ "type": "openvpn",
+ "ports": [
+ "443"
+ ],
+ "protocols": [
+ "tcp"
+ ]
+ },
+ {
+ "type": "obfs4",
+ "ports": [
+ "2345"
+ ],
+ "protocols": [
+ "tcp"
+ ],
+ "options": {
+ "cert": "obfs-cert",
+ "iat-mode": "0"
+ }
+ }
+ ],
+ "user_ips": false
+ },
+ "host": "3.example.com",
+ "ip_address": "3.3.3.3",
+ "location": "b"
+ },
+ {
+ "capabilities": {
+ "adblock": false,
+ "filter_dns": false,
+ "limited": false,
+ "transport": [
+ {
+ "type": "openvpn",
+ "ports": [
+ "443"
+ ],
+ "protocols": [
+ "tcp"
+ ]
+ }
+ ],
+ "user_ips": false
+ },
+ "host": "4.example.com",
+ "ip_address": "4.4.4.4",
+ "location": "c"
+ },
+ {
+ "capabilities": {
+ "adblock": false,
+ "filter_dns": false,
+ "limited": false,
+ "transport": [
+ {
+ "type": "obfs4",
+ "ports": [
+ "2345"
+ ],
+ "protocols": [
+ "tcp"
+ ],
+ "options": {
+ "cert": "obfs-cert",
+ "iat-mode": "0"
+ }
+ }
+ ],
+ "user_ips": false
+ },
+ "host": "5.example.com",
+ "ip_address": "5.5.5.5",
+ "location": "c"
+ }
+ ],
+ "locations": {
+ "a": {
+ "country_code": "AA",
+ "hemisphere": "N",
+ "name": "a",
+ "timezone": "-5"
+ },
+ "b": {
+ "country_code": "BB",
+ "hemisphere": "S",
+ "name": "b",
+ "timezone": "+1"
+ },
+ "c": {
+ "country_code": "CC",
+ "hemisphere": "N",
+ "name": "c",
+ "timezone": "+8"
+ }
+ },
+ "openvpn_configuration": {
+ "auth": "SHA1",
+ "cipher": "AES-128-CBC",
+ "keepalive": "10 30",
+ "tls-cipher": "DHE-RSA-AES128-SHA",
+ "tun-ipv6": true
+ },
+ "serial": 1,
+ "version": 3
+}
diff --git a/pkg/standalone/bonafide_test.go b/pkg/standalone/bonafide_test.go
deleted file mode 100644
index 7bfcaa0..0000000
--- a/pkg/standalone/bonafide_test.go
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright (C) 2018 LEAP
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-package standalone
-
-import (
- "io"
- "io/ioutil"
- "net/http"
- "os"
- "reflect"
- "testing"
-)
-
-const (
- certPath = "testdata/cert"
- eipPath = "testdata/eip-service.json"
-)
-
-type client struct {
- path string
-}
-
-func (c client) Post(url, contentType string, body io.Reader) (resp *http.Response, err error) {
- f, err := os.Open(c.path)
- return &http.Response{
- Body: f,
- StatusCode: 200,
- }, err
-}
-
-func TestGetCert(t *testing.T) {
- b := bonafide{client: client{certPath}}
- cert, err := b.getCertPem()
- if err != nil {
- t.Fatal("getCert returned an error: ", err)
- }
-
- f, err := os.Open(certPath)
- if err != nil {
- t.Fatal("Can't open ", certPath, ": ", err)
- }
- defer f.Close()
-
- certData, err := ioutil.ReadAll(f)
- if err != nil {
- t.Fatal("Can't read all: ", err)
- }
- if !reflect.DeepEqual(certData, cert) {
- t.Errorf("cert doesn't match")
- }
-}
-
-func TestGatewayTzLocation(t *testing.T) {
- // tzOffset -> location
- values := map[int]string{
- -12: "c",
- -10: "a",
- -5: "a",
- -3: "a",
- -1: "b",
- 0: "b",
- 2: "b",
- 5: "c",
- 8: "c",
- 12: "c",
- }
-
- for tzOffset, location := range values {
- b := bonafide{
- client: client{eipPath},
- tzOffsetHours: tzOffset,
- }
- gateways, err := b.getGateways()
- if err != nil {
- t.Errorf("getGateways returned an error: %v", err)
- continue
- }
- if len(gateways) < 4 {
- t.Errorf("Wrong number of gateways: %d", len(gateways))
- continue
-
- }
- if gateways[0].Location != location {
- t.Errorf("Wrong location for tz %d: %s, expected: %s", tzOffset, gateways[0].Location, location)
- }
- }
-}
diff --git a/pkg/standalone/launcher_linux.go b/pkg/standalone/launcher_linux.go
index 39355c9..f2d0392 100644
--- a/pkg/standalone/launcher_linux.go
+++ b/pkg/standalone/launcher_linux.go
@@ -24,6 +24,7 @@ import (
"strings"
"0xacab.org/leap/bitmask-vpn/pkg/config"
+ "0xacab.org/leap/bitmask-vpn/pkg/standalone/bonafide"
"github.com/mitchellh/go-ps"
)
@@ -144,7 +145,7 @@ func (l *launcher) openvpnStop() error {
return runBitmaskRoot("openvpn", "stop")
}
-func (l *launcher) firewallStart(gateways []gateway) error {
+func (l *launcher) firewallStart(gateways []bonafide.Gateway) error {
log.Println("firewall start")
arg := []string{"firewall", "start"}
for _, gw := range gateways {
diff --git a/pkg/standalone/main.go b/pkg/standalone/main.go
index 319af79..e19634c 100644
--- a/pkg/standalone/main.go
+++ b/pkg/standalone/main.go
@@ -21,6 +21,7 @@ import (
"os"
"0xacab.org/leap/bitmask-vpn/pkg/config"
+ "0xacab.org/leap/bitmask-vpn/pkg/standalone/bonafide"
"github.com/apparentlymart/go-openvpn-mgmt/openvpn"
)
@@ -29,7 +30,7 @@ type Bitmask struct {
tempdir string
statusCh chan string
managementClient *openvpn.MgmtClient
- bonafide *bonafide
+ bonafide *bonafide.Bonafide
launch *launcher
}
@@ -40,7 +41,7 @@ func Init() (*Bitmask, error) {
if err != nil {
return nil, err
}
- bonafide := newBonafide()
+ bonafide := bonafide.New()
launch, err := newLauncher()
if err != nil {
return nil, err
diff --git a/pkg/standalone/vpn.go b/pkg/standalone/vpn.go
index b858ee0..260eec1 100644
--- a/pkg/standalone/vpn.go
+++ b/pkg/standalone/vpn.go
@@ -28,7 +28,7 @@ const (
// StartVPN for provider
func (b *Bitmask) StartVPN(provider string) error {
- gateways, err := b.bonafide.getGateways()
+ gateways, err := b.bonafide.GetGateways("openvpn")
if err != nil {
return err
}
@@ -42,7 +42,7 @@ func (b *Bitmask) StartVPN(provider string) error {
return err
}
- arg, err := b.bonafide.getOpenvpnArgs()
+ arg, err := b.bonafide.GetOpenvpnArgs()
if err != nil {
return err
}
@@ -63,7 +63,7 @@ func (b *Bitmask) getCert() (certPath string, err error) {
certPath = b.getCertPemPath()
if _, err := os.Stat(certPath); os.IsNotExist(err) {
- cert, err := b.bonafide.getCertPem()
+ cert, err := b.bonafide.GetCertPem()
if err != nil {
return "", err
}
@@ -95,7 +95,7 @@ func (b *Bitmask) ReloadFirewall() error {
}
if status != Off {
- gateways, err := b.bonafide.getGateways()
+ gateways, err := b.bonafide.GetGateways("openvpn")
if err != nil {
return err
}
@@ -129,7 +129,7 @@ func (b *Bitmask) VPNCheck() (helpers bool, priviledge bool, err error) {
// ListGateways return the names of the gateways
func (b *Bitmask) ListGateways(provider string) ([]string, error) {
- gateways, err := b.bonafide.getGateways()
+ gateways, err := b.bonafide.GetGateways("openvpn")
if err != nil {
return nil, err
}
@@ -142,7 +142,7 @@ func (b *Bitmask) ListGateways(provider string) ([]string, error) {
// UseGateway selects name as the default gateway
func (b *Bitmask) UseGateway(name string) error {
- b.bonafide.setDefaultGateway(name)
+ b.bonafide.SetDefaultGateway(name)
return nil
}