Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 62 restrict interfaces alt #112

Merged
merged 19 commits into from Mar 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.txt
@@ -1,3 +1,7 @@
Version 1.4.5
-------------
* LinuxRestrictInterface feature for MultiHost mode

Version 1.4.4
-------------
* Moved ICMP redirection to singlehost only.
Expand Down
50 changes: 24 additions & 26 deletions fakenet/configs/default.ini
Expand Up @@ -48,22 +48,20 @@ NetworkMode: Auto
# IPTABLES iptables firewall rule activity (Linux only)
DebugLevel: Off

# MultiHost mode only: Specify what interfaces the Linux Diverter should create
# an iptables rule for to redirect traffic destined for other hosts to the
# local networking stack. This allows FakeNet-NG to log and handle packets
# from remote machines that are destined for non-local IP addresses that are
# either hard-coded or were returned in responses from the DNSListener. Use '*'
# (no quotes) or 'any' (no quotes, case-sensitive!) to indicate that this rule
# should be applied to all interfaces. Comment out to leave unconfigured.
LinuxRedirectNonlocal: *
# Restrict which interface on which Fakenet-NG will intercept and handle
# packets. Specify (only) one interface and Fakenet-NG will ignore all other
# interfaces. This feature only applies to interfaces on different subnets.
# Specify interface by name only (ex: eth0). To disable, set to "Off". In
# order to run multiple instance of Fakenet-NG on different interfaces within
# the same guest, LinuxFlushIptables must be turned off to avoid the latest
# instance flushing the rules associated with other instances or restoring
# rules to an incorrect state upon exit.
LinuxRestrictInterface: Off

# Set LinuxFlushIptables to Yes to have the Linux Diverter flush all iptables
# rules before adding its FakeNet-NG-specific rules to iptables. FakeNet-NG
# will restore all old rules when it exits, unless its termination is
# interrupted. If you disable this setting, and you accidentally interrupt the
# termination of FakeNet-NG (such as by hitting Ctrl+C more than once), then be
# prepared for network mayhem as the Diverter may receive each packet multiple
# times due to duplicate NFQUEUE rules.
# rules before adding its FakeNet-NG-specific rules to iptables. This setting
# also restores rules via `iptables-restore` when it exits, unless its
# termination is interrupted.
LinuxFlushIptables: Yes

# Incorporated so that users of the binary release may make this work for
Expand Down Expand Up @@ -204,20 +202,20 @@ BlackListPortsUDP: 67, 68, 137, 138, 443, 1900, 5355
# between 1 and 15 characters (inclusive).

[ProxyTCPListener]
Enabled: True
Protocol: TCP
Listener: ProxyListener
Port: 38926
Listeners: HTTPListener, RawListener, FTPListener, DNSListener, POPListener, SMTPListener, TFTPListener, IRCListener, BITSListener
Hidden: False
Enabled: True
Protocol: TCP
Listener: ProxyListener
Port: 38926
Listeners: HTTPListener, RawListener, FTPListener, DNSListener, POPListener, SMTPListener, TFTPListener, IRCListener, BITSListener
Hidden: False

[ProxyUDPListener]
Enabled: True
Protocol: UDP
Listener: ProxyListener
Port: 38926
Listeners: RawListener, DNSListener, TFTPListener, FTPListener
Hidden: False
Enabled: True
Protocol: UDP
Listener: ProxyListener
Port: 38926
Listeners: RawListener, DNSListener, TFTPListener, FTPListener
Hidden: False

[Forwarder]
Enabled: False
Expand Down
132 changes: 76 additions & 56 deletions fakenet/diverters/linutil.py
Expand Up @@ -13,33 +13,80 @@
from . import diverterbase


class IptCmdTemplate(object):
class IptCmdTemplateBase(object):
"""For managing insertion and removal of iptables rules.

Construct and execute iptables command lines to add (-I or -A) and remove
(-D) rules.

The removal half of this is now redundant with
LinUtilMixin.linux_{capture,restore}_iptables().
(-D) rules in the abstract. Base only handles iptables -I/-D and -i/-o args
"""

def __init__(self, fmt, args=[], add='-I', rem='-D', add_idx=0, rem_idx=0):
self._addcmd = fmt % tuple(args[0:add_idx] + [add] + args[add_idx:])
self._remcmd = fmt % tuple(args[0:add_idx] + [rem] + args[rem_idx:])

def gen_add_cmd(self):
return self._addcmd
def __init(self):
self._addcmd = None
self._remcmd = None

def _iptables_format(self, chain, iface, argfmt):
"""Format iptables command line with optional interface restriction.

Parameters
----------
chain : string
One of 'OUTPUT', 'POSTROUTING', 'INPUT', or 'PREROUTING', used for
deciding the correct flag (-i versus -o)
iface : string or NoneType
Name of interface to restrict the rule to (e.g. 'eth0'), or None
argfmt : string
Format string for remaining iptables arguments. This format string
will not be included in format string evaluation but is appended
as-is to the iptables command.
"""
flag_iface = ''
if iface:
if chain in ['OUTPUT', 'POSTROUTING']:
flag_iface = '-o'
elif chain in ['INPUT', 'PREROUTING']:
flag_iface = '-i'
else:
raise NotImplementedError('Unanticipated chain %s' % (chain))

