summaryrefslogtreecommitdiff
path: root/pkg/linux/bitmask-root
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/linux/bitmask-root')
-rwxr-xr-xpkg/linux/bitmask-root118
1 files changed, 90 insertions, 28 deletions
diff --git a/pkg/linux/bitmask-root b/pkg/linux/bitmask-root
index 136fd6a4..1929b51b 100755
--- a/pkg/linux/bitmask-root
+++ b/pkg/linux/bitmask-root
@@ -22,14 +22,15 @@ It should only be called by the Bitmask application.
USAGE:
bitmask-root firewall stop
- bitmask-root firewall start GATEWAY1 GATEWAY2 ...
+ bitmask-root firewall start [restart] GATEWAY1 GATEWAY2 ...
bitmask-root openvpn stop
bitmask-root openvpn start CONFIG1 CONFIG1 ...
All actions return exit code 0 for success, non-zero otherwise.
The `openvpn start` action is special: it calls exec on openvpn and replaces
-the current process.
+the current process. If the `restart` parameter is passed, the firewall will
+not be teared down in the case of an error during launch.
"""
# TODO should be tested with python3, which can be the default on some distro.
from __future__ import print_function
@@ -38,18 +39,19 @@ import os
import re
import signal
import socket
+import syslog
import subprocess
import sys
import time
import traceback
-
cmdcheck = subprocess.check_output
##
## CONSTANTS
##
+VERSION = "1"
SCRIPT = "bitmask-root"
NAMESERVER = "10.42.0.1"
BITMASK_CHAIN = "bitmask"
@@ -129,6 +131,8 @@ if DEBUG:
logger.setLevel(logging.DEBUG)
logger.addHandler(ch)
+syslog.openlog(SCRIPT)
+
##
## UTILITY
##
@@ -413,6 +417,7 @@ def bail(msg=None, exception=None):
"""
if msg is not None:
print("%s: %s" % (SCRIPT, msg))
+ syslog.syslog(syslog.LOG_ERR, msg)
if exception is not None:
traceback.print_exc()
exit(1)
@@ -566,7 +571,7 @@ class NameserverRestorer(Daemon):
A daemon that will restore the previous nameservers.
"""
- def run(self):
+ def run(self, *args):
"""
Run when daemonized.
"""
@@ -614,7 +619,7 @@ def get_default_device():
"""
routes = subprocess.check_output([IP, "route", "show"])
match = re.search("^default .*dev ([^\s]*) .*$", routes, flags=re.M)
- if match.groups():
+ if match and match.groups():
return match.group(1)
else:
bail("Could not find default device")
@@ -629,7 +634,7 @@ def get_local_network_ipv4(device):
"""
addresses = cmdcheck([IP, "-o", "address", "show", "dev", device])
match = re.search("^.*inet ([^ ]*) .*$", addresses, flags=re.M)
- if match.groups():
+ if match and match.groups():
return match.group(1)
else:
return None
@@ -644,7 +649,7 @@ def get_local_network_ipv6(device):
"""
addresses = cmdcheck([IP, "-o", "address", "show", "dev", device])
match = re.search("^.*inet6 ([^ ]*) .*$", addresses, flags=re.M)
- if match.groups():
+ if match and match.groups():
return match.group(1)
else:
return None
@@ -653,6 +658,7 @@ def get_local_network_ipv6(device):
def run_iptable_with_check(cmd, *args, **options):
"""
Run an iptables command checking to see if it should:
+ for --append: run only if rule does not already exist.
for --insert: run only if rule does not already exist.
for --delete: run only if rule does exist.
other commands are run normally.
@@ -662,6 +668,11 @@ def run_iptable_with_check(cmd, *args, **options):
check_code = run(cmd, *check_args, exitcode=True)
if check_code != 0:
run(cmd, *args, **options)
+ elif "--append" in args:
+ check_args = [arg.replace("--append", "--check") for arg in args]
+ check_code = run(cmd, *check_args, exitcode=True)
+ if check_code != 0:
+ run(cmd, *args, **options)
elif "--delete" in args:
check_args = [arg.replace("--delete", "--check") for arg in args]
check_code = run(cmd, *check_args, exitcode=True)
@@ -729,41 +740,74 @@ def firewall_start(args):
local_network_ipv6 = get_local_network_ipv6(default_device)
gateways = get_gateways(args)
- # add custom chain "bitmask"
+ # add custom chain "bitmask" to front of OUTPUT chain
if not ipv4_chain_exists(BITMASK_CHAIN):
ip4tables("--new-chain", BITMASK_CHAIN)
if not ipv6_chain_exists(BITMASK_CHAIN):
ip6tables("--new-chain", BITMASK_CHAIN)
iptables("--insert", "OUTPUT", "--jump", BITMASK_CHAIN)
- # reject everything
- iptables("--insert", BITMASK_CHAIN, "-o", default_device,
- "--jump", "REJECT")
+ # allow DNS over VPN
+ for allowed_dns in [NAMESERVER, "127.0.0.1", "127.0.1.1"]:
+ ip4tables("--append", BITMASK_CHAIN, "--protocol", "udp",
+ "--dport", "53", "--destination", allowed_dns,
+ "--jump", "ACCEPT")
- # allow traffic to gateways
- for gateway in gateways:
- ip4tables("--insert", BITMASK_CHAIN, "--destination", gateway,
- "-o", default_device, "--jump", "ACCEPT")
+ # block DNS requests to anyone but the service provider or localhost
+ # (when we actually route ipv6, we will need DNS rules for it too)
+ ip4tables("--append", BITMASK_CHAIN, "--protocol", "udp", "--dport", "53",
+ "--jump", "REJECT")
# allow traffic to IPs on local network
if local_network_ipv4:
- ip4tables("--insert", BITMASK_CHAIN,
+ ip4tables("--append", BITMASK_CHAIN,
"--destination", local_network_ipv4, "-o", default_device,
"--jump", "ACCEPT")
+ # allow multicast Simple Service Discovery Protocol
+ ip4tables("--append", BITMASK_CHAIN,
+ "--protocol", "udp",
+ "--destination", "239.255.255.250", "--dport", "1900",
+ "-o", default_device, "--jump", "RETURN")
+ # allow multicast Bonjour/mDNS
+ ip4tables("--append", BITMASK_CHAIN,
+ "--protocol", "udp",
+ "--destination", "224.0.0.251", "--dport", "5353",
+ "-o", default_device, "--jump", "RETURN")
if local_network_ipv6:
- ip6tables("--insert", BITMASK_CHAIN,
+ ip6tables("--append", BITMASK_CHAIN,
"--destination", local_network_ipv6, "-o", default_device,
"--jump", "ACCEPT")
+ # allow multicast Simple Service Discovery Protocol
+ ip6tables("--append", BITMASK_CHAIN,
+ "--protocol", "udp",
+ "--destination", "FF05::C", "--dport", "1900",
+ "-o", default_device, "--jump", "RETURN")
+ # allow multicast Bonjour/mDNS
+ ip6tables("--append", BITMASK_CHAIN,
+ "--protocol", "udp",
+ "--destination", "FF02::FB", "--dport", "5353",
+ "-o", default_device, "--jump", "RETURN")
+
+ # allow ipv4 traffic to gateways
+ for gateway in gateways:
+ ip4tables("--append", BITMASK_CHAIN, "--destination", gateway,
+ "-o", default_device, "--jump", "ACCEPT")
- # block DNS requests to anyone but the service provider or localhost
- # when we actually route ipv6, we will need dns rules for it too
- ip4tables("--insert", BITMASK_CHAIN, "--protocol", "udp", "--dport", "53",
- "--jump", "REJECT")
+ # log rejected packets to syslog
+ if DEBUG:
+ iptables("--append", BITMASK_CHAIN, "-o", default_device,
+ "--jump", "LOG", "--log-prefix", "iptables denied: ",
+ "--log-level", "7")
- for allowed_dns in [NAMESERVER, "127.0.0.1", "127.0.1.1"]:
- ip4tables("--insert", BITMASK_CHAIN, "--protocol", "udp",
- "--dport", "53", "--destination", allowed_dns,
- "--jump", "ACCEPT")
+ # for now, ensure all other ipv6 packets get rejected (regardless of
+ # device)
+ # (not sure why, but "-p any" doesn't work)
+ ip6tables("--append", BITMASK_CHAIN, "-p", "tcp", "--jump", "REJECT")
+ ip6tables("--append", BITMASK_CHAIN, "-p", "udp", "--jump", "REJECT")
+
+ # reject all other ipv4 sent over the default device
+ ip4tables("--append", BITMASK_CHAIN, "-o",
+ default_device, "--jump", "REJECT")
def firewall_stop():
@@ -784,10 +828,27 @@ def firewall_stop():
def main():
- if len(sys.argv) >= 3:
+ """
+ Entry point for cmdline execution.
+ """
+ # TODO use argparse instead.
+
+ if len(sys.argv) >= 2:
command = "_".join(sys.argv[1:3])
args = sys.argv[3:]
+ is_restart = False
+ if args and args[0] == "restart":
+ is_restart = True
+ args.remove('restart')
+
+ if command == "version":
+ print(VERSION)
+ exit(0)
+
+ if os.getuid() != 0:
+ bail("ERROR: must be run as root")
+
if command == "openvpn_start":
openvpn_start(args)
@@ -799,8 +860,9 @@ def main():
firewall_start(args)
nameserver_setter.start(NAMESERVER)
except Exception as ex:
- nameserver_restorer.start()
- firewall_stop()
+ if not is_restart:
+ nameserver_restorer.start()
+ firewall_stop()
bail("ERROR: could not start firewall", ex)
elif command == "firewall_stop":