diff options
Diffstat (limited to 'pkg/linux')
-rwxr-xr-x | pkg/linux/bitmask-root | 118 |
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": |