class IPSResponse(RawResponse):
    _intfs = ((WAN_IN, interface.get_intf('wan')), )

    def _prepare_packet(self, packet, dnx_src_ip):
        # checking if dst port is associated with a nat. if so, will override necessary fields based on protocol
        # and re assign in the packert object
        # NOTE: can we please optimize this. PLEASE!
        port_override = self._Module.open_ports[packet.protocol].get(
            packet.dst_port)
        if port_override:
            self._packet_override(packet, dnx_src_ip, port_override)

        # 1a. generating tcp/pseudo header | iterating to calculate checksum
        if (packet.protocol is PROTO.TCP):
            protocol, checksum = PROTO.TCP, double_byte_pack(0, 0)
            for i in range(2):

                # packing tcp header
                tcp_header = tcp_header_pack(packet.dst_port, packet.src_port,
                                             696969, packet.seq_number + 1, 80,
                                             20, 0, checksum, 0)
                if i: break

                # packing pseudo header
                pseudo_header = [
                    pseudo_header_pack(dnx_src_ip, packet.src_ip.packed, 0, 6,
                                       20), tcp_header
                ]

                checksum = checksum_tcp(byte_join(pseudo_header))

            send_data = [tcp_header]
            ip_len = len(tcp_header) + 20

        # 1b. generating icmp header and payload iterating to calculate checksum
        elif (packet.protocol is PROTO.UDP):
            protocol, checksum = PROTO.ICMP, double_byte_pack(0, 0)
            for i in range(2):

                # packing icmp full
                if (packet.icmp_payload_override):
                    icmp_full = [
                        icmp_header_pack(3, 3, checksum, 0, 0),
                        packet.icmp_payload_override
                    ]

                else:
                    icmp_full = [
                        icmp_header_pack(3, 3, checksum, 0, 0),
                        packet.ip_header, packet.udp_header, packet.udp_payload
                    ]

                icmp_full = byte_join(icmp_full)
                if i: break

                checksum = checksum_icmp(icmp_full)

            send_data = [icmp_full]
            ip_len = len(icmp_full) + 20

        # 2. generating ip header with loop to create header, calculate zerod checksum, then rebuild
        # with correct checksum | append to send data
        checksum = double_byte_pack(0, 0)
        for i in range(2):

            # packing ip header
            ip_header = ip_header_pack(69, 0, ip_len, 0, 16384, 255, protocol,
                                       checksum, dnx_src_ip,
                                       packet.src_ip.packed)
            if i: break

            checksum = checksum_ipv4(ip_header)

        send_data.append(ip_header)

        # NOTE: we shouldnt have to track ethernet headers anymore
        # # 3. generating ethernet header | append to send data
        # send_data.append(eth_header_pack(
        #     packet.src_mac, self._dnx_src_mac, L2_PROTO
        # ))

        # returning with joined data from above
        return byte_join(reversed(send_data))

    def _packet_override(self, packet, dnx_src_ip, port_override):
        if (packet.protocol is PROTO.TCP):
            packet.dst_port = port_override

        elif (packet.protocol is PROTO.UDP):
            packet.udp_header = udp_header_pack(packet.src_port, port_override,
                                                packet.udp_len, packet.udp_chk)
            checksum = double_byte_pack(0, 0)
            for i in range(2):
                ip_header = ip_header_override_pack(packet.ip_header[:10],
                                                    checksum,
                                                    packet.src_ip.packed,
                                                    dnx_src_ip)
                if i: break
                checksum = checksum_ipv4(ip_header)

            # overriding packet ip header after process is complete. this will make the loops more efficient that
            # direct references to the instance object every time.
            packet.ip_header = ip_header

    #all packets need to be further examined in override method
    def _override_needed(self, packet):
        return True
