summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gui/backend.go5
-rw-r--r--gui/gui.qrc1
-rw-r--r--gui/handlers.cpp10
-rw-r--r--gui/handlers.h2
-rw-r--r--gui/qml/main.qml224
-rw-r--r--pkg/backend/api.go4
-rw-r--r--pkg/backend/status.go3
-rw-r--r--pkg/backend/webapi.go50
-rw-r--r--pkg/bitmask/bitmask.go2
-rw-r--r--pkg/vpn/openvpn.go59
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")
}