diff --git a/gui/backend.go b/gui/backend.go index 96f50de468ce280a47c9ce3d626a1484f314e7d6..3e312cf2c5dea6349349943b873174a7c794cace 100644 --- a/gui/backend.go +++ b/gui/backend.go @@ -30,6 +30,16 @@ func SwitchOff() { backend.SwitchOff() } +//export UseGateway +func UseGateway(label string) { + backend.UseGateway(label) +} + +//export UseTransport +func UseTransport(transport string) { + backend.UseTransport(transport) +} + //export Quit func Quit() { backend.Quit() diff --git a/pkg/backend/actions.go b/pkg/backend/actions.go index e45b026e07b29a0378d23e00684892648302e4bb..6e143f9e49370039567b907af3f0a53fff8722e2 100644 --- a/pkg/backend/actions.go +++ b/pkg/backend/actions.go @@ -19,3 +19,23 @@ func stopVPN() { log.Println(err) } } + +// TODO return bool? +func useGateway(label string) { + err := ctx.bm.UseGateway(label) + if err != nil { + log.Println(err) + } +} + +func getGateway() string { + return ctx.bm.GetCurrentGateway() +} + +// TODO return bool? +func useTransport(transport string) { + err := ctx.bm.UseTransport(transport) + if err != nil { + log.Println(err) + } +} diff --git a/pkg/backend/api.go b/pkg/backend/api.go index 293dd9e636fedd203f59b1add96670faf814af49..8d6d0496d04a4302bc8bd0ab54ea202f5a640215 100644 --- a/pkg/backend/api.go +++ b/pkg/backend/api.go @@ -54,6 +54,16 @@ func SwitchOff() { go stopVPN() } +// TODO implement Reconnect? + +func UseGateway(label string) { + ctx.bm.UseGateway(label) +} + +func UseTransport(label string) { + ctx.bm.UseTransport(label) +} + func Quit() { if ctx.autostart != nil { ctx.autostart.Disable() diff --git a/pkg/backend/webapi.go b/pkg/backend/webapi.go index 568980d0c1660cb56883ad4eed25e1b0dc20229d..11abc2481d64522a000e3dffcfd49a7930d51b58 100644 --- a/pkg/backend/webapi.go +++ b/pkg/backend/webapi.go @@ -1,11 +1,13 @@ package backend import ( + "encoding/json" "fmt" "log" "net/http" "os" "strconv" + "time" "0xacab.org/leap/bitmask-vpn/pkg/bitmask" ) @@ -36,6 +38,52 @@ func webStatus(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, ctx.Status.String()) } +func webGatewayGet(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, ctx.bm.GetCurrentGateway()) +} + +func webGatewaySet(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case "POST": + if err := r.ParseForm(); err != nil { + fmt.Fprintf(w, "ParseForm() err: %v", err) + return + } + gwLabel := r.FormValue("gw") + fmt.Fprintf(w, "selected gateway: %s\n", gwLabel) + // FIXME catch error here, return it (error code) + useGateway(gwLabel) + // TODO make sure we don't tear the fw down on reconnect... + SwitchOff() + // a little sleep is needed, though, because iptables takes some time + time.Sleep(500 * time.Millisecond) + SwitchOn() + default: + fmt.Fprintf(w, "Only POST supported.") + } +} + +func webGatewayList(w http.ResponseWriter, r *http.Request) { + gws, err := ctx.bm.ListGateways(ctx.Provider) + if err != nil { + fmt.Fprintf(w, "ListGateways() err: %v", err) + } + gwJson, _ := json.Marshal(gws) + fmt.Fprintf(w, string(gwJson)) +} + +// TODO +func webTransportGet(w http.ResponseWriter, r *http.Request) { +} + +// TODO +func webTransportSet(w http.ResponseWriter, r *http.Request) { +} + +// TODO +func webTransportList(w http.ResponseWriter, r *http.Request) { +} + func webQuit(w http.ResponseWriter, r *http.Request) { log.Println("Web UI: quit") Quit() @@ -48,6 +96,12 @@ func enableWebAPI(port int) { token := bitmask.ReadAuthToken() http.Handle("/vpn/start", CheckAuth(http.HandlerFunc(webOn), token)) http.Handle("/vpn/stop", CheckAuth(http.HandlerFunc(webOff), token)) + http.Handle("/vpn/gw/get", CheckAuth(http.HandlerFunc(webGatewayGet), token)) + http.Handle("/vpn/gw/set", CheckAuth(http.HandlerFunc(webGatewaySet), token)) + http.Handle("/vpn/gw/list", CheckAuth(http.HandlerFunc(webGatewayList), token)) + //http.Handle("/vpn/transport/get", CheckAuth(http.HandlerFunc(webTransportGet), token)) + //http.Handle("/vpn/transport/set", CheckAuth(http.HandlerFunc(webTransportSet), token)) + //http.Handle("/vpn/transport/list", CheckAuth(http.HandlerFunc(webTransportList), token)) http.Handle("/vpn/status", CheckAuth(http.HandlerFunc(webStatus), token)) http.Handle("/vpn/quit", CheckAuth(http.HandlerFunc(webQuit), token)) http.ListenAndServe(":"+strconv.Itoa(port), nil) diff --git a/pkg/bitmask/bitmask.go b/pkg/bitmask/bitmask.go index adfc8491cb89f0374507488ce6225133c9f977b1..7ffe01ab654af6a250af8886ff8dd1feb4e14288 100644 --- a/pkg/bitmask/bitmask.go +++ b/pkg/bitmask/bitmask.go @@ -1,4 +1,4 @@ -// Copyright (C) 2018 LEAP +// Copyright (C) 2018-2020 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 @@ -28,6 +28,7 @@ type Bitmask interface { VPNCheck() (helpers bool, priviledge bool, err error) ListGateways(provider string) ([]string, error) UseGateway(name string) error + GetCurrentGateway() string UseTransport(transport string) error NeedsCredentials() bool DoLogin(username, password string) (bool, error) diff --git a/pkg/vpn/bonafide/bonafide.go b/pkg/vpn/bonafide/bonafide.go index 22e305104147a4ac22442df23dc58d374a082768..8b60641344d6417f029f37a1ede34de6560c127b 100644 --- a/pkg/vpn/bonafide/bonafide.go +++ b/pkg/vpn/bonafide/bonafide.go @@ -197,6 +197,8 @@ func (b *Bonafide) maybeInitializeEIP() error { return nil } +// GetGateways filters by transport, and will return the maximum number defined +// in bonafide.maxGateways, or the maximum by default (3). func (b *Bonafide) GetGateways(transport string) ([]Gateway, error) { err := b.maybeInitializeEIP() if err != nil { @@ -211,6 +213,17 @@ func (b *Bonafide) GetGateways(transport string) ([]Gateway, error) { return gws, err } +// GetAllGateways only filters gateways by transport. +// TODO could pass "any" instead? +func (b *Bonafide) GetAllGateways(transport string) ([]Gateway, error) { + err := b.maybeInitializeEIP() + if err != nil { + return nil, err + } + gws, err := b.gateways.getAll(transport, b.tzOffsetHours) + return gws, err +} + func (b *Bonafide) SetManualGateway(label string) { b.gateways.setUserChoice(label) } @@ -219,6 +232,10 @@ func (b *Bonafide) SetAutomaticGateway() { b.gateways.setAutomaticChoice() } +func (b *Bonafide) GetGatewayByIP(ip string) (Gateway, error) { + return b.gateways.getGatewayByIP(ip) +} + /* TODO this still needs to be called periodically */ func (b *Bonafide) fetchGatewayRanking() error { /* FIXME in float deployments, geolocation is served on gemyip.domain/json, with a LE certificate, but in riseup is served behind the api certificate. diff --git a/pkg/vpn/bonafide/gateways.go b/pkg/vpn/bonafide/gateways.go index 6084985e9668820c5bcee45e02e3a2fef1399a73..d97353048cf7e1092fe2ed2a69b8446800635479 100644 --- a/pkg/vpn/bonafide/gateways.go +++ b/pkg/vpn/bonafide/gateways.go @@ -142,6 +142,14 @@ func (p *gatewayPool) getBest(transport string, tz, max int) ([]Gateway, error) } } +func (p *gatewayPool) getAll(transport string, tz int) ([]Gateway, error) { + if len(p.ranked) != 0 { + return p.getGatewaysByServiceRank(transport, 999) + } else { + return p.getGatewaysByTimezone(transport, tz, 999) + } +} + func (p *gatewayPool) getGatewaysByServiceRank(transport string, max int) ([]Gateway, error) { gws := make([]Gateway, 0) for _, host := range p.ranked { diff --git a/pkg/vpn/main.go b/pkg/vpn/main.go index 9ddd9fd1eb5a8025243d3e999ec354259e9b1ffe..29b843b3ce3a9081028f9deab124981c393f5907 100644 --- a/pkg/vpn/main.go +++ b/pkg/vpn/main.go @@ -29,7 +29,7 @@ import ( // Bitmask holds the bitmask client data type Bitmask struct { tempdir string - onGateway string + onGateway bonafide.Gateway statusCh chan string managementClient *openvpn.MgmtClient bonafide *bonafide.Bonafide @@ -45,12 +45,12 @@ func Init() (*Bitmask, error) { if err != nil { return nil, err } - bonafide := bonafide.New() + bf := bonafide.New() launch, err := newLauncher() if err != nil { return nil, err } - b := Bitmask{tempdir, "", statusCh, nil, bonafide, launch, "", nil} + b := Bitmask{tempdir, bonafide.Gateway{}, statusCh, nil, bf, launch, "", nil} /* TODO -- we still want to do this, since it resets the fw/vpn if running diff --git a/pkg/vpn/openvpn.go b/pkg/vpn/openvpn.go index b6593f21559944a9727e03bd96da816ba9e99c60..38a64a9596ef9a0a714bf227fed42d865e05447f 100644 --- a/pkg/vpn/openvpn.go +++ b/pkg/vpn/openvpn.go @@ -230,22 +230,23 @@ func (b *Bitmask) VPNCheck() (helpers bool, privilege bool, err error) { return b.launch.check() } -// ListGateways return the names of the gateways +// ListGateways return the labels of the gateways (only for transport=openvpn, at the moment) +// TODO return other transports too func (b *Bitmask) ListGateways(provider string) ([]string, error) { - gateways, err := b.bonafide.GetGateways("openvpn") + gateways, err := b.bonafide.GetAllGateways("openvpn") if err != nil { return nil, err } gatewayNames := make([]string, len(gateways)) for i, gw := range gateways { - gatewayNames[i] = gw.Location + gatewayNames[i] = gw.Label } return gatewayNames, nil } -// UseGateway selects name as the default gateway -func (b *Bitmask) UseGateway(name string) error { - b.bonafide.SetManualGateway(name) +// UseGateway selects a gateway, by label, as the default gateway +func (b *Bitmask) UseGateway(label string) error { + b.bonafide.SetManualGateway(label) return nil } diff --git a/pkg/vpn/status.go b/pkg/vpn/status.go index 790127679595ad647770f73b1d9db2c947e0cb05..005db7e903fe7414347f9b4e9ac06889d2246c49 100644 --- a/pkg/vpn/status.go +++ b/pkg/vpn/status.go @@ -73,13 +73,23 @@ func (b *Bitmask) eventHandler(eventCh <-chan openvpn.Event) { b.statusCh <- status } if statusName == "CONNECTED" { - b.onGateway = strings.Split(stateEvent.String(), ": ")[1] - log.Println(">>> CONNECTED TO", b.onGateway) + ip := strings.Split(stateEvent.String(), ": ")[1] + gw, err := b.bonafide.GetGatewayByIP(ip) + if err == nil { + b.onGateway = gw + log.Println("Connected to gateway:", b.onGateway.Label) + } else { + log.Println("ERROR: connected to unknown gateway", ip) + } } } b.statusCh <- Off } +func (b *Bitmask) GetCurrentGateway() string { + return b.onGateway.Label +} + func (b *Bitmask) getOpenvpnState() (string, error) { if b.managementClient == nil { return "", fmt.Errorf("No management connected")