def gen_remove_cmd(self):
return self._remcmd
self._addcmd = 'iptables -I {chain} {flag_if} {iface} {fmt}'
self._addcmd = self._addcmd.format(chain=chain, flag_if=flag_iface,
iface=(iface or ''), fmt=argfmt)
self._remcmd = 'iptables -D {chain} {flag_if} {iface} {fmt}'
self._remcmd = self._remcmd.format(chain=chain, flag_if=flag_iface,
iface=(iface or ''), fmt=argfmt)

def add(self):
if not self._addcmd:
raise ValueError('Iptables rule addition command not initialized')
return subprocess.call(self._addcmd.split())

def remove(self):
if not self._remcmd:
raise ValueError('Iptables rule removal command not initialized')
return subprocess.call(self._remcmd.split())


class IptCmdTemplateNfq(IptCmdTemplateBase):
"""For constructing and executing NFQUEUE iptables rules"""
def __init__(self, chain, qno, table, iface=None):
fmt = '-t {} -j NFQUEUE --queue-num {}'.format(table, qno)
self._iptables_format(chain, iface, fmt)


class IptCmdTemplateRedir(IptCmdTemplateBase):
"""For constructing and executing REDIRECT iptables rules"""
def __init__(self, iface=None):
fmt = '-t nat -j REDIRECT'
self._iptables_format('PREROUTING', iface, fmt)


class IptCmdTemplateIcmpRedir(IptCmdTemplateBase):
"""For constructing and executing ICMP REDIRECT iptables rules"""
def __init__(self, iface=None):
fmt = '-t nat -p icmp -j REDIRECT'
self._iptables_format('OUTPUT', iface, fmt)


class LinuxDiverterNfqueue(object):
"""NetfilterQueue object wrapper.

Expand All @@ -54,17 +101,14 @@ class LinuxDiverterNfqueue(object):
The results are undefined if start() or stop() are called multiple times.
"""

def __init__(self, qno, chain, table, callback):
def __init__(self, qno, chain, table, callback, iface=None):
self.logger = logging.getLogger('Diverter')

# e.g. iptables <-I> <INPUT> -t <mangle> -j NFQUEUE --queue-num <0>'
fmt = 'iptables %s %s -t %s -j NFQUEUE --queue-num %d'

# Specifications
self.qno = qno
self.chain = chain
self.table = table
self._rule = IptCmdTemplate(fmt, [self.chain, self.table, self.qno])
self._rule = IptCmdTemplateNfq(self.chain, self.qno, self.table, iface)
self._callback = callback
self._nfqueue = netfilterqueue.NetfilterQueue()
self._sk = None
Expand Down Expand Up @@ -274,6 +318,8 @@ def linux_capture_iptables(self):
def linux_restore_iptables(self):
ret = None

self.pdebug(DIPTBLS, 'Restoring iptables')

try:
p = subprocess.Popen(['iptables-restore'], stdin=subprocess.PIPE)
p.communicate(self.iptables_captured)
Expand Down Expand Up @@ -359,50 +405,26 @@ def linux_get_next_nfqueue_numbers(self, n):

return next_qnos

def linux_iptables_redir_nonlocal(self, specified_ifaces):
"""Linux-specific iptables processing for 'LinuxRedirectNonlocal'
configuration item.
def linux_iptables_redir_iface(self, iface):
"""Linux-specific iptables processing for interface-based redirect
rules.