class RawResponse:
    '''base class for managing raw socket operations for sending only. interfaces will be registered
    on startup to associate interface, zone, mac, ip, and active socket. the registration process will
    restart on an individual interface basis if a socket error occurs.'''
    __setup = False
    _Log = None
    _Module = None
    _registered_socks = {}
    _intfs = (
        (LAN_IN, interface.get_intf('lan')),
        (WAN_IN, interface.get_intf('wan')),
        #        (DMZ_IN, interface.get_intf('dmz'))
    )

    __slots__ = ('_intf', '_packet', '_dnx_src_mac', '_dnx_src_ip',
                 'send_data')

    def __new__(cls, *args, **kwargs):
        if (cls is RawResponse):
            raise TypeError('RawResponse can only be used via inheritance.')

        return object.__new__(cls)

    @classmethod
    def setup(cls, Module, Log):
        '''iterating over available interfaces, creating a new class instance, then calling
        internal register method in a new thread.'''
        if (cls.__setup):
            raise RuntimeError(
                'response handler setup can only be called once per process.')
        cls.__setup = True

        cls._Module = Module
        cls._Log = Log
        for intf in cls._intfs:
            self = cls()
            threading.Thread(target=self.__register, args=(intf, )).start()

    def __register(self, intf):
        '''will register interface with ip and socket. a new socket will be used every
        time this method is called.

        Do not override.

        '''
        zone, _intf = intf
        self._intf = _intf

        interface.wait_for_interface(interface=_intf)
        ip = interface.wait_for_ip(interface=_intf)
        mac = interface.get_mac(interface=_intf)

        self._registered_socks[zone] = NFQ_SOCK(*intf, mac, ip,
                                                self.listener_sock)

        self._Log.notice(f'{self.__class__.__name__}: {_intf} registered.')

    @classmethod
    def prepare_and_send(cls, packet):
        '''obtains socket object based on interface/zone receieved then prepares a raw packet (all layers).
        internal _send method will be called once finished.

        Do not override.

        '''
        zone = packet.zone

        self = cls()
        self._packet = packet
        # self._zone   = packet.zone

        self._intf = self._registered_socks.get(zone)
        self._dnx_src_mac = self._intf.mac
        self._dnx_src_ip = packet.dst_ip.packed

        #NOTE: if interface call fails, it will return none from raised exception.
        # see if this is ok, if we should pack after checking return, or return quads 0s.
        if (zone == WAN_IN
            ):  # TODO: if static assigned dont need, make a condition.
            self._dnx_src_ip = interface.get_src_ip(
                dst_ip=packet.src_ip).packed

        if (self._override_needed(packet)):
            self._packet_override(packet)

        self._prepare_packet(packet)

        self.__send()

    def _prepare_packet(self, packet):
        '''generates send data based on received packet data and interface/zone.

        Must be overriden.

        '''
        self.send_data = b''

    def _packet_override(self, packet):
        '''overrides protocol information to reverse pre route nat changes.

        May be overridden.

        '''
        pass

    def __send(self):
        '''sends generated data over obtained socket from prepare and send method. !MAYBE: if an error occurs
        a new interface registration thread will be called, but packet will be lost.

        Do not override.

        '''
        try:
            self._intf.sock.send(self.send_data)
        except OSError:
            pass

    def _override_needed(self, packet):
        '''property representing packet override condition, if this returns True the override method
        will be called from prepare and send. default is False.

        May be overriden.

        '''
        return False

    @property
    def listener_sock(self):
        '''returns new socket object to be used with interface registration.

        May be overriden.

        '''
        sock = socket.socket(socket.AF_PACKET, socket.SOCK_RAW)
        sock.bind((self._intf, 3))

        return sock
class RawResponse:
    '''base class for managing raw socket operations for sending data only. interfaces will be registered
    on startup to associate interface, zone, mac, ip, and active socket.'''

    __setup = False
    _Log = None
    _Module = None
    _registered_socks = {}
    _intfs = ((LAN_IN, get_intf('lan')), (WAN_IN, get_intf('wan')),
              (DMZ_IN, get_intf('dmz')))

    __slots__ = ('_intf', '_packet', '_dnx_src_ip', 'send_data')

    def __new__(cls, *args, **kwargs):
        if (cls is RawResponse):
            raise TypeError('RawResponse can only be used via inheritance.')

        return object.__new__(cls)

    def __init__(self, packet):
        self._packet = packet
        self.send_data = b''

    @classmethod
    def setup(cls, Module, Log):
        '''register all available interfaces in a separate thread for each. registration will wait for
        the interface to become available before finalizing.'''
        if (cls.__setup):
            raise RuntimeError(
                'response handler setup can only be called once per process.')
        cls.__setup = True

        cls._Module = Module
        cls._Log = Log

        # direct assignment for perf
        cls._registered_socks_get = cls._registered_socks.get

        for intf in cls._intfs:
            threading.Thread(target=cls.__register, args=(intf, )).start()

    @classmethod
    def __register(cls, intf):
        '''will register interface with ip and socket. a new socket will be used every
        time this method is called.

        Do not override.

        '''
        zone, _intf = intf

        wait_for_interface(interface=_intf)
        ip = wait_for_ip(interface=_intf)

        # sock sender is the direct reference to the socket send method
        cls._registered_socks[zone] = NFQ_SEND_SOCK(*intf, ip,
                                                    cls.sock_sender(_intf))

        cls._Log.notice(f'{cls.__name__}: {_intf} registered.')

    @classmethod
    def prepare_and_send(cls, packet):
        '''obtains socket object based on interface/zone receieved then prepares a raw packet (all layers).
        internal _send method will be called once finished.

        Do not override.

        '''
        zone = packet.zone

        self = cls(packet)

        intf = self._registered_socks_get(zone)

        # NOTE: if the wan interface has a static ip address we can use the ip assigned during registration
        # this will need a condition to check, but wont need to masquerade
        if (zone == WAN_IN):
            dnx_src_ip = get_src_ip(dst_ip=packet.src_ip, packed=True)

        else:
            dnx_src_ip = packet.dst_ip.packed

        # if (self._override_needed(packet)):
        #     self._packet_override(packet)

        # calling hook for packet generation in subclass and sending over direct socket send ref
        send_data = self._prepare_packet(packet, dnx_src_ip)
        try:
            intf.sock_sendto(send_data, (f'{packet.src_ip}', 0))
        except OSError:
            pass

    def _prepare_packet(self, packet, dnx_src_ip):
        '''generates send data based on received packet data and interface/zone.

        Must be overriden.

        '''
        raise NotImplementedError(
            '_prepare_packet method needs to be overridden by subclass.')

    def _packet_override(self, packet):
        '''overrides protocol information to reverse pre route nat changes.

        May be overridden.

        '''
        pass

    def _override_needed(self, packet):
        '''property representing packet override condition, if this returns True the override method
        will be called from prepare and send. default is False.

        May be overriden.

        '''
        return False

    @staticmethod
    def sock_sender(intf):
        '''returns new socket object to be used with interface registration.

        May be overriden.

        '''
        sock = socket.socket(socket.AF_INET, socket.SOCK_RAW,
                             socket.IPPROTO_RAW)

        return sock.sendto
