#!/usr/bin/python

# universe.py honeypot TCP server
# Accepts connections and reads all data without any processing
# Connection is closed after specified timeout

# 2012 Matthaeus Wander <matthaeus.wander@uni-due.de>

# Use iptables to redirect all incoming connection requests to the honeypot server:
# iptables -t nat -A PREROUTING -i eth0 -p tcp -m iprange --dst-range 192.0.2.100-192.0.2.200 -j REDIRECT --to-ports 11111

# You need to manually configure all IP addresses to your network device to make sure the kernel reacts to ARP requests from your router, e.g.:
# for i in {100..200}; do ip address add 192.0.2.$i/24 brd 192.0.2.255 dev eth0; done
# 
# Use another IP address outside the honeypot IP range for administrative SSH access.

import resource
import select
import socket
from time import time

# config
client_timeout = 600
# end of config

resource.setrlimit(resource.RLIMIT_NOFILE, (65536, 65536))

fdlimit = resource.getrlimit(resource.RLIMIT_NOFILE)[0]
fdlimit = fdlimit - 20 # generous tolerance to take STDIN etc. into account

server = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.setblocking(0)
server.bind(('', 11111))
server.listen(200)
sockmap = { }
endpointmap = {}

epoll = select.epoll()
epoll.register(server.fileno(), select.EPOLLIN)

lastactivity = { }
lastmaintenance = 0

def closesock(sock):
    fd = sock.fileno()
    sock.close()
    epoll.unregister(fd)
    del sockmap[fd]
    del lastactivity[fd]
    del endpointmap[fd]

while True:
    events = epoll.poll(client_timeout)
    curtime = time()
    # Maintenance
    if lastmaintenance + client_timeout <= curtime:
        print "* Maintenance, sockmap:", sockmap.keys()
        lastmaintenance = curtime
        timecap = curtime - client_timeout
        for fd in sockmap.keys():
            # skip listening socket
            if fd == server.fileno():
                continue

            print fd, "->", endpointmap[fd]
            if lastactivity[fd] < timecap:
                sock = sockmap[fd]
                print "Timeout for", endpointmap[fd]
                closesock(sock)
    else:
        for fd, event in events:
            # Connection arriving
            if fd == server.fileno():
                # approached limit of file descriptors, close old connection
                if len(sockmap) >= fdlimit:
                    oldsock = min(lastactivity.keys(), key=lambda x: lastactivity[x])
                    print "closing old socket due to fdlimit", endpointmap[oldsock]
                    closesock(sockmap[oldsock])
                try:
                    (sock, endpoint) = server.accept()
                    sock.setblocking(0)
                    print "Connection from", endpoint
                    fd = sock.fileno()
                    endpointmap[fd] = endpoint
                    epoll.register(fd, select.EPOLLIN)
                    sockmap[fd] = sock
                    lastactivity[fd] = curtime
                except socket.error as e:
                    # socket not created yet, no need to deregister
                    print e, "during accept() from", endpoint
            elif event & select.EPOLLIN: # Blackhole function
                sock = sockmap[fd]
                try:
                    buf = sock.recv(4096) # recv and ignore data
                    if len(buf) > 0:
                        print "read", len(buf), "bytes from", endpointmap[fd]
                        lastactivity[fd] = curtime
                    else:
                        print "connection closed by", endpointmap[fd]
                        closesock(sock)
                except socket.error as e:
                    print e, "from", endpointmap[fd]
                    closesock(sock)
            elif event & select.EPOLLHUP:
                sock = sockmap[fd]
                print "EPOLLHUP from", endpointmap[fd]
                closesock(sock)
