From bddadc7323d6467f5233f26b97652fe671d77eed Mon Sep 17 00:00:00 2001 From: "kali kaneko (leap communications)" Date: Thu, 23 Dec 2021 00:43:29 +0100 Subject: [ui] expose bonafide+snowflake bootstrap events --- pkg/backend/api.go | 1 + pkg/backend/status.go | 82 +++++++++++++++++++++++++---------------- pkg/bitmask/bitmask.go | 8 ++++ pkg/snowflake/bootstrap.go | 73 +++++++++++++++++++++++++++++------- pkg/vpn/bonafide/bonafide.go | 24 +++++++++--- pkg/vpn/bonafide/eip_service.go | 12 ++++++ pkg/vpn/bonafide/gateways.go | 13 ++++--- pkg/vpn/main.go | 7 ++++ pkg/vpn/openvpn.go | 1 + 9 files changed, 166 insertions(+), 55 deletions(-) diff --git a/pkg/backend/api.go b/pkg/backend/api.go index ba07adf..02aa383 100644 --- a/pkg/backend/api.go +++ b/pkg/backend/api.go @@ -37,6 +37,7 @@ func Login(username, password string) { ctx.LoginDialog = true ctx.Errors = "bad_auth" } + // XXX shouldn't this be statusChanged? go ctx.updateStatus() } diff --git a/pkg/backend/status.go b/pkg/backend/status.go index de6364f..f6b195c 100644 --- a/pkg/backend/status.go +++ b/pkg/backend/status.go @@ -33,36 +33,38 @@ var updateMutex sync.Mutex // them. type connectionCtx struct { - AppName string `json:"appName"` - Provider string `json:"provider"` - TosURL string `json:"tosURL"` - HelpURL string `json:"helpURL"` - AskForDonations bool `json:"askForDonations"` - DonateDialog bool `json:"donateDialog"` - DonateURL string `json:"donateURL"` - LoginDialog bool `json:"loginDialog"` - LoginOk bool `json:"loginOk"` - Version string `json:"version"` - Errors string `json:"errors"` - Status status `json:"status"` - Locations map[string]float64 `json:"locations"` - LocationLabels map[string][]string `json:"locationLabels"` - CurrentGateway string `json:"currentGateway"` - CurrentLocation string `json:"currentLocation"` - CurrentCountry string `json:"currentCountry"` - BestLocation string `json:"bestLocation"` - Transport string `json:"transport"` - UseUDP bool `json:"udp"` - OffersUDP bool `json:"offersUdp"` - ManualLocation bool `json:"manualLocation"` - IsReady bool `json:"isReady"` - CanUpgrade bool `json:"canUpgrade"` - Motd string `json:"motd"` - HasTor bool `json:"hasTor"` - UseSnowflake bool `json:"snowflake"` - bm bitmask.Bitmask - autostart bitmask.Autostart - cfg *config.Config + AppName string `json:"appName"` + Provider string `json:"provider"` + TosURL string `json:"tosURL"` + HelpURL string `json:"helpURL"` + AskForDonations bool `json:"askForDonations"` + DonateDialog bool `json:"donateDialog"` + DonateURL string `json:"donateURL"` + LoginDialog bool `json:"loginDialog"` + LoginOk bool `json:"loginOk"` + Version string `json:"version"` + Errors string `json:"errors"` + Status status `json:"status"` + Locations map[string]float64 `json:"locations"` + LocationLabels map[string][]string `json:"locationLabels"` + CurrentGateway string `json:"currentGateway"` + CurrentLocation string `json:"currentLocation"` + CurrentCountry string `json:"currentCountry"` + BestLocation string `json:"bestLocation"` + Transport string `json:"transport"` + UseUDP bool `json:"udp"` + OffersUDP bool `json:"offersUdp"` + ManualLocation bool `json:"manualLocation"` + IsReady bool `json:"isReady"` + CanUpgrade bool `json:"canUpgrade"` + Motd string `json:"motd"` + HasTor bool `json:"hasTor"` + UseSnowflake bool `json:"snowflake"` + SnowflakeProgress int `json:"snowflakeProgress"` + SnowflakeTag string `json:"snowflakeTag"` + bm bitmask.Bitmask + autostart bitmask.Autostart + cfg *config.Config } func (c *connectionCtx) toJson() ([]byte, error) { @@ -78,7 +80,7 @@ func (c *connectionCtx) toJson() ([]byte, error) { c.Transport = transport c.UseUDP = c.cfg.UDP // TODO initialize bitmask param? c.OffersUDP = c.bm.OffersUDP() - c.UseSnowflake = c.cfg.Snowflake // TODO initialize bitmask param? + c.UseSnowflake = c.cfg.Snowflake // TODO initialize bitmask c.ManualLocation = c.bm.IsManualLocation() c.CanUpgrade = c.bm.CanUpgrade() c.Motd = c.bm.GetMotd() @@ -102,6 +104,16 @@ func (c connectionCtx) updateStatus() { setStatusFromStr(stStr) } + go func() { + snowflakeCh := c.bm.GetSnowflakeCh() + for { + select { + case event := <-snowflakeCh: + setSnowflakeStatus(event) + } + } + }() + statusCh := c.bm.GetStatusCh() for { select { @@ -111,6 +123,14 @@ func (c connectionCtx) updateStatus() { } } +func setSnowflakeStatus(event *snowflake.StatusEvent) { + statusMutex.Lock() + defer statusMutex.Unlock() + ctx.SnowflakeProgress = event.Progress + ctx.SnowflakeTag = event.Tag + go trigger(OnStatusChanged) +} + func setStatus(st status) { statusMutex.Lock() defer statusMutex.Unlock() diff --git a/pkg/bitmask/bitmask.go b/pkg/bitmask/bitmask.go index 0c2a344..54a5b06 100644 --- a/pkg/bitmask/bitmask.go +++ b/pkg/bitmask/bitmask.go @@ -15,8 +15,16 @@ package bitmask +import ( + "0xacab.org/leap/bitmask-vpn/pkg/snowflake" +) + +// XXX this interface is a relic of a time in which we had a dual implementation. +// Nowadays it could be deprecated. + type Bitmask interface { GetStatusCh() <-chan string + GetSnowflakeCh() <-chan *snowflake.StatusEvent Close() Version() (string, error) StartVPN(provider string) error diff --git a/pkg/snowflake/bootstrap.go b/pkg/snowflake/bootstrap.go index 0f370fa..5e90b0e 100644 --- a/pkg/snowflake/bootstrap.go +++ b/pkg/snowflake/bootstrap.go @@ -9,21 +9,57 @@ import ( "log" "net/http" "os" + "path/filepath" + "strconv" + "strings" "time" "0xacab.org/leap/bitmask-vpn/pkg/config" "github.com/cretz/bine/tor" ) +// TODO +// [ ] fix snowflake-client binary +// [ ] find tor path + const torrc = `UseBridges 1 DataDirectory datadir -ClientTransportPlugin snowflake exec /usr/local/bin/snowflake-client \ --url https://snowflake-broker.torproject.net.global.prod.fastly.net/ -front cdn.sstatic.net \ --ice stun:stun.voip.blackberry.com:3478,stun:stun.altar.com.pl:3478,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.sonetel.net:3478,stun:stun.stunprotocol.org:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 \ --max 3 +ClientTransportPlugin snowflake exec /usr/local/bin/snowflake-client -log /tmp/snowflake.log -url https://snowflake-broker.torproject.net.global.prod.fastly.net/ \ +-front cdn.sstatic.net -ice stun:stun.voip.blackberry.com:3478,stun:stun.altar.com.pl:3478,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.sonetel.net:3478,stun:stun.stunprotocol.org:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 \ +-max 5 + +Bridge snowflake 192.0.2.3:1 + +SocksPort auto` + +type StatusEvent struct { + Progress int + Tag string +} + +type StatusLogger struct { + ch chan *StatusEvent +} -Bridge snowflake 0.0.3.0:1` +func (e *StatusLogger) Write(p []byte) (n int, err error) { + raw := strings.Split(string(p), ":") + if len(raw) > 1 { + l := raw[1] + parts := strings.Split(string(l), " ") + if len(parts) > 2 && parts[2] == "STATUS_CLIENT" { + if parts[4] == "BOOTSTRAP" { + if len(parts) > 6 { + pr, _ := strconv.Atoi(parts[5][9:]) + event := &StatusEvent{Progress: pr, Tag: parts[6][4:]} + go func() { e.ch <- event }() + } + fmt.Println() + } + } + } + return len(p), nil +} func writeTorrc() string { f, err := ioutil.TempFile("", "torrc-snowflake-") @@ -34,9 +70,14 @@ func writeTorrc() string { return f.Name() } -func BootstrapWithSnowflakeProxies() error { +// TODO pass provider api +func BootstrapWithSnowflakeProxies(provider string, api string, ch chan *StatusEvent) error { rcfile := writeTorrc() - conf := &tor.StartConf{DebugWriter: os.Stdout, TorrcFile: rcfile} + logger := &StatusLogger{ch} + conf := &tor.StartConf{ + DebugWriter: logger, + TorrcFile: rcfile, + } fmt.Println("Starting Tor and fetching files to bootstrap VPN tunnel...") fmt.Println("") @@ -78,14 +119,18 @@ func BootstrapWithSnowflakeProxies() error { Timeout: time.Minute * 5, } - // XXX parametrize these urls - fetchFile(apiClient, "https://api.black.riseup.net/3/config/eip-service.json") - fetchFile(apiClient, "https://api.black.riseup.net/3/cert") + eipUri := "https://" + api + "/3/config/eip-service.json" + eipFile := filepath.Join(config.Path, provider+"-eip.json") + fetchFile(apiClient, eipUri, eipFile) + + certUri := "https://" + api + "/3/cert" + certFile := filepath.Join(config.Path, provider+".pem") + fetchFile(apiClient, certUri, certFile) return nil } -func fetchFile(client *http.Client, uri string) error { +func fetchFile(client *http.Client, uri string, file string) error { resp, err := client.Get(uri) if err != nil { return err @@ -96,6 +141,8 @@ func fetchFile(client *http.Client, uri string) error { if err != nil { log.Println(err) } - fmt.Println(string(c)) - return nil + if os.Getenv("DEBUG") == "1" { + fmt.Println(string(c)) + } + return ioutil.WriteFile(file, c, 0600) } diff --git a/pkg/vpn/bonafide/bonafide.go b/pkg/vpn/bonafide/bonafide.go index 024a7e1..129845f 100644 --- a/pkg/vpn/bonafide/bonafide.go +++ b/pkg/vpn/bonafide/bonafide.go @@ -54,6 +54,8 @@ type Bonafide struct { maxGateways int auth authentication token []byte + SnowflakeCh chan *snowflake.StatusEvent + snowflake bool } type openvpnConfig map[string]interface{} @@ -206,7 +208,6 @@ func (b *Bonafide) GetPemCertificateNoDNS() ([]byte, error) { return nil, err } defer resp.Body.Close() - return ioutil.ReadAll(resp.Body) } @@ -241,8 +242,18 @@ func (b *Bonafide) getURLNoDNS(object string) string { } func (b *Bonafide) maybeInitializeEIP() error { + // FIXME - use config/bitmask flag if os.Getenv("SNOWFLAKE") == "1" { - snowflake.BootstrapWithSnowflakeProxies() + p := strings.ToLower(config.Provider) + // FIXME only if progress != 100 %, then just pick files. + // we probably need another status watcher internally, to keep track + // of whether we need to cancel, or just wait. + snowflake.BootstrapWithSnowflakeProxies(p, getAPIAddr(p), b.SnowflakeCh) + err := b.parseEipJSONFromFile() + if err != nil { + return err + } + b.gateways = newGatewayPool(b.eip) } else { if b.eip == nil { err := b.fetchEipJSON() @@ -272,11 +283,11 @@ func (b *Bonafide) GetGateways(transport string) ([]Gateway, error) { if err != nil { return nil, err } + max := maxGateways if b.maxGateways != 0 { max = b.maxGateways } - gws, err := b.gateways.getBest(transport, b.tzOffsetHours, max) return gws, err } @@ -285,6 +296,7 @@ func (b *Bonafide) GetGateways(transport string) ([]Gateway, error) { // if "any" is provided it will return all gateways for all transports func (b *Bonafide) GetAllGateways(transport string) ([]Gateway, error) { err := b.maybeInitializeEIP() + // XXX needs to wait for bonafide too if err != nil { return nil, err } @@ -327,8 +339,10 @@ func (b *Bonafide) GetGatewayByIP(ip string) (Gateway, error) { } func (b *Bonafide) fetchGatewaysFromMenshen() 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. - So this is a workaround until we streamline that behavior */ + /* FIXME in float deployments, geolocation is served on + * gemyip.domain/json, with a LE certificate, but in riseup is served + * behind the api certificate. So this is a workaround until we + * streamline that behavior */ resp, err := b.client.Post(config.GeolocationAPI, "", nil) if err != nil { client := &http.Client{} diff --git a/pkg/vpn/bonafide/eip_service.go b/pkg/vpn/bonafide/eip_service.go index 1b8dc01..5b4c3df 100644 --- a/pkg/vpn/bonafide/eip_service.go +++ b/pkg/vpn/bonafide/eip_service.go @@ -6,6 +6,7 @@ import ( "io" "log" "os" + "path/filepath" "strings" "time" @@ -148,6 +149,17 @@ func (b *Bonafide) fetchEipJSON() error { return nil } +func (b *Bonafide) parseEipJSONFromFile() error { + provider := strings.ToLower(config.Provider) + eipFile := filepath.Join(config.Path, provider+"-eip.json") + f, err := os.Open(eipFile) + if err != nil { + return err + } + b.eip, err = decodeEIP3(f) + return err +} + func decodeEIP3(body io.Reader) (*eipService, error) { var eip eipService decoder := json.NewDecoder(body) diff --git a/pkg/vpn/bonafide/gateways.go b/pkg/vpn/bonafide/gateways.go index c442e72..25ab027 100644 --- a/pkg/vpn/bonafide/gateways.go +++ b/pkg/vpn/bonafide/gateways.go @@ -306,16 +306,17 @@ func (p *gatewayPool) getBestLocation(transport string, tz int) string { } func (p *gatewayPool) getAll(transport string, tz int) ([]Gateway, error) { - /* - if (&gatewayPool{} == p) { - log.Println("getAll tried to access uninitialized struct") - return []Gateway{}, nil - } - */ + if (&gatewayPool{} == p) { + log.Println("getAll tried to access uninitialized struct") + return []Gateway{}, nil + } + log.Println(">>> in getAll") + log.Println("seems to be initialized...") if p.recommended == nil || len(p.recommended) == 0 { return p.getGatewaysFromMenshen(transport, 999) } + log.Println(">>> by timezone") return p.getGatewaysByTimezone(transport, tz, 999) } diff --git a/pkg/vpn/main.go b/pkg/vpn/main.go index d780afe..cd21b1b 100644 --- a/pkg/vpn/main.go +++ b/pkg/vpn/main.go @@ -25,6 +25,7 @@ import ( "0xacab.org/leap/bitmask-vpn/pkg/config" "0xacab.org/leap/bitmask-vpn/pkg/config/version" "0xacab.org/leap/bitmask-vpn/pkg/motd" + "0xacab.org/leap/bitmask-vpn/pkg/snowflake" "0xacab.org/leap/bitmask-vpn/pkg/vpn/bonafide" "0xacab.org/leap/shapeshifter" "github.com/apparentlymart/go-openvpn-mgmt/openvpn" @@ -59,7 +60,9 @@ func Init() (*Bitmask, error) { if err != nil { return nil, err } + snowCh := make(chan *snowflake.StatusEvent, 20) bf := bonafide.New() + bf.SnowflakeCh = snowCh launch, err := newLauncher() if err != nil { return nil, err @@ -121,6 +124,10 @@ func (b *Bitmask) GetStatusCh() <-chan string { return b.statusCh } +func (b *Bitmask) GetSnowflakeCh() <-chan *snowflake.StatusEvent { + return b.bonafide.SnowflakeCh +} + // Close the connection to bitmask, and does cleanup of temporal files func (b *Bitmask) Close() { log.Printf("Close: cleanup and vpn shutdown...") diff --git a/pkg/vpn/openvpn.go b/pkg/vpn/openvpn.go index d0fadc1..567b912 100644 --- a/pkg/vpn/openvpn.go +++ b/pkg/vpn/openvpn.go @@ -201,6 +201,7 @@ func (b *Bitmask) getCert() (certPath string, err error) { failed := false persistentCertFile := filepath.Join(config.Path, strings.ToLower(config.Provider)+".pem") if _, err := os.Stat(persistentCertFile); !os.IsNotExist(err) && isValidCert(persistentCertFile) { + // TODO snowflake might have written a cert here // reuse cert. for the moment we're not writing one there, this is // only to allow users to get certs off-band and place them there // as a last-resort fallback for circumvention. -- cgit v1.2.3