# Trixy: Create network listeners, tunnels, and outbound connections in a
# modular way allowing interception and modification of the traffic.
#
# Copyright (C) 2014 Austin Hartzheim
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import asyncore
import asynchat
import socket
[docs]class TrixyNode():
'''
A base class for TrixyNodes that implements some default packet
forwarding and node linking.
'''
def __init__(self):
self.downstream_nodes = []
self.upstream_nodes = []
[docs] def add_downstream_node(self, node):
'''
Add a one direction downstream link to the node parameter.
:param TrixyNode node: The downstream node to create a
unidirectional link to.
'''
self.downstream_nodes.append(node)
[docs] def add_upstream_node(self, node):
'''
Add a one direction upstream link to the node parameter.
:param TrixyNode node: The upstream node to create a
unidirectional link to.
'''
self.upstream_nodes.append(node)
[docs] def connect_node(self, node):
'''
Create a bidirectional connection between the two nodes with
the downstream node being the parameter.
:param TrixyNode node: The downstream node to create a
bidirectional connection to.
'''
self.add_downstream_node(node)
node.add_upstream_node(self)
[docs] def forward_packet_down(self, data):
'''
Forward data to all downstream nodes.
:param bytes data: The data to forward.
'''
for node in self.downstream_nodes:
node.handle_packet_down(data)
[docs] def forward_packet_up(self, data):
'''
Forward data to all upstream nodes.
:param bytes data: The data to forward.
'''
for node in self.upstream_nodes:
node.handle_packet_up(data)
[docs] def handle_close(self, direction='down'):
'''
The connection has closed on one end. So, shutdown what we are
doing and notify the nodes we are connected to.
:param str direction: 'down' or 'up' depending on if downstream
nodes need to be closed, or upstream nodes need to be closed.
'''
if direction == 'down':
for node in self.downstream_nodes:
node.handle_close()
elif direction == 'up':
for node in self.upstream_nodes:
node.handle_close(direction='up')
[docs] def handle_packet_down(self, data):
'''
Hadle data moving downwards. TrixyProcessor children should
perform some action on `data` whereas `TrixyOutput` children
should send the data to the desired output location.
Generally, the a child implementation of this method should
be implemented such that it calls self.forward_packet_down
with the data (post-modification if necessary) to forward the
data to other processors in the chain. However, if the
processor is a filter, it may drop the packet by omitting that
call.
:param bytes data: The data that is being handled.
'''
self.forward_packet_down(data)
[docs] def handle_packet_up(self, data):
'''
Hadle data moving upwards. TrixyProcessor children should
perform some action on `data` whereas `TrixyOutput` children
should send the data to the desired output location.
Generally, the a child implementation of this method should
be implemented such that it calls self.forward_packet_down
with the data (post-modification if necessary) to forward the
data to other processors in the chain. However, if the
processor is a filter, it may drop the packet by omitting that
call.
:param bytes data: The data that is being handled.
'''
self.forward_packet_up(data)
[docs]class TrixyServer(asyncore.dispatcher):
'''
Main server to grab incoming connections and forward them.
'''
def __init__(self, tinput, host, port):
'''
:param TrixyInput tinput: instantiated every time an incoming
connection is grabbed.
'''
super().__init__()
self.tinput = tinput
self.setup_socket(host, port)
def setup_socket(self, host, port):
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.set_reuse_addr()
# self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.bind((host, port))
self.listen(100)
def handle_accepted(self, sock, addr):
handler = self.tinput(sock, addr)
def handle_close(self):
super().handle_close()
self.close()
[docs]class TrixyProcessor(TrixyNode):
'''
Perform processing on data moving through Trixy.
'''
pass
[docs]class TrixyOutput(TrixyNode, asyncore.dispatcher_with_send):
'''
Output the data, generally to another network service.
'''
#: Denotes whether assumed connections are assumed by the class.
supports_assumed_connections = True
def __init__(self, host, port, autoconnect=True):
'''
:param str host: The hostname to connect to.
:param int port: The port on the host to connect to.
:param bool autoconnect: Should we automatically connect to the
host and port when the object is made? (Default: yes.)
'''
super().__init__()
asyncore.dispatcher_with_send.__init__(self)
self.recvsize = 16384
self.host = host
self.port = port
self.setup_socket(host, port, autoconnect)
[docs] def setup_socket(self, host, port, autoconnect=True):
'''
Establish the outbound connection.
:param str host: The hostname to connect to.
:param int port: The port on the host to connect to.
:param bool autoconnect: Should the connection be established
now, or should it be manually triggered later?
'''
addr_info = socket.getaddrinfo(host, port)
self.create_socket(addr_info[0][0], addr_info[0][1])
if autoconnect:
self.connect((host, port))
[docs] def assume_connected(self, host, port, sock):
'''
Assume that the connection has already been made. Setup all
state accordingly. This is useful in situations where one
output wants to pass off work to a different output (for
example, a proxy output might establish the connection and
then pass it off to an SSL output (which needs to act on the
raw socket object).
'''
if not self.supports_assumed_connections:
raise NotImplementedError('No support for assumed connections')
self.host = host
self.port = port
self.set_socket(sock)
def handle_close(self, direction='up'):
super().handle_close(direction)
self.close()
def handle_read(self):
data = self.recv(self.recvsize)
self.handle_packet_up(data)
def handle_packet_down(self, data):
self.send(data)