class Listener:
    _Log = None
    _packet_parser = _NOT_IMPLEMENTED
    _proxy_callback = _NOT_IMPLEMENTED
    _intfs = (
        interface.get_intf('lan'),
        # interface.get_intf('dmz')
    )

    __slots__ = (
        # standard vars
        '_intf',
        '_intf_ip',
        '_threaded',
        '_name',

        # private vars
        '__epoll',
        '__registered_socks',
        '__epoll_poll',
        '__registered_socks_get')

    def __new__(cls, *args, **kwargs):
        if (cls is Listener):
            raise TypeError('Listener can only be used via inheritance.')

        return object.__new__(cls)

    def __init__(self, intf, threaded):
        '''general constructor. can only be reached through subclass.

        May be expanded.

        '''
        self._intf = intf
        self._threaded = threaded

        self._name = self.__class__.__name__

        if (self.is_service_loop):
            self.__epoll = select.epoll()
            self.__registered_socks = {}

            # assigning local reference to method itself to prevent lookups
            self.__epoll_poll = self.__epoll.poll
            self.__registered_socks_get = self.__registered_socks.get

    def __str__(self):
        return f'Listener/{self._name}(intf={self._intf})'

    @classmethod
    def run(cls, Log, *, threaded=True):
        '''associating subclass Log reference with Listener class. registering all interfaces in _intfs and starting service listener loop. calling class method setup before to
        provide subclass specific code to run at class level before continueing.'''
        Log.notice(f'{cls.__name__} initialization started.')
        # class setup
        cls._Log = Log
        cls._setup()

        # running main epoll/ socket loop. threaded so proxy and server can run side by side
        listener = cls(None, threaded)
        threading.Thread(target=listener.__listener).start()
        # starting a registration thread for all available interfaces
        # upon registration the threads will exit
        for intf in cls._intfs:
            self = cls(intf, threaded)
            threading.Thread(target=self.__register, args=(listener, )).start()

    @classmethod
    def _setup(cls):
        '''called prior to creating listener interface instances. module wide code can be ran here.

        May be overriden.

        '''
        pass

    # TODO: what happens if interface comes online, then immediately gets unplugged. the registration would fail potentially,
    # and would no longer be active so it would never happen if the interface was replugged after.
    def __register(self, listener, intf=None):
        '''will register interface with listener. requires subclass property for listener_sock returning valid socket object.
        once registration is complete the thread will exit.'''
        # this is being defined here the listener will be able to correlate socket back to interface and send in.
        _intf = intf if intf else self._intf
        self._Log.debug(
            f'{self._name} started interface registration for {_intf}')

        interface.wait_for_interface(interface=_intf)
        self._intf_ip = interface.wait_for_ip(interface=_intf)

        l_sock = self.listener_sock
        listener.__registered_socks[l_sock.fileno()] = L_SOCK(
            l_sock, _intf)  # TODO: make a namedtuple
        # TODO: if we dont re register, and im pretty sure i got rid of that, we shouldnt need to track the interface
        # anymore yea? the fd and socket object is all we need, unless we need to get the source ip address. OH. does the
        # dns proxy need to grab its interface ip for sending to the client? i dont think so, right? it jsut needs to
        # spoof the original destination.
        listener.__epoll.register(l_sock.fileno(), select.EPOLLIN)

        self._Log.notice(f'{self._name} | {_intf} registered.')

    @classmethod
    def set_proxy_callback(cls, *, func):
        '''takes a callback function to handle packets after parsing. the reference will be called
        as part of the packet flow with one argument passed in for "packet".'''
        if (not callable(func)):
            raise TypeError('proxy callback must be a callable object.')

        cls._proxy_callback = func

    @looper(NO_DELAY)
    def __listener(self):
        l_socks = self.__epoll_poll()
        for fd, _ in l_socks:
            sock_info = self.__registered_socks_get(fd)

            try:
                data, address = sock_info.socket.recvfrom(4096)
            except OSError:
                pass
            else:
                self.__parse_packet(data, address, sock_info)

    def __parse_packet(self, data, address, sock_info):
        packet = self._packet_parser(data, address, sock_info)
        try:
            packet.parse()
        except:
            traceback.print_exc()
        else:
            self.__after_parse(packet)

    def __after_parse(self, packet):
        if (self._pre_inspect(packet)):
            if (self._threaded):
                threading.Thread(target=self._proxy_callback,
                                 args=(packet, )).start()
            else:
                self._proxy_callback(packet)

    def _pre_inspect(self, packet):
        '''handle the request after packet is parsed and confirmed protocol match.

        Must be overriden.

        '''
        pass

    @property
    def listener_sock(self):
        '''returns instance level listener socket.

        Must be overridden.

        '''
        raise NotImplementedError(
            'the listenr sock property must be overriden in subclass.')

    @property
    def is_service_loop(self):
        '''boolean value representing whether current instance is a listener.'''
        return self._intf is None

    @classmethod
    def send_to_client(cls, packet):
        '''sending data generated by server over socket original data was recieved on.

        May be overridden.

        '''
        try:
            packet.sock.send(packet.send_data)
        except OSError:
            pass  # NOTE: make this nicer, this is incase socket gets shutdown midway
