diff options
-rw-r--r-- | gui/backend.go | 5 | ||||
-rw-r--r-- | gui/gui.qrc | 1 | ||||
-rw-r--r-- | gui/handlers.cpp | 10 | ||||
-rw-r--r-- | gui/handlers.h | 2 | ||||
-rw-r--r-- | gui/qml/main.qml | 224 | ||||
-rw-r--r-- | pkg/backend/api.go | 4 | ||||
-rw-r--r-- | pkg/backend/status.go | 3 | ||||
-rw-r--r-- | pkg/backend/webapi.go | 50 | ||||
-rw-r--r-- | pkg/bitmask/bitmask.go | 2 | ||||
-rw-r--r-- | pkg/vpn/openvpn.go | 59 |
10 files changed, 252 insertions, 108 deletions
diff --git a/gui/backend.go b/gui/backend.go index d4f5738..38f40a8 100644 --- a/gui/backend.go +++ b/gui/backend.go @@ -48,6 +48,11 @@ func UseTransport(transport string) { backend.UseTransport(transport) } +//export GetTransport +func GetTransport() *C.char { + return (*C.char)(backend.GetTransport()) +} + //export Quit func Quit() { backend.Quit() diff --git a/gui/gui.qrc b/gui/gui.qrc index 22b3b73..be0d3e9 100644 --- a/gui/gui.qrc +++ b/gui/gui.qrc @@ -22,6 +22,7 @@ <file>assets/icon/png/white/vpn_wait_1.png</file> <file>assets/icon/png/white/vpn_wait_2.png</file> <file>assets/icon/png/white/vpn_wait_3.png</file> + <file>assets/img/bird.jpg</file> <file alias="providers.json">providers/providers.json</file> </qresource> diff --git a/gui/handlers.cpp b/gui/handlers.cpp index 9f68834..cec43ca 100644 --- a/gui/handlers.cpp +++ b/gui/handlers.cpp @@ -52,6 +52,16 @@ void Backend::useAutomaticGateway() UseAutomaticGateway(); } +void Backend::useTransport(QString transport) +{ + UseTransport(toGoStr(transport)); +} + +QString Backend::getTransport() +{ + return QString(GetTransport()); +} + void Backend::login(QString username, QString password) { Login(toGoStr(username), toGoStr(password)); diff --git a/gui/handlers.h b/gui/handlers.h index 3415b7e..9c11b60 100644 --- a/gui/handlers.h +++ b/gui/handlers.h @@ -38,6 +38,8 @@ public slots: void donateSeen(); void useLocation(QString username); void useAutomaticGateway(); + void useTransport(QString transport); + QString getTransport(); void login(QString username, QString password); void resetError(QString errlabel); void resetNotification(QString label); diff --git a/gui/qml/main.qml b/gui/qml/main.qml index 1399f0f..7048a81 100644 --- a/gui/qml/main.qml +++ b/gui/qml/main.qml @@ -8,9 +8,9 @@ import Qt.labs.platform 1.0 ApplicationWindow { id: app visible: true - width: 500 + width: 300 height: 600 - maximumWidth: 600 + maximumWidth: 300 minimumWidth: 300 maximumHeight: 500 minimumHeight: 300 @@ -24,17 +24,20 @@ ApplicationWindow { console.debug(msg) } - // TODO get a nice background color for this mainwindow. It should be customizable. - // TODO refactorize all this mess into discrete components. + // TODO refactor into discrete components. + TabBar { id: bar width: parent.width TabButton { - text: qsTr("Info") + text: qsTr("Status") } TabButton { text: qsTr("Location") } + TabButton { + text: qsTr("Bridges") + } } StackLayout { @@ -47,93 +50,121 @@ ApplicationWindow { id: infoTab anchors.centerIn: parent - Column { + Rectangle { + id: background + anchors.fill: parent; + anchors.topMargin: 40; + Image { + source: "qrc:/assets/img/bird.jpg"; + fillMode: Image.PreserveAspectCrop; + anchors.fill: parent; + opacity: 0.8; + } + } + + Item { + id: connBox anchors.centerIn: parent - spacing: 5 - Text { - id: mainStatus - text: "off" - font.pixelSize: 26 - anchors.horizontalCenter: parent.horizontalCenter - } + width: 300 + height: 300 - Text { - id: mainCurrentGateway - text: "" - font.pixelSize: 20 - anchors.horizontalCenter: parent.horizontalCenter + Rectangle { + anchors.fill: parent + color: "white" + opacity: 0.3 + layer.enabled: true } - SwitchDelegate { + Column { - id: vpntoggle + anchors.centerIn: parent + spacing: 5 - text: qsTr("") - checked: false - anchors.horizontalCenter: parent.horizontalCenter + Text { + id: mainStatus + text: "off" + font.pixelSize: 26 + anchors.horizontalCenter: parent.horizontalCenter + } - Connections { - function onCheckedChanged() { - if (vpntoggle.checked == true - && ctx.status == "off") { - backend.switchOn() - } - if (vpntoggle.checked === false - && ctx.status == "on") { - backend.switchOff() + Text { + id: mainCurrentGateway + text: "" + font.pixelSize: 20 + anchors.horizontalCenter: parent.horizontalCenter + } + + SwitchDelegate { + + id: vpntoggle + + text: qsTr("") + checked: false + anchors.horizontalCenter: parent.horizontalCenter + + Connections { + function onCheckedChanged() { + if (vpntoggle.checked == true + && ctx.status == "off") { + backend.switchOn() + } + if (vpntoggle.checked === false + && ctx.status == "on") { + backend.switchOff() + } } } - } - contentItem: Text { - rightPadding: vpntoggle.indicator.width + vpntoggle.spacing - text: vpntoggle.text - font: vpntoggle.font - opacity: enabled ? 1.0 : 0.3 - color: vpntoggle.down ? "#17a81a" : "#21be2b" - elide: Text.ElideRight - verticalAlignment: Text.AlignVCenter - } + contentItem: Text { + rightPadding: vpntoggle.indicator.width + vpntoggle.spacing + text: vpntoggle.text + font: vpntoggle.font + opacity: enabled ? 1.0 : 0.5 + color: vpntoggle.down ? "#17a81a" : "#21be2b" + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } - indicator: Rectangle { - implicitWidth: 48 - implicitHeight: 26 - x: vpntoggle.width - width - vpntoggle.rightPadding - y: parent.height / 2 - height / 2 - radius: 13 - color: vpntoggle.checked ? "#17a81a" : "transparent" - border.color: vpntoggle.checked ? "#17a81a" : "#cccccc" - - Rectangle { - x: vpntoggle.checked ? parent.width - width : 0 - width: 26 - height: 26 + indicator: Rectangle { + implicitWidth: 48 + implicitHeight: 26 + x: vpntoggle.width - width - vpntoggle.rightPadding + y: parent.height / 2 - height / 2 radius: 13 - color: vpntoggle.down ? "#cccccc" : "#ffffff" - border.color: vpntoggle.checked ? (vpntoggle.down ? "#17a81a" : "#21be2b") : "#999999" + color: vpntoggle.checked ? "#17a81a" : "transparent" + border.color: vpntoggle.checked ? "#17a81a" : "#cccccc" + + Rectangle { + x: vpntoggle.checked ? parent.width - width : 0 + width: 26 + height: 26 + radius: 13 + color: vpntoggle.down ? "#cccccc" : "#ffffff" + border.color: vpntoggle.checked ? (vpntoggle.down ? "#17a81a" : "#21be2b") : "#999999" + } } - } - background: Rectangle { - implicitWidth: 100 - implicitHeight: 40 - visible: vpntoggle.down || vpntoggle.highlighted - color: vpntoggle.down ? "#bdbebf" : "#eeeeee" + background: Rectangle { + implicitWidth: 100 + implicitHeight: 40 + visible: vpntoggle.down || vpntoggle.highlighted + color: vpntoggle.down ? "#17a81a" : "#eeeeee" + } + } // end switchdelegate + + Text { + id: manualOverrideWarning + font.pixelSize: 10 + color: "grey" + text: qsTr("Location has been manually set.") + anchors.horizontalCenter: parent.horizontalCenter + visible: isManualLocation() } - } // end switchdelegate - - Text { - id: manualOverrideWarning - font.pixelSize: 10 - color: "grey" - text: qsTr("Location has been manually set.") - anchors.horizontalCenter: parent.horizontalCenter - visible: isManualLocation() - } - } - } + } // end column + } // end inner item + } // end outer item Item { @@ -148,7 +179,7 @@ ApplicationWindow { RadioButton { id: autoSelectionButton checked: !isManualLocation() - text: qsTr("Best") + text: qsTr("Recommended") onClicked: backend.useAutomaticGateway() } RadioButton { @@ -163,7 +194,7 @@ ApplicationWindow { visible: manualSelectionButton.checked anchors.horizontalCenter: parent.horizontalCenter - model: [qsTr("Best")] + model: [qsTr("Recommended")] onActivated: { console.debug("Selected gateway:", currentText) backend.useLocation(currentText.toString()) @@ -200,6 +231,45 @@ ApplicationWindow { } } // end column } // end item + + Item { + + id: bridgesTab + anchors.centerIn: parent + width: parent.width + + Column { + + anchors.centerIn: parent + spacing: 10 + width: parent.width + + CheckBox { + checked: false + text: qsTr("Use obfs4 bridges") + font.pixelSize: 14 + anchors.horizontalCenter: parent.horizontalCenter + } + Text { + id: bridgesInfo + width: 250 + color: "grey" + text: qsTr("Select a bridge only if you know that you need it to evade censorship in your country or local network.") + anchors.horizontalCenter: parent.horizontalCenter + wrapMode: Text.WordWrap + } + Text { + id: bridgeReconnect + width: 250 + font.pixelSize: 12 + color: "red" + text: qsTr("The change will take effect next time you connect to the VPN.") + anchors.horizontalCenter: parent.horizontalCenter + wrapMode: Text.WordWrap + visible: true + } + } // end column + } // end item } // end stacklayout Connections { diff --git a/pkg/backend/api.go b/pkg/backend/api.go index 0db26ae..e96c65b 100644 --- a/pkg/backend/api.go +++ b/pkg/backend/api.go @@ -83,6 +83,10 @@ func UseTransport(label string) { ctx.bm.UseTransport(label) } +func GetTransport() *C.char { + return C.CString(ctx.bm.GetTransport()) +} + func Quit() { if ctx.autostart != nil { ctx.autostart.Disable() diff --git a/pkg/backend/status.go b/pkg/backend/status.go index bdbdd35..1ec5c4f 100644 --- a/pkg/backend/status.go +++ b/pkg/backend/status.go @@ -57,7 +57,8 @@ type connectionCtx struct { func (c *connectionCtx) toJson() ([]byte, error) { statusMutex.Lock() if c.bm != nil { - c.Locations = c.bm.ListLocationFullness("openvpn") + transport := c.bm.GetTransport() + c.Locations = c.bm.ListLocationFullness(transport) c.CurrentGateway = c.bm.GetCurrentGateway() c.CurrentLocation = c.bm.GetCurrentLocation() c.CurrentCountry = c.bm.GetCurrentCountry() diff --git a/pkg/backend/webapi.go b/pkg/backend/webapi.go index 903112e..a14974e 100644 --- a/pkg/backend/webapi.go +++ b/pkg/backend/webapi.go @@ -49,7 +49,7 @@ func webGatewaySet(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "ParseForm() err: %v", err) return } - gwLabel := r.FormValue("gw") + gwLabel := r.FormValue("transport") fmt.Fprintf(w, "selected gateway: %s\n", gwLabel) ctx.bm.UseGateway(gwLabel) // TODO make sure we don't tear the fw down on reconnect... @@ -63,23 +63,48 @@ func webGatewaySet(w http.ResponseWriter, r *http.Request) { } func webGatewayList(w http.ResponseWriter, r *http.Request) { - locationJson, err := json.Marshal(ctx.bm.ListLocationFullness("openvpn")) + transport := ctx.bm.GetTransport() + locationJson, err := json.Marshal(ctx.bm.ListLocationFullness(transport)) if err != nil { fmt.Fprintf(w, "Error converting json: %v", err) } fmt.Fprintf(w, string(locationJson)) } -// TODO func webTransportGet(w http.ResponseWriter, r *http.Request) { + t, err := json.Marshal(ctx.bm.GetTransport()) + if err != nil { + fmt.Fprintf(w, "Error converting json: %v", err) + } + fmt.Fprintf(w, string(t)) + } -// TODO func webTransportSet(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 + } + t := r.FormValue("transport") + if isValidTransport(t) { + fmt.Fprintf(w, "Selected transport: %s\n", t) + go ctx.bm.SetTransport(string(t)) + } else { + fmt.Fprintf(w, "Unknown transport: %s\n", t) + } + default: + fmt.Fprintf(w, "Only POST supported.") + } } -// TODO func webTransportList(w http.ResponseWriter, r *http.Request) { + t, err := json.Marshal([]string{"openvpn", "obfs4"}) + if err != nil { + fmt.Fprintf(w, "Error converting json: %v", err) + } + fmt.Fprintf(w, string(t)) } func webQuit(w http.ResponseWriter, r *http.Request) { @@ -97,10 +122,19 @@ func enableWebAPI(port int) { 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/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) } + +func isValidTransport(t string) bool { + for _, b := range []string{"openvpn", "obfs4"} { + if b == t { + return true + } + } + return false +} diff --git a/pkg/bitmask/bitmask.go b/pkg/bitmask/bitmask.go index 3f484e8..b430808 100644 --- a/pkg/bitmask/bitmask.go +++ b/pkg/bitmask/bitmask.go @@ -30,6 +30,8 @@ type Bitmask interface { ListLocationFullness(protocol string) map[string]float64 UseGateway(name string) UseAutomaticGateway() + GetTransport() string + SetTransport(string) error GetCurrentGateway() string GetCurrentLocation() string GetCurrentCountry() string diff --git a/pkg/vpn/openvpn.go b/pkg/vpn/openvpn.go index 244195b..d69f4e6 100644 --- a/pkg/vpn/openvpn.go +++ b/pkg/vpn/openvpn.go @@ -121,26 +121,8 @@ func (b *Bitmask) listenShapeErr() { func (b *Bitmask) startOpenVPN() error { arg := []string{} - // Empty transport means we get only the openvpn gateways - if b.transport == "" { - arg = b.openvpnArgs - gateways, err := b.bonafide.GetGateways("openvpn") - if err != nil { - return err - } - err = b.launch.firewallStart(gateways) - if err != nil { - return err - } - - for _, gw := range gateways { - for _, port := range gw.Ports { - arg = append(arg, "--remote", gw.IPAddress, port, "tcp4") - } - } - } else { - // For now, obf4 is the only supported Pluggable Transport - gateways, err := b.bonafide.GetGateways(b.transport) + if b.GetTransport() == "obfs4" { + gateways, err := b.bonafide.GetGateways("obfs4") if err != nil { return err } @@ -164,6 +146,22 @@ func (b *Bitmask) startOpenVPN() error { proxyArgs := strings.Split(proxy, ":") arg = append(arg, "--remote", proxyArgs[0], proxyArgs[1], "tcp4") arg = append(arg, "--route", gw.IPAddress, "255.255.255.255", "net_gateway") + } else { + arg = b.openvpnArgs + gateways, err := b.bonafide.GetGateways("openvpn") + if err != nil { + return err + } + err = b.launch.firewallStart(gateways) + if err != nil { + return err + } + + for _, gw := range gateways { + for _, port := range gw.Ports { + arg = append(arg, "--remote", gw.IPAddress, port, "tcp4") + } + } } arg = append(arg, "--verb", "3", @@ -171,8 +169,8 @@ func (b *Bitmask) startOpenVPN() error { "--management", openvpnManagementAddr, openvpnManagementPort, "--ca", b.getTempCaCertPath(), "--cert", b.certPemPath, - "--key", b.certPemPath, - "--persist-tun") + "--key", b.certPemPath) + //"--persist-tun") return b.launch.openvpnStart(arg...) } @@ -330,6 +328,23 @@ func (b *Bitmask) UseTransport(transport string) error { return nil } +func (b *Bitmask) GetTransport() string { + if b.transport == "obfs4" { + return "obfs4" + } else { + return "openvpn" + } +} + +func (b *Bitmask) SetTransport(t string) error { + if t != "openvpn" && t != "obfs4" { + return errors.New("Transport not supported: " + t) + } + log.Println("Setting transport to", t) + b.transport = t + return nil +} + func (b *Bitmask) getTempCertPemPath() string { return path.Join(b.tempdir, "openvpn.pem") } |