returns:
tuple(bool, list(IptCmdTemplate))
Status of the operation and any successful iptables rules that will
need to be undone.
"""

local_ifaces = self._linux_get_ifaces()
all_iface_aliases = ['any', '*']
acceptable_ifaces = local_ifaces + all_iface_aliases
iptables_rules = []
rule = IptCmdTemplateRedir(iface)
ret = rule.add()

# Catch cases where the user isn't going to get what they expect
# because iptables does not err for non-existent ifaces...
if not set(specified_ifaces).issubset(acceptable_ifaces):
# And indicate ALL interfaces that do not appear to exist
for iface in specified_ifaces:
if iface not in acceptable_ifaces:
self.logger.error(('Interface %s not found for nonlocal ' +
'packet redirection, must be one of ' +
'%s') % (iface, str(acceptable_ifaces)))
return (False, [])

for iface in specified_ifaces:
fmt, args = '', list()
if iface in all_iface_aliases:
# Handle */any case by omitting -i switch and corresponding arg
fmt = 'iptables -t nat %s PREROUTING -j REDIRECT'
else:
fmt = 'iptables -t nat %s PREROUTING -i %s -j REDIRECT'
args = [iface]

rule = IptCmdTemplate(fmt, args)
ret = rule.add()

if ret != 0:
self.logger.error('Failed to create PREROUTING/REDIRECT ' +
'rule for %s, stopping...' % (iface))
return (False, iptables_rules)
if ret != 0:
self.logger.error('Failed to create PREROUTING/REDIRECT ' +
'rule for %s, stopping...' % (iface))
return (False, iptables_rules)

iptables_rules.append(rule)
iptables_rules.append(rule)

return (True, iptables_rules)

Expand Down Expand Up @@ -615,9 +637,8 @@ def scan_for_default_gw(fields):

return dgw

def linux_redir_icmp(self):
fmt = 'iptables -t nat %s OUTPUT -p icmp -j REDIRECT'
rule = IptCmdTemplate(fmt)
def linux_redir_icmp(self, iface=None):
rule = IptCmdTemplateIcmpRedir(iface)
ret = rule.add()
return (ret == 0), rule

Expand Down Expand Up @@ -739,4 +760,3 @@ def linux_endpoint_owned_by_processes(self, ipver, proto_name, ip, port,
(ip, port, t))

return False

57 changes: 30 additions & 27 deletions fakenet/diverters/linux.py
Expand Up @@ -35,11 +35,6 @@ def __init__(self, diverter_config, listeners_config, ip_addrs,

def init_diverter_linux(self):
"""Linux-specific Diverter initialization."""
# String list configuration item that is specific to the Linux
# Diverter, will not be parsed by DiverterBase, and needs to be
# accessed as an array in the future.
slists = ['linuxredirectnonlocal', ]
self.reconfigure(portlists=[], stringlists=slists)

self.logger.info('Running in %s mode' % (self.network_mode))

Expand Down Expand Up @@ -136,16 +131,26 @@ def startCallback(self):
'netfilter queue numbers')
sys.exit(1)

fn_iface = None
if ((not self.single_host_mode) and
self.is_configured('linuxrestrictinterface') and not
self.is_clear('linuxrestrictinterface')):
self.pdebug(DMISC, 'Processing LinuxRestrictInterface config %s' %
self.getconfigval('linuxrestrictinterface'))
fn_iface = self.getconfigval('linuxrestrictinterface')

self.pdebug(DNFQUEUE, 'Next available NFQUEUE numbers: ' + str(qnos))

self.pdebug(DNFQUEUE, 'Enumerating queue numbers and hook ' +
'specifications to create NFQUEUE objects')

self.nfqueues = list()
for qno, hk in zip(qnos, callbacks):
self.pdebug(DNFQUEUE, ('Creating NFQUEUE object for chain %s / ' +
'table %s / queue # %d => %s') % (hk.chain, hk.table,
qno, str(hk.callback)))
q = LinuxDiverterNfqueue(qno, hk.chain, hk.table, hk.callback)
q = LinuxDiverterNfqueue(qno, hk.chain, hk.table, hk.callback,
fn_iface)
self.nfqueues.append(q)
ok = q.start()
if not ok:
Expand All @@ -154,7 +159,7 @@ def startCallback(self):
sys.exit(1)

if self.single_host_mode:

if self.is_set('fixgateway'):
if not self.linux_get_default_gw():
self.linux_set_default_gw()
Expand All @@ -166,32 +171,29 @@ def startCallback(self):
cmd = self.getconfigval('linuxflushdnscommand')
ret = subprocess.call(cmd.split())
if ret != 0:
self.logger.error('Failed to flush DNS cache. Local machine '
'may use cached DNS results.')
ok, rule = self.linux_redir_icmp()
self.logger.error('Failed to flush DNS cache. Local '
'machine may use cached DNS results.')

ok, rule = self.linux_redir_icmp(fn_iface)
if not ok:
self.logger.error('Failed to redirect ICMP')
self.stop()
sys.exit(1)
sys.exit(1)
self.rules_added.append(rule)

if self.is_configured('linuxredirectnonlocal'):
self.pdebug(DMISC, 'Processing LinuxRedirectNonlocal')
specified_ifaces = self.getconfigval('linuxredirectnonlocal')
self.pdebug(DMISC, 'Processing linuxredirectnonlocal on ' +
'interfaces: %s' % (specified_ifaces))
ok, rules = self.linux_iptables_redir_nonlocal(specified_ifaces)
self.pdebug(DMISC, 'Processing interface redirection on ' +
'interface: %s' % (fn_iface))
ok, rules = self.linux_iptables_redir_iface(fn_iface)

# Irrespective of whether this failed, we want to add any
# successful iptables rules to the list so that stop() will be able
# to remove them using linux_remove_iptables_rules().
self.rules_added += rules
# Irrespective of whether this failed, we want to add any
# successful iptables rules to the list so that stop() will be able
# to remove them using linux_remove_iptables_rules().
self.rules_added += rules

if not ok:
self.logger.error('Failed to process LinuxRedirectNonlocal')
self.stop()
sys.exit(1)
if not ok:
self.logger.error('Failed to process interface redirection')
self.stop()
sys.exit(1)

return True

Expand All @@ -217,7 +219,8 @@ def stopCallback(self):
if self.single_host_mode and self.is_set('modifylocaldns'):
self.linux_restore_local_dns()

self.linux_restore_iptables()
if self.is_set('linuxflushiptables'):
self.linux_restore_iptables()

return True

Expand Down