diff --git a/gui/backend.go b/gui/backend.go
index cf7c0fb225fef88514fa1f27da5cc3c5b02bc4d4..9c65025a9bacb46a7d1735075ce796e59f7ebf4a 100644
--- a/gui/backend.go
+++ b/gui/backend.go
@@ -1,333 +1,59 @@
 package main
 
-/* a wrapper around bitmask that exposes status to a QtQml gui */
+/* a wrapper around bitmask that exposes status to a QtQml gui.
+   Have a look at the pkg/backend module for further enlightment. */
 
 import (
-	"bytes"
-	"encoding/json"
-	"fmt"
-	"log"
-	"net/http"
-	"os"
-	"reflect"
-	"sync"
-	//"time"
+	"C"
 	"unsafe"
 
-	"0xacab.org/leap/bitmask-vpn/pkg/bitmask"
-	"0xacab.org/leap/bitmask-vpn/pkg/pickle"
+	"0xacab.org/leap/bitmask-vpn/pkg/backend"
 )
 
-// typedef void (*cb)();
-// inline void _do_callback(cb f) {
-// 	f();
-// }
-import "C"
-
-/* callbacks into C-land */
-
-var mut sync.Mutex
-var stmut sync.Mutex
-var cbs = make(map[string](*[0]byte))
-var initOnce sync.Once
-
-// Events are just a enumeration of all the posible events that C functions can
-// be interested in subscribing to. You cannot subscribe to an event that is
-// not listed here.
-type Events struct {
-	OnStatusChanged string
-}
-
-const OnStatusChanged string = "OnStatusChanged"
-
-// subscribe registers a callback from C-land.
-// This callback needs to be passed as a void* C function pointer.
-func subscribe(event string, fp unsafe.Pointer) {
-	mut.Lock()
-	defer mut.Unlock()
-	e := &Events{}
-	v := reflect.Indirect(reflect.ValueOf(&e))
-	hf := v.Elem().FieldByName(event)
-	if reflect.ValueOf(hf).IsZero() {
-		fmt.Println("ERROR: not a valid event:", event)
-	} else {
-		cbs[event] = (*[0]byte)(fp)
-	}
-}
-
-// trigger fires a callback from C-land.
-func trigger(event string) {
-	mut.Lock()
-	defer mut.Unlock()
-	cb := cbs[event]
-	if cb != nil {
-		C._do_callback(cb)
-	} else {
-		fmt.Println("ERROR: this event does not have subscribers:", event)
-	}
-}
-
-/* connection status */
-
-const (
-	offStr      = "off"
-	startingStr = "starting"
-	onStr       = "on"
-	stoppingStr = "stopping"
-	failedStr   = "failed"
-)
-
-// status reflects the current VPN status. Go code is responsible for updating
-// it; C-land just watches its changes and pulls its updates via the serialized
-// context object.
-type status int
-
-const (
-	off status = iota
-	starting
-	on
-	stopping
-	failed
-	unknown
-)
-
-func (s status) String() string {
-	return [...]string{offStr, startingStr, onStr, stoppingStr, failedStr}[s]
-}
-
-func (s status) MarshalJSON() ([]byte, error) {
-	b := bytes.NewBufferString(`"`)
-	b.WriteString(s.String())
-	b.WriteString(`"`)
-	return b.Bytes(), nil
-}
-
-func (s status) fromString(st string) status {
-	switch st {
-	case offStr:
-		return off
-	case startingStr:
-		return starting
-	case onStr:
-		return on
-	case stoppingStr:
-		return stopping
-	case failedStr:
-		return failed
-	default:
-		return unknown
-	}
-}
-
-// The connectionCtx keeps the global state that is passed around to C-land. It
-// also serves as the primary way of passing requests from the frontend to the
-// Go-core, by letting the UI write some of these variables and processing
-// them.
-type connectionCtx struct {
-	AppName  string `json:"appName"`
-	Provider string `json:"provider"`
-	Donate   bool   `json:"donate"`
-	Status   status `json:"status"`
-	bm       bitmask.Bitmask
-}
-
-func (c connectionCtx) toJson() ([]byte, error) {
-	stmut.Lock()
-	defer stmut.Unlock()
-	b, err := json.Marshal(c)
-	if err != nil {
-		log.Println(err)
-		return nil, err
-	}
-	return b, nil
-}
-
-func (c connectionCtx) updateStatus() {
-	if stStr, err := c.bm.GetStatus(); err != nil {
-		log.Printf("Error getting status: %v", err)
-	} else {
-		setStatusFromStr(stStr)
-	}
-
-	statusCh := c.bm.GetStatusCh()
-	for {
-		select {
-		case stStr := <-statusCh:
-			setStatusFromStr(stStr)
-		}
-	}
-}
-
-var ctx *connectionCtx
-
-func setStatus(st status) {
-	stmut.Lock()
-	defer stmut.Unlock()
-	ctx.Status = st
-	go trigger(OnStatusChanged)
-}
-
-func toggleDonate() {
-	stmut.Lock()
-	defer stmut.Unlock()
-	ctx.Donate = !ctx.Donate
-	go trigger(OnStatusChanged)
-}
-
-func setStatusFromStr(stStr string) {
-	setStatus(unknown.fromString(stStr))
-}
-
-// initializeBitmask instantiates a bitmask connection
-func initializeBitmask() {
-	if ctx == nil {
-		log.Println("error: cannot initialize bitmask, ctx is nil")
-		os.Exit(1)
-	}
-	bitmask.InitializeLogger()
-
-	b, err := bitmask.InitializeBitmask()
-	if err != nil {
-		log.Println("error: cannot initialize bitmask")
-	}
-	ctx.bm = b
-}
-
-func startVPN() {
-	err := ctx.bm.StartVPN(ctx.Provider)
-	if err != nil {
-		log.Println(err)
-		os.Exit(1)
-	}
-}
-
-func stopVPN() {
-	err := ctx.bm.StopVPN()
-	if err != nil {
-		log.Println(err)
-	}
-}
-
-// initializeContext initializes an empty connStatus and assigns it to the
-// global ctx holder. This is expected to be called only once, so the public
-// api uses the sync.Once primitive to call this.
-func initializeContext(provider, appName string) {
-	var st status = off
-	ctx = &connectionCtx{
-		AppName:  appName,
-		Provider: provider,
-		Donate:   false,
-		Status:   st,
-	}
-	go trigger(OnStatusChanged)
-	initializeBitmask()
-}
-
-/* mock http server: easy way to mocking vpn behavior on ui interaction. This
-* should also show a good way of writing functionality tests just for the Qml
-* layer */
-
-func mockUIOn(w http.ResponseWriter, r *http.Request) {
-	log.Println("changing status: on")
-	setStatus(on)
-}
-
-func mockUIOff(w http.ResponseWriter, r *http.Request) {
-	log.Println("changing status: off")
-	setStatus(off)
-}
-
-func mockUIFailed(w http.ResponseWriter, r *http.Request) {
-	log.Println("changing status: failed")
-	setStatus(failed)
-}
-
-func mockUI() {
-	http.HandleFunc("/on", mockUIOn)
-	http.HandleFunc("/off", mockUIOff)
-	http.HandleFunc("/failed", mockUIFailed)
-	http.ListenAndServe(":8080", nil)
-}
-
-/*
-
-  exported C api
-
-*/
-
 //export SwitchOn
 func SwitchOn() {
-	go setStatus(starting)
-	go startVPN()
+	backend.SwitchOn()
 }
 
 //export SwitchOff
 func SwitchOff() {
-	go setStatus(stopping)
-	go stopVPN()
+	backend.SwitchOff()
 }
 
 //export Unblock
 func Unblock() {
-	fmt.Println("unblock... [not implemented]")
+	backend.Unblock()
 }
 
 //export Quit
 func Quit() {
-	if ctx.Status != off {
-		go setStatus(stopping)
-		stopVPN()
-	}
+	backend.Quit()
+
 }
 
 //export ToggleDonate
 func ToggleDonate() {
-	toggleDonate()
+	backend.ToggleDonate()
 }
 
 //export SubscribeToEvent
 func SubscribeToEvent(event string, f unsafe.Pointer) {
-	subscribe(event, f)
+	backend.SubscribeToEvent(event, f)
 }
 
 //export InitializeBitmaskContext
 func InitializeBitmaskContext() {
-	pi := bitmask.GetConfiguredProvider()
-
-	initOnce.Do(func() {
-		initializeContext(pi.Provider, pi.AppName)
-	})
-	go ctx.updateStatus()
-
-	/* DEBUG
-	timer := time.NewTimer(time.Second * 3)
-	go func() {
-		<-timer.C
-		fmt.Println("donate timer fired")
-		toggleDonate()
-	}()
-	*/
+	backend.InitializeBitmaskContext()
 }
 
 //export RefreshContext
 func RefreshContext() *C.char {
-	c, _ := ctx.toJson()
-	return C.CString(string(c))
+	return (*C.char)(backend.RefreshContext())
 }
 
 //export InstallHelpers
 func InstallHelpers() {
-	pickle.InstallHelpers()
-}
-
-/* end of the exposed api */
-
-/* we could enable this one optionally for the qt tests */
-
-/* uncomment: export MockUIInteraction */
-func MockUIInteraction() {
-	log.Println("mocking ui interaction on port 8080. \nTry 'curl localhost:8080/{on|off|failed}' to toggle status.")
-	go mockUI()
+	backend.InstallHelpers()
 }
 
 func main() {}
