diff --git a/.gitignore b/.gitignore
index 42a41b7716309702fc6f3adbba0a82571db23b22..4b631a73c108fda27238a706ac55c66238c4f1b6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,7 @@
 /bitmask-vpn
 cmd/bitmask-vpn/bitmask-vpn
+/bitmask-helper
+cmd/bitmask-helper/bitmask-helper
 locales/*/out.gotext.json
 .*.swp
 *.exe
diff --git a/cmd/bitmask-helper/main.go b/cmd/bitmask-helper/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..9673763138e089ec589ef3c5653a0f35af61aed9
--- /dev/null
+++ b/cmd/bitmask-helper/main.go
@@ -0,0 +1,41 @@
+// Copyright (C) 2018 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
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package main
+
+import (
+	"log"
+	"path"
+
+	"0xacab.org/leap/bitmask-systray/pkg/config"
+	"0xacab.org/leap/bitmask-systray/pkg/helper"
+)
+
+const (
+	bindAddr = "localhost:7171"
+	logFile  = "helper.log"
+)
+
+func main() {
+	logger, err := config.ConfigureLogger(path.Join(helper.LogFolder, logFile))
+	if err != nil {
+		log.Println("Can't configure logger: ", err)
+	} else {
+		defer logger.Close()
+	}
+
+	helper.ServeHTTP(bindAddr)
+
+}
diff --git a/pkg/helper/args.go b/pkg/helper/args.go
new file mode 100644
index 0000000000000000000000000000000000000000..d6b3bb4b13967a71582c1cde2bddc200f55e61cb
--- /dev/null
+++ b/pkg/helper/args.go
@@ -0,0 +1,103 @@
+package helper
+
+import (
+	"log"
+	"net"
+	"os"
+	"regexp"
+	"strconv"
+)
+
+const (
+	// TODO: this is the nameserver for tcp, but for udp is 10.42.0.1
+	//       the nameserver pick up should be dependent on the proto being used
+	nameserver = "10.41.0.1"
+)
+
+var (
+	fixedArgs = []string{
+		"--nobind",
+		"--client",
+		"--dev", "tun",
+		"--tls-client",
+		"--remote-cert-tls", "server",
+		"--dhcp-option", "DNS", nameserver,
+		"--log", LogFolder + "openvpn.log",
+	}
+
+	allowendArgs = map[string][]string{
+		"--remote":            []string{"IP", "NUMBER", "PROTO"},
+		"--tls-cipher":        []string{"CIPHER"},
+		"--cipher":            []string{"CIPHER"},
+		"--auth":              []string{"CIPHER"},
+		"--management-client": []string{},
+		"--management":        []string{"IP", "NUMBER"},
+		"--cert":              []string{"FILE"},
+		"--key":               []string{"FILE"},
+		"--ca":                []string{"FILE"},
+		"--fragment":          []string{"NUMBER"},
+		"--keepalive":         []string{"NUMBER", "NUMBER"},
+		"--verb":              []string{"NUMBER"},
+		"--tun-ipv6":          []string{},
+	}
+
+	cipher  = regexp.MustCompile("^[A-Z0-9-]+$")
+	formats = map[string]func(s string) bool{
+		"NUMBER": isNumber,
+		"PROTO":  isProto,
+		"IP":     isIP,
+		"CIPHER": cipher.MatchString,
+		"FILE":   isFile,
+	}
+)
+
+func parseOpenvpnArgs(args []string) []string {
+	newArgs := fixedArgs
+	newArgs = append(newArgs, platformOpenvpnFlags...)
+	for i := 0; i < len(args); i++ {
+		params, ok := allowendArgs[args[i]]
+		if !ok {
+			log.Printf("Invalid openvpn arg: %s", args[i])
+			continue
+		}
+		for j, arg := range args[i+1 : i+len(params)+1] {
+			if !formats[params[j]](arg) {
+				ok = false
+				break
+			}
+		}
+		if ok {
+			newArgs = append(newArgs, args[i:i+len(params)+1]...)
+			i = i + len(params)
+		} else {
+			log.Printf("Invalid openvpn arg params: %v", args[i:i+len(params)+1])
+		}
+	}
+	return newArgs
+}
+
+func isNumber(s string) bool {
+	_, err := strconv.Atoi(s)
+	return err == nil
+}
+
+func isProto(s string) bool {
+	for _, proto := range []string{"tcp", "udp", "tcp4", "udp4", "tcp6", "udp6"} {
+		if s == proto {
+			return true
+		}
+	}
+	return false
+}
+
+func isIP(s string) bool {
+	return net.ParseIP(s) != nil
+}
+
+func isFile(s string) bool {
+	info, err := os.Stat(s)
+	if err != nil {
+		return false
+	}
+	return !info.IsDir()
+}
diff --git a/pkg/helper/darwin.go b/pkg/helper/darwin.go
new file mode 100644
index 0000000000000000000000000000000000000000..7261de8965efc2da630889e0296924a01f7531fb
--- /dev/null
+++ b/pkg/helper/darwin.go
@@ -0,0 +1,184 @@
+// +build darwin
+// Copyright (C) 2018 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
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+/*
+
+This module holds some specific constants for osx, and it also contains the implementation of the pf firewall.
+
+To inspect the rules in the firewall manually, use the bitmask anchor:
+
+  sudo pfctl -s rules -a com.apple/250.BitmaskFirewall
+
+*/
+
+package helper
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"log"
+	"os"
+	"os/exec"
+	"path"
+	"strings"
+
+	"github.com/sevlyar/go-daemon"
+)
+
+const (
+	appPath     = "/Applications/RiseupVPN.app/"
+	helperPath  = appPath + "Contents/helper/"
+	LogFolder   = helperPath
+	openvpnPath = appPath + "Contents/Resources/openvpn.leap"
+
+	rulefilePath   = helperPath + "bitmask.pf.conf"
+	bitmask_anchor = "com.apple/250.BitmaskFirewall"
+	gateways_table = "bitmask_gateways"
+
+	pfctl = "/sbin/pfctl"
+)
+
+var (
+	platformOpenvpnFlags = []string{
+		"--script-security", "2",
+		"--up", helperPath + "client.up.sh",
+		"--down", helperPath + "client.down.sh",
+	}
+)
+
+func daemonize() {
+	cntxt := &daemon.Context{
+		PidFileName: "pid",
+		PidFilePerm: 0644,
+		LogFileName: "bitmask-helper.log",
+		LogFilePerm: 0640,
+		WorkDir:     "./",
+		Umask:       027,
+		Args:        []string{"[bitmask-helper]"},
+	}
+
+	d, err := cntxt.Reborn()
+	if err != nil {
+		log.Fatal("Unable to run: ", err)
+	}
+	if d != nil {
+		return
+	}
+	defer cntxt.Release()
+	log.Print("bitmask-helper daemon started")
+}
+
+func getOpenvpnPath() string {
+	return openvpnPath
+}
+
+func kill(cmd *exec.Cmd) error {
+	return cmd.Process.Signal(os.Interrupt)
+}
+
+func firewallStart(gateways []string) error {
+	enablePf()
+	err := resetGatewaysTable(gateways)
+	if err != nil {
+		return err
+	}
+
+	return loadBitmaskAnchor()
+}
+
+func firewallStop() error {
+	return exec.Command(pfctl, "-a", bitmask_anchor, "-F", "all").Run()
+}
+
+func firewallIsUp() bool {
+	out, err := exec.Command(pfctl, "-a", bitmask_anchor, "-sr").Output()
+	if err != nil {
+		log.Printf("An error ocurred getting the status of the firewall: %v", err)
+		return false
+	}
+	return bytes.Contains(out, []byte("block out proto udp to any port 53"))
+}
+
+func enablePf() {
+	cmd := exec.Command(pfctl, "-e")
+	cmd.Run()
+}
+
+func resetGatewaysTable(gateways []string) error {
+	log.Println("Resetting gateways")
+	cmd := exec.Command(pfctl, "-a", bitmask_anchor, "-t", gateways_table, "-T", "delete")
+	err := cmd.Run()
+	if err != nil {
+		log.Printf("Can't delete table: %v", err)
+	}
+
+	for _, gateway := range gateways {
+		log.Println("Adding Gateway:", gateway)
+		cmd = exec.Command(pfctl, "-a", bitmask_anchor, "-t", gateways_table, "-T", "add", gateway)
+		err = cmd.Run()
+		if err != nil {
+			log.Printf("Error adding gateway to table: %v", err)
+		}
+	}
+
+	cmd = exec.Command(pfctl, "-a", bitmask_anchor, "-t", gateways_table, "-T", "add", nameserver)
+	return cmd.Run()
+
+}
+
+func getDefaultDevice() string {
+	out, err := exec.Command("/bin/sh", "-c", "/sbin/route -n get -net default | /usr/bin/grep interface | /usr/bin/awk '{print $2}'").Output()
+	if err != nil {
+		log.Printf("Error getting default device")
+	}
+	return strings.TrimSpace(bytesToString(out))
+}
+
+func loadBitmaskAnchor() error {
+	dev := getDefaultDevice()
+	rulePath, err := getRulefilePath()
+	if err != nil {
+		return err
+	}
+	cmdline := fmt.Sprintf("%s -D default_device=%s -a %s -f %s", pfctl, dev, bitmask_anchor, rulePath)
+
+	log.Println("Loading Bitmask Anchor:", cmdline)
+
+	_, err = exec.Command("/bin/sh", "-c", cmdline).Output()
+	return err
+}
+
+func getRulefilePath() (string, error) {
+	if _, err := os.Stat(rulefilePath); !os.IsNotExist(err) {
+		return rulefilePath, nil
+	}
+
+	gopath := os.Getenv("GOPATH")
+	if gopath == "" {
+		gopath = path.Join(os.Getenv("HOME"), "go")
+	}
+	rulefile := path.Join(gopath, "0xacab.org", "leap", "riseup_vpn", "osx", "bitmask.pf.conf")
+
+	if _, err := os.Stat(rulefile); !os.IsNotExist(err) {
+		return rulefile, nil
+	}
+	return "", errors.New("Can't find rule file for the firewall")
+}
+
+func bytesToString(data []byte) string {
+	return string(data[:])
+}
diff --git a/pkg/helper/helper.go b/pkg/helper/helper.go
new file mode 100644
index 0000000000000000000000000000000000000000..2e7ffd1847f597d02694c3b72da550e7e5147cca
--- /dev/null
+++ b/pkg/helper/helper.go
@@ -0,0 +1,139 @@
+// Copyright (C) 2018 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
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package helper
+
+import (
+	"encoding/json"
+	"log"
+	"net/http"
+	"os/exec"
+)
+
+type openvpnT struct {
+	cmd *exec.Cmd
+}
+
+func ServeHTTP(bindAddr string) {
+	daemonize()
+	openvpn := openvpnT{nil}
+	http.HandleFunc("/openvpn/start", openvpn.start)
+	http.HandleFunc("/openvpn/stop", openvpn.stop)
+	http.HandleFunc("/firewall/start", firewallStartHandler)
+	http.HandleFunc("/firewall/stop", firewallStopHandler)
+	http.HandleFunc("/firewall/isup", firewallIsUpHandler)
+
+	log.Fatal(http.ListenAndServe(bindAddr, nil))
+}
+
+func (openvpn *openvpnT) start(w http.ResponseWriter, r *http.Request) {
+	args, err := getArgs(r)
+	if err != nil {
+		log.Printf("An error has occurred processing flags: %v", err)
+		w.Write([]byte(err.Error()))
+		return
+	}
+
+	args = parseOpenvpnArgs(args)
+	log.Printf("start openvpn: %v", args)
+	err = openvpn.run(args)
+	if err != nil {
+		log.Printf("Error starting openvpn: %v", err)
+		w.Write([]byte(err.Error()))
+	}
+}
+
+func (openvpn *openvpnT) run(args []string) error {
+	if openvpn.cmd != nil {
+		log.Printf("openvpn was running, stop it first")
+		err := openvpn.kill()
+		if err != nil {
+			return err
+		}
+	}
+
+	// TODO: if it dies we should restart it
+	openvpn.cmd = exec.Command(getOpenvpnPath(), args...)
+	return openvpn.cmd.Start()
+}
+
+func (openvpn *openvpnT) stop(w http.ResponseWriter, r *http.Request) {
+	log.Println("stop openvpn")
+	if openvpn.cmd == nil || openvpn.cmd.ProcessState != nil {
+		openvpn.cmd = nil
+		return
+	}
+
+	err := openvpn.kill()
+	if err != nil {
+		log.Printf("Error stoping openvpn: %v", err)
+		w.Write([]byte(err.Error()))
+	}
+}
+
+func (openvpn *openvpnT) kill() error {
+	err := kill(openvpn.cmd)
+	if err == nil {
+		openvpn.cmd.Wait()
+	} else {
+		log.Printf("Error killing the process: %v", err)
+	}
+
+	openvpn.cmd = nil
+	return nil
+}
+
+func firewallStartHandler(w http.ResponseWriter, r *http.Request) {
+	gateways, err := getArgs(r)
+	if err != nil {
+		log.Printf("An error has occurred processing gateways: %v", err)
+		w.Write([]byte(err.Error()))
+		return
+	}
+
+	err = firewallStart(gateways)
+	if err != nil {
+		log.Printf("Error starting firewall: %v", err)
+		w.Write([]byte(err.Error()))
+		return
+	}
+	log.Println("Start firewall: firewall started")
+}
+
+func firewallStopHandler(w http.ResponseWriter, r *http.Request) {
+	err := firewallStop()
+	if err != nil {
+		log.Printf("Error stoping firewall: %v", err)
+		w.Write([]byte(err.Error()))
+	}
+	log.Println("Stop firewall: firewall stopped")
+}
+
+func firewallIsUpHandler(w http.ResponseWriter, r *http.Request) {
+	if firewallIsUp() {
+		w.Write([]byte("true"))
+		w.WriteHeader(http.StatusOK)
+	} else {
+		w.Write([]byte("false"))
+		w.WriteHeader(http.StatusNoContent)
+	}
+}
+
+func getArgs(r *http.Request) ([]string, error) {
+	args := []string{}
+	decoder := json.NewDecoder(r.Body)
+	err := decoder.Decode(&args)
+	return args, err
+}
diff --git a/pkg/helper/linux.go b/pkg/helper/linux.go
new file mode 100644
index 0000000000000000000000000000000000000000..88c3e1071464f87eaba7da769ad869f496fb64e5
--- /dev/null
+++ b/pkg/helper/linux.go
@@ -0,0 +1,67 @@
+// +build linux
+// Copyright (C) 2018 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
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package helper
+
+import (
+	"log"
+	"os"
+	"os/exec"
+)
+
+const (
+	openvpnUser       = "nobody"
+	openvpnGroup      = "nogroup"
+	LogFolder         = "/var/log/"
+	systemOpenvpnPath = "/usr/sbin/openvpn"
+	snapOpenvpnPath   = "/snap/bin/riseup-vpn.openvpn"
+)
+
+var (
+	platformOpenvpnFlags = []string{
+		"--script-security", "1",
+		"--user", openvpnUser,
+		"--group", openvpnGroup,
+	}
+)
+
+func daemonize() {}
+
+func getOpenvpnPath() string {
+	if os.Getenv("SNAP") != "" {
+		return snapOpenvpnPath
+	}
+	return systemOpenvpnPath
+}
+
+func kill(cmd *exec.Cmd) error {
+	return cmd.Process.Signal(os.Interrupt)
+}
+
+func firewallStart(gateways []string) error {
+	log.Println("Start firewall: do nothing, not implemented")
+	return nil
+}
+
+func firewallStop() error {
+	log.Println("Stop firewall: do nothing, not implemented")
+	return nil
+}
+
+func firewallIsUp() bool {
+	log.Println("IsUp firewall: do nothing, not implemented")
+	return false
+}
diff --git a/pkg/helper/windows.go b/pkg/helper/windows.go
new file mode 100644
index 0000000000000000000000000000000000000000..a19c92bf82299d05b3a5443ef403578d16aef578
--- /dev/null
+++ b/pkg/helper/windows.go
@@ -0,0 +1,66 @@
+// +build windows
+// Copyright (C) 2018 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
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package helper
+
+import (
+	"log"
+	"os"
+	"os/exec"
+)
+
+const (
+	appPath          = `C:\Program Files\RiseupVPN\`
+	LogFolder        = appPath
+	openvpnPath      = appPath + `openvpn.exe`
+	chocoOpenvpnPath = `C:\Program Files\OpenVPN\bin\openvpn.exe`
+)
+
+var (
+	platformOpenvpnFlags = []string{
+		"--script-security", "1",
+	}
+)
+
+func daemonize() {}
+
+func getOpenvpnPath() string {
+	if _, err := os.Stat(openvpnPath); !os.IsNotExist(err) {
+		return openvpnPath
+	} else if _, err := os.Stat(chocoOpenvpnPath); !os.IsNotExist(err) {
+		return chocoOpenvpnPath
+	}
+	return "openvpn.exe"
+}
+
+func kill(cmd *exec.Cmd) error {
+	return cmd.Process.Kill()
+}
+
+func firewallStart(gateways []string) error {
+	log.Println("Start firewall: do nothing, not implemented")
+	return nil
+}
+
+func firewallStop() error {
+	log.Println("Stop firewall: do nothing, not implemented")
+	return nil
+}
+
+func firewallIsUp() bool {
+	log.Println("IsUp firewall: do nothing, not implemented")
+	return false
+}