diff --git a/qubesadmin/firewall.py b/qubesadmin/firewall.py index 3a75678..858b625 100644 --- a/qubesadmin/firewall.py +++ b/qubesadmin/firewall.py @@ -195,6 +195,31 @@ class DstPorts(RuleOption): return 'dstports=' + '{!s}-{!s}'.format(*self.range) +class SrcPorts(RuleOption): + '''Source port(s), for TCP/UDP forwarding only''' + def __init__(self, value): + if isinstance(value, int): + value = str(value) + if value.count('-') == 1: + self.range = [int(x) for x in value.split('-', 1)] + elif not value.count('-'): + self.range = [int(value), int(value)] + else: + raise ValueError(value) + if any(port < 0 or port > 65536 for port in self.range): + raise ValueError('Ports out of range') + if self.range[0] > self.range[1]: + raise ValueError('Invalid port range') + super().__init__( + str(self.range[0]) if self.range[0] == self.range[1] + else '{!s}-{!s}'.format(*self.range)) + + @property + def rule(self): + '''API representation of this rule element''' + return 'srcports=' + '{!s}-{!s}'.format(*self.range) + + class IcmpType(RuleOption): '''ICMP packet type''' def __init__(self, value): diff --git a/qubesadmin/tools/qvm_firewall.py b/qubesadmin/tools/qvm_firewall.py index a345e15..2669ac5 100644 --- a/qubesadmin/tools/qvm_firewall.py +++ b/qubesadmin/tools/qvm_firewall.py @@ -91,7 +91,8 @@ Both formats, positional and keyword arguments, can be used interchangeably. Available matches: - action: accept, drop or forward + action accept, drop or forward + forwardtype internal or external (only with action=forward) dst4 synonym for dsthost dst6 synonym for dsthost dsthost IP, network or hostname @@ -99,6 +100,7 @@ Available matches: www.example.com, fd00::/8) dstports port or port range (e.g. 443 or 1200-1400) + srcports port in input (only with action=forward) icmptype icmp type number (e.g. 8 for echo requests) proto icmp, tcp or udp specialtarget only the value dns is currently supported, @@ -146,15 +148,17 @@ def rules_list_table(vm): :param vm: VM object :return: None ''' - header = ['NO', 'ACTION', 'HOST', 'PROTOCOL', 'PORT(S)', + header = ['NO', 'ACTION', 'FORWARD TYPE', 'HOST', 'PROTOCOL', 'SRCPORT', 'PORT(S)', 'SPECIAL TARGET', 'ICMP TYPE', 'EXPIRE', 'COMMENT'] rows = [] for (rule, rule_no) in zip(vm.firewall.rules, itertools.count()): row = [x.pretty_value if x is not None else '-' for x in [ rule.action, + rule.forwardtype, rule.dsthost, rule.proto, rule.dstports, + rule.srcports, rule.specialtarget, rule.icmptype, rule.expire,