mirror of
https://github.com/pavel-odintsov/fastnetmon
synced 2024-11-24 02:46:36 +01:00
Rewrite code; Unification, divide functions to separate modules;
This commit is contained in:
parent
b520b2301e
commit
ef677000bb
@ -7,7 +7,11 @@ import os
|
|||||||
logging.basicConfig(filename='/var/log/firewall_queue_worker.log', level=logging.INFO)
|
logging.basicConfig(filename='/var/log/firewall_queue_worker.log', level=logging.INFO)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
firewall_backend = 'netmap_ipfw'
|
# netmap-ipfw or iptables
|
||||||
|
firewall_backend = 'iptables'
|
||||||
|
|
||||||
|
|
||||||
|
firewall_comment_text = "Received from: "
|
||||||
|
|
||||||
# u'destination-ipv4': [u'10.0.0.2/32'],
|
# u'destination-ipv4': [u'10.0.0.2/32'],
|
||||||
# u'destination-port': [u'=3128'],
|
# u'destination-port': [u'=3128'],
|
||||||
@ -16,24 +20,62 @@ firewall_backend = 'netmap_ipfw'
|
|||||||
# u'string': u'flow destination-ipv4 10.0.0.2/32 source-ipv4 10.0.0.1/32 protocol =tcp destination-port =3128'}
|
# u'string': u'flow destination-ipv4 10.0.0.2/32 source-ipv4 10.0.0.1/32 protocol =tcp destination-port =3128'}
|
||||||
|
|
||||||
class AbstractFirewall:
|
class AbstractFirewall:
|
||||||
pass
|
def generate_rules(self, peer_ip, pyflow_list):
|
||||||
|
generated_rules = []
|
||||||
|
for pyflow_rule in pyflow_list:
|
||||||
|
flow_is_correct = self.check_pyflow_rule_correctness(pyflow_rule)
|
||||||
|
|
||||||
|
if not flow_is_correct:
|
||||||
|
return
|
||||||
|
|
||||||
|
generated_rules.append(self.generate_rule(peer_ip, pyflow_rule))
|
||||||
|
|
||||||
|
return generated_rules
|
||||||
|
def check_pyflow_rule_correctness(self, pyflow_rule):
|
||||||
|
allowed_actions = [ 'allow', 'deny' ]
|
||||||
|
allowed_protocols = [ 'udp', 'tcp', 'all', 'icmp' ]
|
||||||
|
|
||||||
|
if not pyflow_rule['action'] in allowed_actions:
|
||||||
|
logger.info("Bad action: " + pyflow_rule['action'])
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not pyflow_rule['protocol'] in allowed_protocols:
|
||||||
|
logger.info("Bad protocol: " + pyflow_rule['protocol'])
|
||||||
|
return False
|
||||||
|
|
||||||
|
if len (pyflow_rule['source_port']) > 0 and not pyflow_rule['source_port'].isdigit():
|
||||||
|
logger.warning("Bad source port format")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if len (pyflow_rule['packet_length']) > 0 and not pyflow_rule['packet_length'].isdigit():
|
||||||
|
logger.warning("Bad packet length format")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if len (pyflow_rule['target_port']) > 0 and not pyflow_rule['target_port'].isdigit():
|
||||||
|
return "Bad target port: " + pyflow_rule['target_port']
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
# sport/dport suitable only for udp/tcp
|
|
||||||
# iptables -I FORWARD -p tcp/udp -s -d --sport 80 --dport XXX --fragment -m comment --comment "peer" -j DROP
|
|
||||||
class Iptables(AbstractFirewall):
|
class Iptables(AbstractFirewall):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.iptables_path = '/sbin/iptables'
|
self.iptables_path = '/sbin/iptables'
|
||||||
# In some cases we could work on INPUT/OUTPUT
|
# In some cases we could work on INPUT/OUTPUT
|
||||||
self.working_chain = 'FORWARD'
|
self.working_chain = 'FORWARD'
|
||||||
def flush_rules(self, peer_ip):
|
def flush_rules(self, peer_ip):
|
||||||
|
# iptables -nvL FORWARD -x --line-numbers
|
||||||
|
execute_command_with_shell(self.iptables_path, [ '--flush', self.working_chain ])
|
||||||
|
def flush(self):
|
||||||
execute_command_with_shell(self.iptables_path, [ '--flush', self.working_chain ])
|
execute_command_with_shell(self.iptables_path, [ '--flush', self.working_chain ])
|
||||||
def add_rules(self, peer_ip, pyflow_list):
|
def add_rules(self, peer_ip, pyflow_list):
|
||||||
for pyflow_rule in pyflow_list:
|
rules_list = self.generate_rules(peer_ip, pyflow_list)
|
||||||
flow_is_correct = check_pyflow_rule_correctness(pyflow_rule)
|
|
||||||
|
if rules_list != None and len(rules_list) > 0:
|
||||||
if not flow_is_correct:
|
for iptables_rule in rules_list:
|
||||||
return
|
execute_command_with_shell(self.iptables_path, iptables_rule)
|
||||||
|
else:
|
||||||
|
logger.error("Generated rule list is blank!")
|
||||||
|
def generate_rule(self, peer_ip, pyflow_rule):
|
||||||
iptables_arguments = ['-I', self.working_chain ]
|
iptables_arguments = ['-I', self.working_chain ]
|
||||||
|
|
||||||
if pyflow_rule['protocol'] != 'all':
|
if pyflow_rule['protocol'] != 'all':
|
||||||
@ -47,23 +89,32 @@ class Iptables(AbstractFirewall):
|
|||||||
|
|
||||||
# We have ports only for udp and tcp protocol
|
# We have ports only for udp and tcp protocol
|
||||||
if pyflow_rule['protocol'] == 'udp' or pyflow_rule['protocol'] == 'tcp':
|
if pyflow_rule['protocol'] == 'udp' or pyflow_rule['protocol'] == 'tcp':
|
||||||
if 'source_port' in pyflow_rule:
|
if 'source_port' in pyflow_rule and len(pyflow_rule['source_port']) > 0:
|
||||||
iptables_arguments.extend(['--sport', pyflow_rule['source_port']])
|
iptables_arguments.extend(['--sport', pyflow_rule['source_port']])
|
||||||
|
|
||||||
if 'target_port' in pyflow_rule:
|
if 'target_port' in pyflow_rule and len(pyflow_rule['target_port']) > 0:
|
||||||
iptables_arguments.extend(['--dport', pyflow_rule['target_port']])
|
iptables_arguments.extend(['--dport', pyflow_rule['target_port']])
|
||||||
|
|
||||||
|
|
||||||
iptables_arguments.extend([ '-m', 'comment', '--comment', '"' + "Received from: " + str(peer_ip) + '"' ])
|
if 'tcp_flags' in pyflow_rule and len(pyflow_rule['tcp_flags']) > 0:
|
||||||
|
# ALL means we check all flags for packet
|
||||||
|
iptables_arguments.extend([ '--tcp-flags', 'ALL', ",".join(pyflow_rule['tcp_flags'])])
|
||||||
|
|
||||||
if pyflow_rule['fragmentation']:
|
if pyflow_rule['fragmentation']:
|
||||||
iptables_arguments.extend(['--fragment'])
|
iptables_arguments.extend(['--fragment'])
|
||||||
|
|
||||||
pp = pprint.PrettyPrinter(indent=4)
|
|
||||||
logger.info("WIll run iptables command: " + pp.pformat(iptables_arguments))
|
|
||||||
execute_command_with_shell(self.iptables_path, iptables_arguments)
|
|
||||||
|
|
||||||
return True
|
iptables_arguments.extend([ '-m', 'comment', '--comment', firewall_comment_text + str(peer_ip) ])
|
||||||
|
|
||||||
|
# We could specify only range here, list is not allowed
|
||||||
|
if 'packet-length' in pyflow_rule:
|
||||||
|
iptables_arguments.extend(['-m', 'length', '--length', pyflow_rule[packet-length] ])
|
||||||
|
|
||||||
|
iptables_arguments.extend(['-j', 'DROP' ])
|
||||||
|
|
||||||
|
pp = pprint.PrettyPrinter(indent=4)
|
||||||
|
logger.info("Will run iptables command: " + pp.pformat(iptables_arguments))
|
||||||
|
|
||||||
|
return iptables_arguments
|
||||||
|
|
||||||
# Ban specific protocol:
|
# Ban specific protocol:
|
||||||
# ipfw add deny udp from any to 10.10.10.221/32
|
# ipfw add deny udp from any to 10.10.10.221/32
|
||||||
@ -105,9 +156,7 @@ class Ipfw(AbstractFirewall):
|
|||||||
|
|
||||||
args = [ self.netmap_path ]
|
args = [ self.netmap_path ]
|
||||||
# split interpret multiple spaces as single
|
# split interpret multiple spaces as single
|
||||||
args.extend( ipfw_command.split() )
|
args.extend( ipfw_command )
|
||||||
|
|
||||||
pp = pprint.PrettyPrinter(indent=4)
|
|
||||||
|
|
||||||
new_env = os.environ.copy()
|
new_env = os.environ.copy()
|
||||||
# Will fail without explicit conversion:
|
# Will fail without explicit conversion:
|
||||||
@ -121,26 +170,41 @@ class Ipfw(AbstractFirewall):
|
|||||||
# TODO: switch to another code parser
|
# TODO: switch to another code parser
|
||||||
self.execute_command_for_all_ipfw_backends("-f flush")
|
self.execute_command_for_all_ipfw_backends("-f flush")
|
||||||
def add_rules(self, peer_ip, pyflow_list):
|
def add_rules(self, peer_ip, pyflow_list):
|
||||||
for pyflow_rule in pyflow_list:
|
generated_rules = self.generate_rules(peer_ip, pyflow_list)
|
||||||
flow_is_correct = check_pyflow_rule_correctness(pyflow_rule)
|
|
||||||
|
for rule in generated_text_rules:
|
||||||
|
self.execute_command_for_all_ipfw_backends(rule)
|
||||||
|
def generate_rule(self, peer_ip, pyflow_rule):
|
||||||
|
# Add validity check for IP for source and target hosts
|
||||||
|
ipfw_command = "add %(action) %(protocol) from %(source_host) %(source_port) to %(target_host) %(target_port)".format(pyflow_rule)
|
||||||
|
|
||||||
if not flow_is_correct:
|
if pyflow_rule['fragmentation']:
|
||||||
return
|
ipfw_command += " frag"
|
||||||
|
|
||||||
# Add validity check for IP for source and target hosts
|
if 'tcp_flags' in pyflow_rule and len(pyflow_rule['tcp_flags']) > 0:
|
||||||
ipfw_command = "add %(action) %(protocol) from %(source_host) %(source_port) to %(target_host) %(target_port)".format(pyflow_rule)
|
ipfw_command += " tcpflags " + ','.join(pyflow_rule['tcp_flags']).lower()
|
||||||
|
|
||||||
|
# We could specify multiple values here
|
||||||
|
if 'packet-length' in pyflow_rule:
|
||||||
|
ipfw_command += " iplen " + pyflow_rule['packet-length']
|
||||||
|
|
||||||
if pyflow_rule['fragmentation']:
|
# Add comment
|
||||||
ipfw_command = ipfw_command + " frag"
|
ipfw_command += '//' + firewall_comment_text + peer_ip
|
||||||
|
|
||||||
# Add skip for multiple spaces to single
|
# Add skip for multiple spaces to single
|
||||||
logger.info( "We generated this command: " + ipfw_command )
|
logger.info( "We generated this command: " + ipfw_command )
|
||||||
|
|
||||||
|
return ipfw_command.split()
|
||||||
|
|
||||||
self.execute_command_for_all_ipfw_backends(ipfw_command)
|
firewall = None;
|
||||||
|
|
||||||
return True
|
if (firewall_backend == 'netmap-ipfw'):
|
||||||
|
firewall = Ipfw()
|
||||||
firewall = Iptables()
|
elif firewall_backend == 'iptables':
|
||||||
|
firewall = Iptables()
|
||||||
|
else:
|
||||||
|
logger.error("Firewall" + firewall_backend + " is not supported")
|
||||||
|
sys.exit("Firewall" + firewall_backend + " is not supported")
|
||||||
|
|
||||||
def manage_flow(action, peer_ip, flow):
|
def manage_flow(action, peer_ip, flow):
|
||||||
allowed_actions = [ 'withdrawal', 'announce' ]
|
allowed_actions = [ 'withdrawal', 'announce' ]
|
||||||
@ -172,11 +236,12 @@ def convert_exabgp_to_pyflow(flow):
|
|||||||
'target_host' : 'any',
|
'target_host' : 'any',
|
||||||
'fragmentation' : False,
|
'fragmentation' : False,
|
||||||
'packet_length' : 'any',
|
'packet_length' : 'any',
|
||||||
|
'tcp_flags' : [],
|
||||||
}
|
}
|
||||||
|
|
||||||
# But we definitely could have MULTIPLE ports here
|
# But we definitely could have MULTIPLE ports here
|
||||||
if 'packet-length' in flow:
|
if 'packet-length' in flow:
|
||||||
current_flow['packet_length'] = flow['packet-length'][0]
|
current_flow['packet_length'] = flow['packet-length'][0].lstrip('=')
|
||||||
|
|
||||||
# We support only one subnet for source and destination
|
# We support only one subnet for source and destination
|
||||||
if 'source-ipv4' in flow:
|
if 'source-ipv4' in flow:
|
||||||
@ -185,6 +250,10 @@ def convert_exabgp_to_pyflow(flow):
|
|||||||
if 'destination-ipv4' in flow:
|
if 'destination-ipv4' in flow:
|
||||||
current_flow['target_host'] = flow["destination-ipv4"][0]
|
current_flow['target_host'] = flow["destination-ipv4"][0]
|
||||||
|
|
||||||
|
if 'tcp-flags' in flow and len(flow['tcp-flags']) > 0:
|
||||||
|
for tcp_flag in flow['tcp-flags']:
|
||||||
|
current_flow['tcp_flags'].append(tcp_flag.lstrip('='))
|
||||||
|
|
||||||
if current_flow['source_host'] == "any" and current_flow['target_host'] == "any":
|
if current_flow['source_host'] == "any" and current_flow['target_host'] == "any":
|
||||||
logger.info( "We can't process this rule because it will drop whole traffic to the network" )
|
logger.info( "We can't process this rule because it will drop whole traffic to the network" )
|
||||||
return False
|
return False
|
||||||
@ -213,26 +282,4 @@ def convert_exabgp_to_pyflow(flow):
|
|||||||
|
|
||||||
return pyflow_list
|
return pyflow_list
|
||||||
|
|
||||||
def check_pyflow_rule_correctness(pyflow_rule):
|
|
||||||
allowed_actions = [ 'allow', 'deny' ]
|
|
||||||
allowed_protocols = [ 'udp', 'tcp', 'all', 'icmp' ]
|
|
||||||
|
|
||||||
if not pyflow_rule['action'] in allowed_actions:
|
|
||||||
logger.info("Bad action")
|
|
||||||
return False
|
|
||||||
|
|
||||||
if not pyflow_rule['protocol'] in allowed_protocols:
|
|
||||||
logger.info("Bad protocol")
|
|
||||||
return False
|
|
||||||
|
|
||||||
if len (pyflow_rule['source_port']) > 0 and not pyflow_rule['source_port'].isdigit():
|
|
||||||
logger.warning("Bad source port")
|
|
||||||
return False
|
|
||||||
|
|
||||||
if len (pyflow_rule['target_port']) > 0 and not pyflow_rule['target_port'].isdigit():
|
|
||||||
return "Bad target port: " + pyflow_rule['target_port']
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user