Beispiel #5
0
class IPSResponse(RawResponse):
    _intfs = ((WAN_IN, interface.get_intf('wan')), )

    def _prepare_packet(self, packet):
        # 1a. generating tcp/pseudo header | iterating to calculate checksum
        if (packet.protocol == PROTO.TCP):
            protocol, checksum = PROTO.TCP, double_byte_pack(0, 0)
            for i in range(2):
                tcp_header = tcp_header_pack(packet.dst_port, packet.src_port,
                                             696969, packet.seq_number + 1, 80,
                                             20, 0, checksum, 0)
                if i: break
                pseudo_header = [
                    pseudo_header_pack(self._dnx_src_ip, packet.src_ip.packed,
                                       0, 6, 20), tcp_header
                ]
                checksum = checksum_tcp(b''.join(pseudo_header))

            send_data = [tcp_header]

        # 1b. generating icmp header and payload iterating to calculate checksum
        elif (packet.protocol == PROTO.UDP):
            protocol, checksum = PROTO.ICMP, double_byte_pack(0, 0)
            for i in range(2):
                icmp_full = [icmp_header_pack(3, 3, checksum, 0, 0)]
                if (packet.icmp_payload_override):
                    icmp_full.append(packet.icmp_payload_override)
                else:
                    icmp_full.extend([
                        packet.ip_header, packet.udp_header, packet.udp_payload
                    ])
                if i: break
                checksum = checksum_icmp(b''.join(icmp_full))

            send_data = [icmp_full]

        # 2. generating ip header with loop to create header, calculate zerod checksum, then rebuild
        # with correct checksum | append to send data
        ip_len, checksum = 20 + len(b''.join(send_data)), double_byte_pack(
            0, 0)
        for i in range(2):
            ip_header = ip_header_pack(69, 0, ip_len, 0, 16384, 255, protocol,
                                       checksum, self._dnx_src_ip,
                                       packet.src_ip.packed)
            if i: break
            checksum = checksum_ipv4(ip_header)

        send_data.append(ip_header)

        # 3. generating ethernet header | append to send data
        send_data.append(
            eth_header_pack(packet.src_mac, self._dnx_src_mac, L2_PROTO))
        # assigning instance variable with joined data from above
        self.send_data = b''.join(reversed(send_data))

    def _packet_override(self, packet):
        port_override = self._Module.open_ports[packet.protocol].get(
            packet.dst_port)
        if (not port_override): return

        if (packet.protocol == PROTO.TCP):
            packet.dst_port = port_override

        elif (packet.protocol == PROTO.UDP):
            packet.udp_header = udp_header_pack(packet.src_port, port_override,
                                                packet.udp_len,
                                                packet.udp_check)
            checksum = double_byte_pack(0, 0)
            for i in range(2):
                ip_header = ip_header_pack(packet.ip_header[:10], checksum,
                                           packet.src_ip.packed,
                                           self._dnx_src_ip)
                if i: break
                checksum = checksum_ipv4(ip_header)

    #all packets need to be further examined in override method
    def _override_needed(self, packet):
        return True