geneva/censors/censor9.py

107 lines
4.2 KiB
Python

"""
Censor 9 is a IP dropping TCB Teardown censor. It does not tear down its TCB,
but it will resynchronize it's TCB if a RST or FIN is sent if the full tuple
of the TCB matches (src, dst, sport, dport, seq).
More closely mimics GFW behavior.
"""
import logging
import layers.packet
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
from scapy.all import IP, TCP
from censors.censor import Censor
class Censor9(Censor):
def __init__(self, environment_id, forbidden, log_dir, log_level, port, queue_num):
Censor.__init__(self, environment_id, log_dir, log_level, port, queue_num)
self.forbidden = forbidden
self.tcb = {}
self.drop_all_from = None
self.resynchronize = False
def check_censor(self, packet, verbose=False):
"""
Check if the censor should run against this packet. Returns true or false.
"""
try:
self.logger.debug("Inbound packet to censor: " + layers.packet.Packet._str_packet(packet))
if self.drop_all_from == packet["IP"].src:
self.logger.debug("Dropping all from this IP %s..." % self.drop_all_from)
return True
# Only censor TCP packets for now
if "TCP" not in packet:
return False
# If we are in a resynchronization state, or we do not yet have a connection and a new one
# is being created, definte the TCB
if self.resynchronize or (not self.tcb and packet["TCP"].sprintf('%TCP.flags%') == "S"):
self.tcb["src"] = packet["IP"].src
self.tcb["dst"] = packet["IP"].dst
self.tcb["sport"] = packet["TCP"].sport
self.tcb["dport"] = packet["TCP"].dport
self.tcb["seq"] = packet["TCP"].seq
# If we're synchronizing on a SYN flag, need to add 1.
if packet["TCP"].sprintf('%TCP.flags%') == "S":
self.tcb["seq"] += 1
else:
self.tcb["seq"] += len(self.get_payload(packet))
self.resynchronize = False
self.logger.debug("Synchronizing TCB on packet " + layers.packet.Packet._str_packet(packet))
return self.check_forbidden(packet)
# If connection is getting torn down
elif self.tcb_matches(packet) and \
(packet["TCP"].sprintf('%TCP.flags%') == "R" or \
packet["TCP"].sprintf('%TCP.flags%') == "F"):
self.resynchronize = True
self.logger.debug(("Entering resynchronization state on packet " + layers.packet.Packet._str_packet(packet)))
if not self.tcb_matches(packet):
self.logger.debug("TCB does not match packet.")
return False
# Keep the TCB up to date
elif "seq" in self.tcb:
self.tcb["seq"] += len(self.get_payload(packet))
return self.check_forbidden(packet)
except Exception:
self.logger.exception("Exception caught by Censor 9")
return False
def censor(self, scapy_packet):
"""
Marks this IP to be dropped in the future and drops this packet.
"""
self.drop_all_from = scapy_packet["IP"].src
self.logger.debug("Marking IP %s for dropping..." % self.drop_all_from)
return "drop"
def check_forbidden(self, packet):
"""
Checks if a packet contains forbidden words.
"""
# Check if any forbidden words appear in the packet payload
for keyword in self.forbidden:
if keyword in self.get_payload(packet):
self.logger.debug("Packet triggered censor: " + layers.packet.Packet._str_packet(packet))
return True
return False
def tcb_matches(self, packet):
"""
Checks if the packet matches the stored TCB.
"""
self.logger.debug(self.tcb)
return not self.tcb or (self.tcb and \
packet["IP"].src == self.tcb["src"] and \
packet["IP"].dst == self.tcb["dst"] and \
packet["TCP"].sport == self.tcb["sport"] and \
packet["TCP"].dport == self.tcb["dport"] and \
packet["TCP"].seq == self.tcb["seq"])