diff --git a/pkg/backend/api.go b/pkg/backend/api.go
new file mode 100644
index 0000000000000000000000000000000000000000..f924cbda122dac780530b4f047f3e02c9157a906
--- /dev/null
+++ b/pkg/backend/api.go
@@ -0,0 +1,74 @@
+/* All the exported functions live here */
+
+package backend
+
+import (
+	"C"
+	"fmt"
+	"log"
+	"unsafe"
+
+	"0xacab.org/leap/bitmask-vpn/pkg/bitmask"
+	"0xacab.org/leap/bitmask-vpn/pkg/pickle"
+)
+
+func SwitchOn() {
+	go setStatus(starting)
+	go startVPN()
+}
+
+func SwitchOff() {
+	go setStatus(stopping)
+	go stopVPN()
+}
+
+func Unblock() {
+	fmt.Println("unblock... [not implemented]")
+}
+
+func Quit() {
+	if ctx.Status != off {
+		go setStatus(stopping)
+		stopVPN()
+	}
+}
+
+func ToggleDonate() {
+	toggleDonate()
+}
+
+func SubscribeToEvent(event string, f unsafe.Pointer) {
+	subscribe(event, f)
+}
+
+func InitializeBitmaskContext() {
+	pi := bitmask.GetConfiguredProvider()
+
+	initOnce.Do(func() {
+		initializeContext(pi.Provider, pi.AppName)
+	})
+	go ctx.updateStatus()
+
+	/* DEBUG
+	timer := time.NewTimer(time.Second * 3)
+	go func() {
+		<-timer.C
+		fmt.Println("donate timer fired")
+		toggleDonate()
+	}()
+	*/
+}
+
+func RefreshContext() *C.char {
+	c, _ := ctx.toJson()
+	return C.CString(string(c))
+}
+
+func InstallHelpers() {
+	pickle.InstallHelpers()
+}
+
+func MockUIInteraction() {
+	log.Println("mocking ui interaction on port 8080. \nTry 'curl localhost:8080/{on|off|failed}' to toggle status.")
+	go mockUI()
+}
diff --git a/pkg/backend/bitmask.go b/pkg/backend/bitmask.go
new file mode 100644
index 0000000000000000000000000000000000000000..07d27ea6a2df61c0b4fcbfc4b16504aeb8300843
--- /dev/null
+++ b/pkg/backend/bitmask.go
@@ -0,0 +1,52 @@
+package backend
+
+import (
+	"log"
+	"os"
+
+	"0xacab.org/leap/bitmask-vpn/pkg/bitmask"
+)
+
+func initializeBitmask() {
+	if ctx == nil {
+		log.Println("error: cannot initialize bitmask, ctx is nil")
+		os.Exit(1)
+	}
+	bitmask.InitializeLogger()
+
+	b, err := bitmask.InitializeBitmask()
+	if err != nil {
+		log.Println("error: cannot initialize bitmask")
+	}
+	ctx.bm = b
+}
+
+func startVPN() {
+	err := ctx.bm.StartVPN(ctx.Provider)
+	if err != nil {
+		log.Println(err)
+		os.Exit(1)
+	}
+}
+
+func stopVPN() {
+	err := ctx.bm.StopVPN()
+	if err != nil {
+		log.Println(err)
+	}
+}
+
+// initializeContext initializes an empty connStatus and assigns it to the
+// global ctx holder. This is expected to be called only once, so the public
+// api uses the sync.Once primitive to call this.
+func initializeContext(provider, appName string) {
+	var st status = off
+	ctx = &connectionCtx{
+		AppName:  appName,
+		Provider: provider,
+		Donate:   false,
+		Status:   st,
+	}
+	go trigger(OnStatusChanged)
+	initializeBitmask()
+}
diff --git a/pkg/backend/callbacks.go b/pkg/backend/callbacks.go
new file mode 100644
index 0000000000000000000000000000000000000000..5ea3b0484039c4a97d55e23bf0c155ea2264ff9c
--- /dev/null
+++ b/pkg/backend/callbacks.go
@@ -0,0 +1,63 @@
+package backend
+
+import (
+	"fmt"
+	"reflect"
+	"sync"
+	"unsafe"
+)
+
+/* NOTE! ATCHUNG! what follow are not silly comments. Well, *this one* is, but
+   the lines after this are not.
+   Those are inline C functions, that are invoked by CGO later on.
+   it's also crucial that you don't any extra space between the function
+   block and the 'import "C"' line. */
+
+// typedef void (*cb)();
+// inline void _do_callback(cb f) {
+// 	f();
+// }
+import "C"
+
+/* callbacks into C-land */
+
+var mut sync.Mutex
+var stmut sync.Mutex
+var cbs = make(map[string](*[0]byte))
+var initOnce sync.Once
+
+// Events are just a enumeration of all the posible events that C functions can
+// be interested in subscribing to. You cannot subscribe to an event that is
+// not listed here.
+type Events struct {
+	OnStatusChanged string
+}
+
+const OnStatusChanged string = "OnStatusChanged"
+
+// subscribe registers a callback from C-land.
+// This callback needs to be passed as a void* C function pointer.
+func subscribe(event string, fp unsafe.Pointer) {
+	mut.Lock()
+	defer mut.Unlock()
+	e := &Events{}
+	v := reflect.Indirect(reflect.ValueOf(&e))
+	hf := v.Elem().FieldByName(event)
+	if reflect.ValueOf(hf).IsZero() {
+		fmt.Println("ERROR: not a valid event:", event)
+	} else {
+		cbs[event] = (*[0]byte)(fp)
+	}
+}
+
+// trigger fires a callback from C-land.
+func trigger(event string) {
+	mut.Lock()
+	defer mut.Unlock()
+	cb := cbs[event]
+	if cb != nil {
+		C._do_callback(cb)
+	} else {
+		fmt.Println("ERROR: this event does not have subscribers:", event)
+	}
+}
diff --git a/pkg/backend/mocks.go b/pkg/backend/mocks.go
new file mode 100644
index 0000000000000000000000000000000000000000..a8ede7363d07e9e198f60a150674ee1d9eb984a3
--- /dev/null
+++ b/pkg/backend/mocks.go
@@ -0,0 +1,32 @@
+package backend
+
+import (
+	"log"
+	"net/http"
+)
+
+/* mock http server: easy way to mocking vpn behavior on ui interaction. This
+* should also show a good way of writing functionality tests just for the Qml
+* layer */
+
+func mockUIOn(w http.ResponseWriter, r *http.Request) {
+	log.Println("changing status: on")
+	setStatus(on)
+}
+
+func mockUIOff(w http.ResponseWriter, r *http.Request) {
+	log.Println("changing status: off")
+	setStatus(off)
+}
+
+func mockUIFailed(w http.ResponseWriter, r *http.Request) {
+	log.Println("changing status: failed")
+	setStatus(failed)
+}
+
+func mockUI() {
+	http.HandleFunc("/on", mockUIOn)
+	http.HandleFunc("/off", mockUIOff)
+	http.HandleFunc("/failed", mockUIFailed)
+	http.ListenAndServe(":8080", nil)
+}
diff --git a/pkg/backend/status.go b/pkg/backend/status.go
new file mode 100644
index 0000000000000000000000000000000000000000..e2d31dbe1cbcadb36a3e80bc0ed8ba8fa6666a7f
--- /dev/null
+++ b/pkg/backend/status.go
@@ -0,0 +1,122 @@
+package backend
+
+import (
+	"bytes"
+	"encoding/json"
+	"log"
+
+	"0xacab.org/leap/bitmask-vpn/pkg/bitmask"
+)
+
+const (
+	offStr      = "off"
+	startingStr = "starting"
+	onStr       = "on"
+	stoppingStr = "stopping"
+	failedStr   = "failed"
+)
+
+// ctx will be our glorious global object.
+// if we ever switch again to a provider-agnostic app, we should keep a map here.
+var ctx *connectionCtx
+
+// the status type reflects the current VPN status. Go code is responsible for updating
+// it; the C gui just watches its changes and pulls its updates via the serialized
+// context object.
+
+type status int
+
+const (
+	off status = iota
+	starting
+	on
+	stopping
+	failed
+	unknown
+)
+
+func (s status) String() string {
+	return [...]string{offStr, startingStr, onStr, stoppingStr, failedStr}[s]
+}
+
+func (s status) MarshalJSON() ([]byte, error) {
+	b := bytes.NewBufferString(`"`)
+	b.WriteString(s.String())
+	b.WriteString(`"`)
+	return b.Bytes(), nil
+}
+
+func (s status) fromString(st string) status {
+	switch st {
+	case offStr:
+		return off
+	case startingStr:
+		return starting
+	case onStr:
+		return on
+	case stoppingStr:
+		return stopping
+	case failedStr:
+		return failed
+	default:
+		return unknown
+	}
+}
+
+// The connectionCtx keeps the global state that is passed around to C-land. It
+// also serves as the primary way of passing requests from the frontend to the
+// Go-core, by letting the UI write some of these variables and processing
+// them.
+
+type connectionCtx struct {
+	AppName  string `json:"appName"`
+	Provider string `json:"provider"`
+	Donate   bool   `json:"donate"`
+	Status   status `json:"status"`
+	bm       bitmask.Bitmask
+}
+
+func (c connectionCtx) toJson() ([]byte, error) {
+	stmut.Lock()
+	defer stmut.Unlock()
+	b, err := json.Marshal(c)
+	if err != nil {
+		log.Println(err)
+		return nil, err
+	}
+	return b, nil
+}
+
+func (c connectionCtx) updateStatus() {
+	if stStr, err := c.bm.GetStatus(); err != nil {
+		log.Printf("Error getting status: %v", err)
+	} else {
+		setStatusFromStr(stStr)
+	}
+
+	statusCh := c.bm.GetStatusCh()
+	for {
+		select {
+		case stStr := <-statusCh:
+			setStatusFromStr(stStr)
+		}
+	}
+}
+
+func setStatus(st status) {
+	stmut.Lock()
+	defer stmut.Unlock()
+	ctx.Status = st
+	go trigger(OnStatusChanged)
+}
+
+func toggleDonate() {
+	stmut.Lock()
+	defer stmut.Unlock()
+	ctx.Donate = !ctx.Donate
+	go trigger(OnStatusChanged)
+}
+
+func setStatusFromStr(stStr string) {
+	setStatus(unknown.fromString(stStr))
+}