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")