<|start_filename|>src/netintercept.c<|end_filename|>
/* standard includes */
#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>
#include <errno.h>
#include <memory.h>
#include <assert.h>
#include <limits.h>
#include <unistd.h>
#include <pthread.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/utsname.h>

#include <arpa/inet.h>

#include <link.h>

/* libpcap includes */
#include <pcap/pcap.h>

#include "netintercept.h"
#include "stream.h"

static struct netintercept_context ctx;
//static char type_string[128] = {0};
static __thread size_t lock_counter = 0;
/*
static char domain_string[128] = {0};

#define domain_case(domain)\
	case domain:\
		name = #domain;\
		break

static const char *domain_to_string(int domain) {
	const char *name = NULL;
	switch(domain) {
	domain_case(AF_UNIX);
	domain_case(AF_INET);
	domain_case(AF_INET6);
	domain_case(AF_IPX);
	domain_case(AF_NETLINK);
	domain_case(AF_X25);
	domain_case(AF_AX25);
	domain_case(AF_ATMPVC);
	domain_case(AF_APPLETALK);
	domain_case(AF_ALG);

	default:
		name = "UNKNOWN";
		break;
	}
	snprintf(domain_string, sizeof(domain_string), "%s(%d)", name, domain);
	return domain_string;
}
*/

/*
static const char *type_to_string(int domain) {
	const char *name = NULL;
	//if(SOCK_STREAM & )
	snprintf(type_string, sizeof(type_string), "%s(%d)", name, domain);
	return type_string;
}
*/

struct dl_iterate_phdr_data {
	const char *func_name;
	void *func_cur;
	void *func_ptr;
};

static int
dl_iterate_phdr_callback(struct dl_phdr_info *info, size_t __attribute__((unused)) size, void *__data)
{
	struct dl_iterate_phdr_data *data = (struct dl_iterate_phdr_data *)__data;
	void *dlh = dlopen(info->dlpi_name, RTLD_LAZY);
	void *func_ptr = dlsym(dlh, data->func_name);
	if(func_ptr && func_ptr != data->func_cur) {
		data->func_ptr = func_ptr;
		return 1;
	}
	return 0;
}

#define NETINTERCEPT_STACK_PUSH() lock_counter++
#define NETINTERCEPT_STACK_POP() lock_counter--
#define NETINTERCEPT_STACK_SIZE() lock_counter

#define NETINTERCEPT_SETUP_HOOK(hook_name, func_name)\
	netintercept_lock();\
	if(!ctx.hook_name) {\
		ctx.hook_name = dlsym(RTLD_NEXT, func_name);\
		if(!ctx.hook_name) {\
			struct dl_iterate_phdr_data data = {\
				func_name,\
				dlsym(RTLD_DEFAULT, func_name),\
				NULL\
			};\
			dl_iterate_phdr(dl_iterate_phdr_callback, &data);\
			if(!data.func_ptr) {\
				fprintf(stderr, "[%d] Could not hook %s\n", getpid(), func_name);\
				abort();\
			}\
			ctx.hook_name = data.func_ptr;\
		}\
	}\
	netintercept_unlock();

void netintercept_lock() {
	if(lock_counter == 1) {
		if(pthread_mutex_lock(&ctx.lock) != 0) {
			perror("pthread_mutex_lock");
			abort();
		}
	}
}

void netintercept_unlock() {
	if(lock_counter == 1) {
		if(pthread_mutex_unlock(&ctx.lock) != 0) {
			perror("pthread_mutex_unlock");
			abort();
		}
	}
}

void netintercept_dump(uint8_t *data, size_t len) {
	netintercept_lock();
	if(!ctx.pcap_dumper) {
		ctx.pcap_dumper = pcap_dump_open(ctx.pcap, ctx.path);
		if(!ctx.pcap_dumper) {
			fprintf(stderr, "pcap_dump_open error: could not open \"%s\"%s\n", ctx.path, pcap_geterr(ctx.pcap));
			abort();
		}
	}

	struct pcap_pkthdr pkthdr;
	memset(&pkthdr, 0, sizeof(pkthdr));

	gettimeofday(&pkthdr.ts, NULL);
	pkthdr.len = len;
	pkthdr.caplen = pkthdr.len;

	bool match = true;
	if(ctx.filter.bf_len) {
		match = pcap_offline_filter(&ctx.filter, &pkthdr, data);
	}

	if(match) {
		pcap_dump((uint8_t *)ctx.pcap_dumper, &pkthdr, data);
		pcap_dump_flush(ctx.pcap_dumper);
	}
	netintercept_unlock();
}

struct stream *netintercept_add_stream(struct stream *stream) {
	netintercept_lock();

	struct stream_node *node = ctx.head;
	while(node) {
		if(node->stream.fd == -1) {
			break;
		}
		node = node->next;
	}

	if(!node) {
		node = calloc(1, sizeof(*node));
		if(!node) {
			perror("calloc");
			abort();
		}
		node->prev = NULL;
		if(ctx.head) {
			ctx.head->prev = node;
		}
		node->next = ctx.head;
		ctx.head = node;
	}

	if(pthread_mutex_init(&stream->lock, NULL) != 0) {
		perror("pthread_mutex_init");
		abort();
	}

	node->stream = *stream;

	netintercept_unlock();

	return &node->stream;
}

struct stream *netintercept_get_stream(int fd, const struct sockaddr *remote_addr, socklen_t remote_addrlen) {

	netintercept_lock();

	struct stream *stream = NULL;
	struct stream_node *node = ctx.head;
	while(node) {
		if(node->stream.fd == fd) {
			stream = &node->stream;
		}
		node = node->next;
	}

	netintercept_unlock();

	struct stream new_stream;
	stream_init(&new_stream, fd, remote_addr, remote_addrlen);

	if(stream) {
		stream_lock(stream);
		new_stream.lock = stream->lock;
		if(stream_compare(stream, &new_stream) != 0 && S_ISSOCK(stream->mode)) {
			stream_close(stream, netintercept_dump);
			*stream = new_stream;
		}
		stream_unlock(stream);
		//printf("[%d] replacing old stream\n", getpid());
	} else {
		stream = netintercept_add_stream(&new_stream);
		//printf("[%d] creating new stream\n", getpid());
	}

	return stream;
}

void __attribute__((constructor)) netintercept_init(void) {
	if(pthread_mutex_init(&ctx.lock, NULL) != 0) {
		perror("pthread_mutex_init");
		abort();
	}

	pid_t pid = getpid();

	ctx.pcap = pcap_open_dead(DLT_RAW, 65535);
	if(!ctx.pcap) {
		perror("pcap_open_dead");
		abort();
	}

	const char *filter = getenv("NETINTERCEPT_FILTER");
	if(filter) {
		NETINTERCEPT_STACK_PUSH();
		if(pcap_compile(ctx.pcap, &ctx.filter, filter, 1, PCAP_NETMASK_UNKNOWN) != 0) {
			pcap_perror(ctx.pcap, "pcap_compile");
			abort();
		}
		NETINTERCEPT_STACK_POP();
	}

	const char *file = getenv("NETINTERCEPT_FILE");
	if(file) {
		time_t start = time(NULL);
		struct utsname utsname;
		memset(&utsname, 0, sizeof(utsname));
		if(uname(&utsname) != 0) {
			perror("uname");
			abort();
		}
		char self[PATH_MAX] = {0};
		ssize_t self_len = readlink("/proc/self/exe", self, sizeof(self));
		if(self_len < 0) {
			perror("readlink");
			abort();
		}
		self[self_len] = '\0';
		char *exe = strrchr(self, '/'), *slash = self;
		if(exe) {
			exe ++;
		} else {
			exe = self;
		}
		while((slash = strchr(slash, '/'))) {
			*slash = '!';
			slash++;
		}
		uid_t uid = getuid();

		size_t i, k, len = strlen(file);
		for(i = 0, k = 0; i < len && k+1 < sizeof(ctx.path); i++) {
			if(file[i] == '%' && i+1 < len) {
				i++;
				switch(file[i]) {
				case '%':
					ctx.path[k++] = file[i];
				break;
				case 'p':
					k += snprintf(ctx.path+k, sizeof(ctx.path)-k, "%d", pid);
				break;
				case 't':
					k += snprintf(ctx.path+k, sizeof(ctx.path)-k, "%ld", start);
				break;
				case 'h':
					k += snprintf(ctx.path+k, sizeof(ctx.path)-k, "%s", utsname.nodename);
				break;
				case 'e':
					k += snprintf(ctx.path+k, sizeof(ctx.path)-k, "%s", exe);
				break;
				case 'E':
					k += snprintf(ctx.path+k, sizeof(ctx.path)-k, "%s", self);
				break;
				case 'u':
					k += snprintf(ctx.path+k, sizeof(ctx.path)-k, "%d", uid);
				break;
				default:
				break;
				}
			} else {
				ctx.path[k++] = file[i];
			}
		}
	} else {
		snprintf(ctx.path, sizeof(ctx.path), "netintercept.pcap");
	}
}

void __attribute__((destructor)) netintercept_fini(void) {
	if(ctx.pcap_dumper) {
		pcap_dump_close(ctx.pcap_dumper);
	}
	pcap_close(ctx.pcap);
}

/*
int socket(int domain, int type, int protocol) {
	int orig_errno = errno;
	if(pthread_rwlock_wrlock(&ctx.rwlock) != 0) {
		perror("pthread_rwlock_wrlock");
		abort();
	}
	lock_counter++;

	errno = orig_errno;
	int sockfd = ctx.socket(domain, type, protocol);
	orig_errno = errno;

	printf("socket(%s, %d, %d)=%d\n",
	       domain_to_string(domain),
	       type,
	       protocol,
	       sockfd
	);

	lock_counter--;
	if(pthread_rwlock_unlock(&ctx.rwlock) != 0) {
		perror("pthread_rwlock_unlock");
		abort();
	}
	errno = orig_errno;
	return sockfd;
}
*/

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) {
	int orig_errno = errno;
	NETINTERCEPT_STACK_PUSH();

	NETINTERCEPT_SETUP_HOOK(connect, "connect");
	
	struct stream *stream = netintercept_get_stream(sockfd, NULL, 0);
	if(!stream) {
		fprintf(stderr, "%s error: could not get stream for file descriptor %d\n", __func__, sockfd);
		abort();
	}

	stream_lock(stream);

	errno = orig_errno;
	int ret = ctx.connect(sockfd, addr, addrlen);
	orig_errno = errno;
	//printf("connect(%d, %p, %u)=%d\n", sockfd, addr, addrlen, ret);

	if(ret == 0) {
		//printf("connecting socket %d\n", stream->fd);
		if(addr && addrlen) {
			stream_init(stream, sockfd, addr, addrlen);
		}
		stream_connect(stream, netintercept_dump);
	}

	stream_unlock(stream);

	NETINTERCEPT_STACK_POP();
	errno = orig_errno;
	return ret;
}

int shutdown(int sockfd, int how) {
	int orig_errno = errno;
	NETINTERCEPT_STACK_PUSH();

	NETINTERCEPT_SETUP_HOOK(shutdown, "shutdown");

	struct stream *stream = netintercept_get_stream(sockfd, NULL, 0);
	if(!stream) {
		fprintf(stderr, "%s error: could not get stream for file descriptor %d\n", __func__, sockfd);
		abort();
	}

	stream_lock(stream);

	errno = orig_errno;
	int ret = ctx.shutdown(sockfd, how);
	orig_errno = errno;

	//printf("shutdown(%d, %d)=%d\n", sockfd, how, ret);

	stream_unlock(stream);

	NETINTERCEPT_STACK_POP();
	errno = orig_errno;
	return ret;
}

int close(int fd) {
	int orig_errno = errno;
	NETINTERCEPT_STACK_PUSH();

	NETINTERCEPT_SETUP_HOOK(close, "close");

	struct stream *stream = netintercept_get_stream(fd, NULL, 0);
	if(!stream) {
		fprintf(stderr, "%s error: could not get stream for file descriptor %d\n", __func__, fd);
		abort();
	}

	stream_lock(stream);

	errno = orig_errno;
	int ret = ctx.close(fd);
	orig_errno = errno;
	//printf("close(%d)=%d\n", fd, ret);

	if(ret == 0 && S_ISSOCK(stream->mode)) {
		//printf("[%d] closing socket %d\n", getpid(), stream->fd);
		stream_close(stream, netintercept_dump);
		stream->fd = -1;
	}

	stream_unlock(stream);

	NETINTERCEPT_STACK_POP();
	errno = orig_errno;
	return ret;
}

ssize_t read(int fd, void *buf, size_t count) {
	int orig_errno = errno;
	NETINTERCEPT_STACK_PUSH();

	NETINTERCEPT_SETUP_HOOK(read, "read");

	struct stream *stream = NULL;
	if(lock_counter == 1) {
		stream = netintercept_get_stream(fd, NULL, 0);
		if(!stream) {
			fprintf(stderr, "%s error: could not get stream for file descriptor %d\n", __func__, fd);
			abort();
		}
		stream_lock(stream);
	}

	errno = orig_errno;
	ssize_t ret = ctx.read(fd, buf, count);
	orig_errno = errno;

	if(lock_counter == 1) {
		if(S_ISSOCK(stream->mode) && ret > 0) {
			stream_read(stream, buf, ret, netintercept_dump);
		}
		stream_unlock(stream);
	}

	NETINTERCEPT_STACK_POP();
	errno = orig_errno;
	return ret;
}

ssize_t write(int fd, const void *buf, size_t count) {
	int orig_errno = errno;
	NETINTERCEPT_STACK_PUSH();

	NETINTERCEPT_SETUP_HOOK(write, "write");

	struct stream *stream = NULL;
	if(lock_counter == 1) {
		stream = netintercept_get_stream(fd, NULL, 0);
		if(!stream) {
			fprintf(stderr, "%s error: could not get stream for file descriptor %d\n", __func__, fd);
			abort();
		}
		stream_lock(stream);
	}

	errno = orig_errno;
	ssize_t ret = ctx.write(fd, buf, count);
	orig_errno = errno;

	if(lock_counter == 1) {
		if(S_ISSOCK(stream->mode) && ret > 0) {
			stream_write(stream, buf, ret, netintercept_dump);
		}
		stream_unlock(stream);
	}

	NETINTERCEPT_STACK_POP();
	errno = orig_errno;
	return ret;
}

ssize_t recv(int sockfd, void *buf, size_t len, int flags) {
	int orig_errno = errno;
	NETINTERCEPT_STACK_PUSH();

	NETINTERCEPT_SETUP_HOOK(recv, __func__);

	struct stream *stream = NULL;
	if(lock_counter == 1) {
		stream = netintercept_get_stream(sockfd, NULL, 0);
		if(!stream) {
			fprintf(stderr, "%s error: could not get stream for file descriptor %d\n", __func__, sockfd);
			abort();
		}
		stream_lock(stream);
	}

	errno = orig_errno;
	ssize_t ret = ctx.recv(sockfd, buf, len, flags);
	orig_errno = errno;

	if(lock_counter == 1) {
		if(ret > 0) {
			stream_read(stream, buf, ret, netintercept_dump);
		}
		stream_unlock(stream);
	}

	NETINTERCEPT_STACK_POP();
	errno = orig_errno;
	return ret;
}

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen) {
	int orig_errno = errno;
	NETINTERCEPT_STACK_PUSH();

	NETINTERCEPT_SETUP_HOOK(recvfrom, __func__);

	struct stream *stream = NULL;
	if(lock_counter == 1) {
		stream = netintercept_get_stream(sockfd, NULL, 0);
		if(!stream) {
			fprintf(stderr, "%s error: could not get stream for file descriptor %d\n", __func__, sockfd);
			abort();
		}
		stream_lock(stream);
	}

	errno = orig_errno;
	ssize_t ret = ctx.recvfrom(sockfd, buf, len, flags, src_addr, addrlen);
	orig_errno = errno;

	if(lock_counter == 1) {
		if(ret > 0) {
			if(src_addr && addrlen) {
				stream_init(stream, sockfd, src_addr, *addrlen);
			}
			stream_read(stream, buf, ret, netintercept_dump);
		}
		stream_unlock(stream);
	}

	NETINTERCEPT_STACK_POP();
	errno = orig_errno;
	return ret;
}

ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags) {
	int orig_errno = errno;
	NETINTERCEPT_STACK_PUSH();

	NETINTERCEPT_SETUP_HOOK(recvmsg, __func__);

	struct stream *stream = NULL;
	if(lock_counter == 1) {
		stream = netintercept_get_stream(sockfd, NULL, 0);
		if(!stream) {
			fprintf(stderr, "%s error: could not get stream for file descriptor %d\n", __func__, sockfd);
			abort();
		}
		stream_lock(stream);
	}

	errno = orig_errno;
	ssize_t ret = ctx.recvmsg(sockfd, msg, flags);
	orig_errno = errno;

	if(lock_counter == 1) {
		if(ret > 0) {
			if(msg->msg_name && msg->msg_namelen) {
				stream_init(stream, sockfd, msg->msg_name, msg->msg_namelen);
			}
			uint8_t *data = malloc(ret);
			size_t i, bytes = (size_t)ret, offset = 0, length;
			for(i = 0; i < msg->msg_iovlen && offset < bytes; i++) {
				if(offset + msg->msg_iov[i].iov_len <= bytes) {
					length = msg->msg_iov[i].iov_len;
				} else {
					length = bytes - offset;
				}
				memcpy(data+offset, msg->msg_iov[i].iov_base, length);
				offset += msg->msg_iov[i].iov_len;
			}
			stream_read(stream, data, bytes, netintercept_dump);
			free(data);
		}
		stream_unlock(stream);
	}

	NETINTERCEPT_STACK_POP();
	errno = orig_errno;
	return ret;
}

ssize_t send(int sockfd, const void *buf, size_t len, int flags) {
	int orig_errno = errno;
	NETINTERCEPT_STACK_PUSH();

	NETINTERCEPT_SETUP_HOOK(send, __func__);

	struct stream *stream = NULL;
	if(lock_counter == 1) {
		stream = netintercept_get_stream(sockfd, NULL, 0);
		if(!stream) {
			fprintf(stderr, "%s error: could not get stream for file descriptor %d\n", __func__, sockfd);
			abort();
		}
		stream_lock(stream);
	}

	errno = orig_errno;
	ssize_t ret = ctx.send(sockfd, buf, len, flags);
	orig_errno = errno;

	if(lock_counter == 1) {
		if(ret > 0) {
			stream_write(stream, buf, ret, netintercept_dump);
		}
		stream_unlock(stream);
	}

	NETINTERCEPT_STACK_POP();
	errno = orig_errno;
	return ret;
}

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) {
	int orig_errno = errno;
	NETINTERCEPT_STACK_PUSH();

	NETINTERCEPT_SETUP_HOOK(sendto, __func__);

	struct stream *stream = NULL;
	if(lock_counter == 1) {
		stream = netintercept_get_stream(sockfd, NULL, 0);
		if(!stream) {
			fprintf(stderr, "%s error: could not get stream for file descriptor %d\n", __func__, sockfd);
			abort();
		}
		stream_lock(stream);
	}

	errno = orig_errno;
	ssize_t ret = ctx.sendto(sockfd, buf, len, flags, dest_addr, addrlen);
	orig_errno = errno;

	if(lock_counter == 1) {
		if(ret > 0) {
			if(dest_addr && addrlen) {
				stream_init(stream, sockfd, dest_addr, addrlen);
			}
			stream_write(stream, buf, ret, netintercept_dump);
		}
		stream_unlock(stream);
	}

	NETINTERCEPT_STACK_POP();
	errno = orig_errno;
	return ret;
}

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags) {
	int orig_errno = errno;
	NETINTERCEPT_STACK_PUSH();

	NETINTERCEPT_SETUP_HOOK(sendmsg, __func__);

	struct stream *stream = NULL;
	if(lock_counter == 1) {
		stream = netintercept_get_stream(sockfd, NULL, 0);
		if(!stream) {
			fprintf(stderr, "%s error: could not get stream for file descriptor %d\n", __func__, sockfd);
			abort();
		}
		stream_lock(stream);
	}

	errno = orig_errno;
	ssize_t ret = ctx.sendmsg(sockfd, msg, flags);
	orig_errno = errno;

	if(lock_counter == 1) {
		if(ret > 0) {
			if(stream->type == SOCK_DGRAM && msg->msg_name && msg->msg_namelen) {
				stream_init(stream, sockfd, msg->msg_name, msg->msg_namelen);
			}
			uint8_t *data = malloc(ret);
			size_t i, bytes = (size_t)ret, offset = 0, length;
			for(i = 0; i < msg->msg_iovlen && offset < bytes; i++) {
				if(offset + msg->msg_iov[i].iov_len <= bytes) {
					length = msg->msg_iov[i].iov_len;
				} else {
					length = bytes - offset;
				}
				memcpy(data+offset, msg->msg_iov[i].iov_base, length);
				offset += msg->msg_iov[i].iov_len;
			}
			stream_write(stream, data, bytes, netintercept_dump);
			free(data);
		}
		stream_unlock(stream);
	}

	NETINTERCEPT_STACK_POP();
	errno = orig_errno;
	return ret;
}

int __sendmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen, int flags) {
	int orig_errno = errno;
	NETINTERCEPT_STACK_PUSH();

	NETINTERCEPT_SETUP_HOOK(__sendmmsg, __func__);

	struct stream *stream = NULL;
	if(lock_counter == 1) {
		stream = netintercept_get_stream(sockfd, NULL, 0);
		if(!stream) {
			fprintf(stderr, "%s error: could not get stream for file descriptor %d\n", __func__, sockfd);
			abort();
		}
		stream_lock(stream);
	}

	errno = orig_errno;
	int ret = ctx.__sendmmsg(sockfd, msgvec, vlen, flags);
	orig_errno = errno;

	if(lock_counter == 1) {
		if(ret > 0 && (unsigned int)ret <= vlen) {
			int i;
			for(i = 0; i < ret; i++) {
				if(msgvec[i].msg_hdr.msg_iovlen != 1) {
					abort();
				}

				//printf("sendmmsg[%d]: msg_len=%d, msg_hdr.msg_iov[0].iov_len=%zu\n", i, msgvec[i].msg_len, msgvec[i].msg_hdr.msg_iov[0].iov_len);
				stream_write(stream, msgvec[i].msg_hdr.msg_iov[0].iov_base, msgvec[i].msg_len, netintercept_dump);
			}
		}
		stream_unlock(stream);
	}

	NETINTERCEPT_STACK_POP();
	errno = orig_errno;
	return ret;
}

#if OPENSSL_FOUND

int SSL_connect(SSL *ssl) {
	int orig_errno = errno;
	NETINTERCEPT_STACK_PUSH();

	NETINTERCEPT_SETUP_HOOK(ssl_get_rfd, "SSL_get_rfd");
	NETINTERCEPT_SETUP_HOOK(ssl_connect, __func__);

	int sockfd = ctx.ssl_get_rfd(ssl);
	struct stream *stream = NULL;
	if(lock_counter == 1) {
		stream = netintercept_get_stream(sockfd, NULL, 0);
		if(!stream) {
			fprintf(stderr, "%s error: could not get stream for file descriptor %d\n", __func__, sockfd);
			abort();
		}
		stream_lock(stream);
	}

	errno = orig_errno;
	int ret = ctx.ssl_connect(ssl);
	orig_errno = errno;

	if(lock_counter == 1) {
		stream_unlock(stream);
	}

	NETINTERCEPT_STACK_POP();
	errno = orig_errno;
	return ret;
}

int SSL_read(SSL *ssl, void *buf, int num) {
	int orig_errno = errno;
	NETINTERCEPT_STACK_PUSH();

	NETINTERCEPT_SETUP_HOOK(ssl_get_rfd, "SSL_get_rfd");
	NETINTERCEPT_SETUP_HOOK(ssl_read, __func__);

	int sockfd = ctx.ssl_get_rfd(ssl);
	struct stream *stream = NULL;
	if(lock_counter == 1) {
		stream = netintercept_get_stream(sockfd, NULL, 0);
		if(!stream) {
			fprintf(stderr, "%s error: could not get stream for file descriptor %d\n", __func__, sockfd);
			abort();
		}
		stream_lock(stream);
	}

	//printf("SSL_read: %p, ssl: %p, buf: %p, num: %d\n", orig_SSL_read, ssl, buf, num);
	errno = orig_errno;
	int ret = ctx.ssl_read(ssl, buf, num);
	orig_errno = errno;

	if(lock_counter == 1) {
		if(ret > 0) {
			stream_read(stream, buf, ret, netintercept_dump);
		}
		stream_unlock(stream);
	}

	NETINTERCEPT_STACK_POP();
	errno = orig_errno;
	return ret;
}

int SSL_write(SSL *ssl, const void *buf, int num) {
	int orig_errno = errno;
	NETINTERCEPT_STACK_PUSH();

	NETINTERCEPT_SETUP_HOOK(ssl_get_wfd, "SSL_get_wfd");
	NETINTERCEPT_SETUP_HOOK(ssl_write, __func__);

	int sockfd = ctx.ssl_get_wfd(ssl);
	struct stream *stream = NULL;
	if(lock_counter == 1) {
		stream = netintercept_get_stream(sockfd, NULL, 0);
		if(!stream) {
			fprintf(stderr, "%s error: could not get stream for file descriptor %d\n", __func__, sockfd);
			abort();
		}
		stream_lock(stream);
	}

	//printf("SSL_write: %p, ssl: %p, buf: %p, num: %d\n", ctx.ssl_write, ssl, buf, num);
	errno = orig_errno;
	int ret = ctx.ssl_write(ssl, buf, num);
	orig_errno = errno;

	if(lock_counter == 1) {
		if(ret > 0) {
			stream_write(stream, buf, ret, netintercept_dump);
		}
		stream_unlock(stream);
	}

	NETINTERCEPT_STACK_POP();
	errno = orig_errno;
	return ret;
}

int BIO_read(BIO *b, void *buf, int len) {
	int orig_errno = errno;
	NETINTERCEPT_STACK_PUSH();

	NETINTERCEPT_SETUP_HOOK(bio_read, __func__);

	errno = orig_errno;
	int ret = ctx.bio_read(b, buf, len);
	orig_errno = errno;

	if(lock_counter == 1 && ret > 0) {
		/*
		int fd = ctx.ssl_get_wfd(ssl);
		struct stream *stream = netintercept_get_stream(fd, NULL, 0);
		if(!stream) {
			fprintf(stderr, "could not find fd:%d\n", fd);
			abort();
		}

		stream_read(stream, buf, ret, netintercept_dump);
		*/
	}

	NETINTERCEPT_STACK_POP();
	errno = orig_errno;
	return ret;
}

int BIO_write(BIO *b, const void *buf, int len) {
	int orig_errno = errno;
	NETINTERCEPT_STACK_PUSH();

	NETINTERCEPT_SETUP_HOOK(bio_write, __func__);

	errno = orig_errno;
	int ret = ctx.bio_write(b, buf, len);
	orig_errno = errno;

	if(lock_counter == 1 && ret > 0) {
		/*
		int fd = ctx.ssl_get_wfd(ssl);
		struct stream *stream = netintercept_get_stream(fd, NULL, 0);
		if(!stream) {
			fprintf(stderr, "could not find fd:%d\n", fd);
			abort();
		}

		stream_write(stream, buf, ret, netintercept_dump);
		*/
	}

	NETINTERCEPT_STACK_POP();
	errno = orig_errno;
	return ret;
}

#endif // OPENSSL_FOUND

#if NSPR_FOUND

PRInt32 tcp_pt_Recv(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags, PRIntervalTime timeout) {
	int orig_errno = errno;
	NETINTERCEPT_STACK_PUSH();

	errno = orig_errno;
	PRInt32 ret = ctx.tcp_pt_recv(fd, buf, amount, flags, timeout);
	orig_errno = errno;

	NETINTERCEPT_STACK_POP();
	errno = orig_errno;
	return ret;
}

PRInt32 tcp_pt_Send(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags, PRIntervalTime timeout) {
	int orig_errno = errno;
	NETINTERCEPT_STACK_PUSH();

	errno = orig_errno;
	PRInt32 ret = ctx.tcp_pt_send(fd, buf, amount, flags, timeout);
	orig_errno = errno;

	NETINTERCEPT_STACK_POP();
	errno = orig_errno;
	return ret;
}

const PRIOMethods* PR_GetTCPMethods(void) {
	int orig_errno = errno;
	NETINTERCEPT_STACK_PUSH();

	PR_GetTCPMethods_t *pr_gettcpmethods = ctx.pr_gettcpmethods;
	NETINTERCEPT_SETUP_HOOK(pr_gettcpmethods, __func__);

	errno = orig_errno;
	const PRIOMethods* ret = ctx.pr_gettcpmethods();
	orig_errno = errno;

	if(!pr_gettcpmethods) {

		ctx.tcp_pt_recv = ((PRIOMethods*)ret)->recv;
		((PRIOMethods*)ret)->recv = &tcp_pt_Recv;

		ctx.tcp_pt_send = ((PRIOMethods*)ret)->send;
		((PRIOMethods*)ret)->send = &tcp_pt_Send;
	}

	NETINTERCEPT_STACK_POP();
	errno = orig_errno;
	return ret;
}

PRStatus PR_Connect(PRFileDesc *fd, const PRNetAddr *addr, PRIntervalTime timeout) {
	int orig_errno = errno;
	NETINTERCEPT_STACK_PUSH();

	NETINTERCEPT_SETUP_HOOK(pr_filedesc2nativehandle, "PR_FileDesc2NativeHandle");
	NETINTERCEPT_SETUP_HOOK(pr_connect, __func__);

	int sockfd = ctx.pr_filedesc2nativehandle(fd);
	struct stream *stream = NULL;
	if(lock_counter == 1) {
		stream = netintercept_get_stream(sockfd, NULL, 0);
		if(!stream) {
			fprintf(stderr, "%s error: could not get stream for file descriptor %d\n", __func__, sockfd);
			abort();
		}
		stream_lock(stream);
	}

	errno = orig_errno;
	PRStatus status = ctx.pr_connect(fd, addr, timeout);
	orig_errno = errno;

	if(lock_counter == 1) {
		stream_unlock(stream);
	}

	NETINTERCEPT_STACK_POP();
	errno = orig_errno;
	return status;
}

PRInt32 PR_Read(PRFileDesc *fd, void *buf, PRInt32 amount) {
	int orig_errno = errno;
	NETINTERCEPT_STACK_PUSH();

	NETINTERCEPT_SETUP_HOOK(pr_filedesc2nativehandle, "PR_FileDesc2NativeHandle");
	NETINTERCEPT_SETUP_HOOK(pr_read, __func__);

	int sockfd = ctx.pr_filedesc2nativehandle(fd);
	struct stream *stream = NULL;
	if(lock_counter == 1) {
		stream = netintercept_get_stream(sockfd, NULL, 0);
		if(!stream) {
			fprintf(stderr, "%s error: could not get stream for file descriptor %d\n", __func__, sockfd);
			abort();
		}
		stream_lock(stream);
	}

	errno = orig_errno;
	PRInt32 ret = ctx.pr_read(fd, buf, amount);
	orig_errno = errno;


	if(lock_counter == 1) {
		if(S_ISSOCK(stream->mode) && ret > 0) {
			stream_read(stream, buf, ret, netintercept_dump);
		}
		stream_unlock(stream);
	}

	NETINTERCEPT_STACK_POP();
	errno = orig_errno;
	return ret;
}

PRInt32 PR_Write(PRFileDesc *fd,const void *buf,PRInt32 amount) {
	int orig_errno = errno;
	NETINTERCEPT_STACK_PUSH();

	NETINTERCEPT_SETUP_HOOK(pr_filedesc2nativehandle, "PR_FileDesc2NativeHandle");
	NETINTERCEPT_SETUP_HOOK(pr_write, __func__);

	int sockfd = ctx.pr_filedesc2nativehandle(fd);
	struct stream *stream = NULL;
	if(lock_counter == 1) {
		stream = netintercept_get_stream(sockfd, NULL, 0);
		if(!stream) {
			fprintf(stderr, "%s error: could not get stream for file descriptor %d\n", __func__, sockfd);
			abort();
		}
		stream_lock(stream);
	}

	errno = orig_errno;
	PRInt32 ret = ctx.pr_write(fd, buf, amount);
	orig_errno = errno;


	if(lock_counter == 1) {
		if(S_ISSOCK(stream->mode) && ret > 0) {
			stream_write(stream, buf, ret, netintercept_dump);
		}
		stream_unlock(stream);
	}

	NETINTERCEPT_STACK_POP();
	errno = orig_errno;
	return ret;
}

//typedef PRInt32 PR_Writev_t(PRFileDesc *fd, const PRIOVec *iov, PRInt32 iov_size, PRIntervalTime timeout);
#endif // NSPR_FOUND


<|start_filename|>src/stream.c<|end_filename|>
#include <stdlib.h>
#include <stddef.h>
#include <memory.h>
#include <time.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <pcap/pcap.h>

#include <arpa/inet.h>

#include "stream.h"

uint16_t cksum_generic(unsigned char *p, size_t len, uint16_t initial)
{
	uint32_t sum = htons(initial);
	const uint16_t *u16 = (const uint16_t *)p;

	while (len >= (sizeof(*u16) * 4)) {
		sum += u16[0];
		sum += u16[1];
		sum += u16[2];
		sum += u16[3];
		len -= sizeof(*u16) * 4;
		u16 += 4;
	}
	while (len >= sizeof(*u16)) {
		sum += *u16;
		len -= sizeof(*u16);
		u16 += 1;
	}

	/* if length is in odd bytes */
	if (len == 1) {
		sum += *((const uint8_t *)u16);
	}

	while(sum>>16) {
		sum = (sum & 0xFFFF) + (sum>>16);
	}
	return ntohs((uint16_t)~sum);
}

// calculates the initial checksum value resulting from
// the pseudo header.
// return values:
// 0x0000 - 0xFFFF : initial checksum (in host byte order).
// 0xFFFF0001 : unknown packet (non IPv4/6 or non TCP/UDP)
// 0xFFFF0002 : bad header
uint32_t pseudo_header_initial(const int8_t *buf, size_t len)
{
	const uint16_t *hwbuf = (const uint16_t *)buf;
	int8_t ipv = (buf[0] & 0xF0) >> 4;
	int8_t proto = 0;
	int headersize = 0;

	if (ipv == 4) { // IPv4
		proto = buf[9];
		headersize = (buf[0] & 0x0F) * 4;
	} else if (ipv == 6) { // IPv6
		proto = buf[6];
		headersize = 40;
	} else {
		return 0xFFFF0001;
	}

	if (proto == 6 || proto == 17) { // TCP || UDP
		uint32_t sum = 0;
		len -= headersize;
		if (ipv == 4) { // IPv4
			if (cksum_generic((unsigned char *)buf, headersize, 0) != 0) {
				return 0xFFFF0002;
			}
			sum = htons(len & 0x0000FFFF) + (proto << 8)
							+ hwbuf[6]
							+ hwbuf[7]
							+ hwbuf[8]
							+ hwbuf[9];

		} else { // IPv6
			sum = hwbuf[2] + (proto << 8);
			int i;
			for (i = 4; i < 20; i+=4) {
				sum += hwbuf[i] +
				       hwbuf[i+1] +
				       hwbuf[i+2] +
				       hwbuf[i+3];
			}
		}
		sum = ((sum & 0xffff0000) >> 16) + (sum & 0xffff);
		sum = ((sum & 0xffff0000) >> 16) + (sum & 0xffff);
		return ntohs(sum);
	}
	return 0xFFFF0001;
}

static uint16_t *peer_len(int family, struct stream_peer *peer) {
	if(family == AF_INET) {
		return &peer->ip4.tot_len;
	} else if(family == AF_INET6) {
		return &peer->ip6.payload_len;
	}
	abort();
}

size_t stream_dump(int family, int protocol, struct stream_peer *peer, const uint8_t *data, size_t len, stream_dump_t *dump) {
	uint8_t pkt[1500], *ip_ptr = NULL, *trans_ptr = NULL;
	uint16_t *cksum_ptr = NULL;
	size_t ip_len = 0, trans_len = 0;

	if(family == AF_INET) {
		ip_ptr = (uint8_t *)&peer->ip4;
		ip_len = sizeof(peer->ip4);
	} else if(family == AF_INET6) {
		ip_ptr = (uint8_t *)&peer->ip6;
		ip_len = sizeof(peer->ip6);
	} else {
		abort();
	}

	if(protocol == IPPROTO_TCP) {
		trans_ptr = (uint8_t *)&peer->tcp;
		trans_len = sizeof(peer->tcp);
		cksum_ptr = (uint16_t *)(pkt + ip_len + offsetof(struct tcphdr, check));
	} else if(protocol == IPPROTO_UDP) {
		trans_ptr = (uint8_t *)&peer->udp;
		trans_len = sizeof(peer->udp);
		cksum_ptr = (uint16_t *)(pkt + ip_len + offsetof(struct udphdr, check));
	} else {
		abort();
	}

	if(len) {
		if(ip_len + trans_len + len > sizeof(pkt)) {
			len = sizeof(pkt) - ip_len - trans_len;
		}
		//printf("before: %d\n", ntohs(*peer->len));
		*peer_len(family, peer) = htons(ntohs(*peer_len(family, peer)) + len);
		//printf("after: %d\n", ntohs(*peer->len));
		if(protocol == IPPROTO_UDP) {
			peer->udp.len = htons(ntohs(peer->udp.len) + len);
		}
	}

	if(family == AF_INET) {
		peer->ip4.check = 0;
		peer->ip4.check = htons(cksum_generic((uint8_t *)&peer->ip4, sizeof(peer->ip4), 0));
	}

	memcpy(pkt, ip_ptr, ip_len);
	memcpy(pkt+ip_len, trans_ptr, trans_len);
	memcpy(pkt+ip_len+trans_len, data, len);

	*cksum_ptr = htons(cksum_generic(pkt+ip_len, trans_len + len, pseudo_header_initial((int8_t *)pkt, ip_len + trans_len + len)));

	dump(pkt, ip_len + trans_len + len);
	
	if(len) {
		*peer_len(family, peer) = htons(ntohs(*peer_len(family, peer)) - len);
		if(protocol == IPPROTO_UDP) {
			peer->udp.len = htons(ntohs(peer->udp.len) - len);
		}
	}

	return len;
}

void stream_init(struct stream *stream, int fd, const struct sockaddr *remote_addr, socklen_t remote_addrlen) {
	struct sockaddr_storage addr;
	struct sockaddr_in *addr_in;
	struct sockaddr_in6 *addr_in6;
	socklen_t addrlen;
	int optval;
	socklen_t optlen = sizeof(optval);
	struct stat sb;

	//if(stream->protocol == IPPROTO_TCP && stream->connected) {
	//	abort();
	//}

	memset(stream, 0, sizeof(*stream));

	stream->fd = fd;

	if(fstat(stream->fd, &sb) != 0) {
		//perror("fstat");
		return;
	}


	stream->mode = sb.st_mode;

	if(!S_ISSOCK(stream->mode)) {
		return;
	}
/*
	if(pthread_mutex_init(&stream->lock, NULL) != 0) {
		perror("pthread_mutex_init");
		abort();
	}
*/
	if(getsockopt(stream->fd, SOL_SOCKET, SO_PROTOCOL, &optval, &optlen) != 0) {
		perror("getsockopt");
		abort();
	}
	stream->protocol = optval;
	//printf("stream->protocol: %d\n", stream->protocol);

	if(getsockopt(stream->fd, SOL_SOCKET, SO_TYPE, &optval, &optlen) != 0) {
		perror("getsockopt");
		abort();
	}
	stream->type = optval;
	//printf("stream->type: %d\n", stream->type);

	addrlen = sizeof(addr);
	if(getsockname(stream->fd, (struct sockaddr *)&addr, &addrlen) != 0) {
		perror("getsockname");
		return;
	}

	uint16_t local_port, remote_port;

	stream->family = addr.ss_family;
	switch(stream->family) {
	case AF_INET:
		addr_in = (struct sockaddr_in *)&addr;
		
		stream->local.ip4.version = 4;
		stream->local.ip4.ihl = 5;
		stream->local.ip4.ttl = 64;
		stream->local.ip4.saddr = addr_in->sin_addr.s_addr;
		stream->remote.ip4.daddr = addr_in->sin_addr.s_addr;
		stream->local.ip4.protocol = stream->protocol;
		stream->local.ip4.tot_len = htons(sizeof(stream->local.ip4));

		local_port = addr_in->sin_port;

		inet_ntop(addr.ss_family, &addr_in->sin_addr, stream->local_addr, sizeof(stream->local_addr));
		//printf("client ip: %s\n", stream->local_addr);
	break;
	case AF_INET6:
		addr_in6 = (struct sockaddr_in6 *)&addr;

		stream->local.ip6.version = 6;
		stream->local.ip6.hop_limit = 64;
		stream->local.ip6.saddr = addr_in6->sin6_addr;
		stream->remote.ip6.daddr = addr_in6->sin6_addr;
		stream->local.ip6.nexthdr = stream->protocol;
		stream->local.ip6.payload_len = 0;

		local_port = addr_in6->sin6_port;

		inet_ntop(addr.ss_family, &addr_in6->sin6_addr, stream->local_addr, sizeof(stream->local_addr));
		//printf("client ip: %s\n", stream->local_addr);
	break;
	case AF_UNIX:
		return;
	default:
		//fprintf(stderr, "stream_init error: unknown family %d\n", stream->family);
		//abort();
		return;
	}

	addrlen = sizeof(addr);
	memset(&addr, 0, sizeof(addr));
	if(remote_addr) {
		//printf("stream_init: remote_addr=%p, remote_addrlen=%u\n", remote_addr, remote_addrlen);
		memcpy(&addr, remote_addr, addrlen<remote_addrlen?addrlen:remote_addrlen);
		addrlen = remote_addrlen;
	} else if(getpeername(stream->fd, (struct sockaddr *)&addr, &addrlen) != 0) {
		//perror("getpeername");
		return;
	}

	if(stream->family != addr.ss_family) {
		abort();
	}

	switch(stream->family) {
	case AF_INET:
		addr_in = (struct sockaddr_in *)&addr;

		stream->remote.ip4.version = 4;
		stream->remote.ip4.ihl = 5;
		stream->remote.ip4.ttl = 64;
		stream->local.ip4.daddr = addr_in->sin_addr.s_addr;
		stream->remote.ip4.saddr = addr_in->sin_addr.s_addr;
		stream->remote.ip4.protocol = stream->protocol;
		stream->remote.ip4.tot_len = htons(sizeof(stream->remote.ip4));

		remote_port = addr_in->sin_port;

		inet_ntop(addr.ss_family, &addr_in->sin_addr, stream->remote_addr, sizeof(stream->remote_addr));
		//printf("server ip: %s\n", stream->remote_addr);
	break;
	case AF_INET6:
		addr_in6 = (struct sockaddr_in6 *)&addr;

		stream->remote.ip6.version = 6;
		stream->remote.ip6.hop_limit = 64;
		stream->local.ip6.daddr = addr_in6->sin6_addr;
		stream->remote.ip6.saddr = addr_in6->sin6_addr;
		stream->remote.ip6.nexthdr = stream->protocol;
		stream->remote.ip6.payload_len = 0;

		remote_port = addr_in6->sin6_port;

		inet_ntop(addr.ss_family, &addr_in6->sin6_addr, stream->remote_addr, sizeof(stream->remote_addr));
		//printf("server ip: %s\n", stream->remote_addr);
	break;
	case AF_UNIX:
		return;
	break;
	default:
		//fprintf(stderr, "stream_init error: unknown family %d\n", stream->family);
		//abort();
		return;
	}

	if(stream->protocol == IPPROTO_TCP) {
		stream->local.tcp.source = local_port;
		stream->local.tcp.dest = remote_port;
		stream->local.tcp.doff = 5;
		stream->local.tcp.window = 2;
		//printf("debug1: %d\n", ntohs(*stream->local.len));
		*peer_len(stream->family, &stream->local) = htons(ntohs(*peer_len(stream->family, &stream->local)) + sizeof(stream->local.tcp));
		//printf("debug2: %d\n", ntohs(*stream->local.len));

		stream->remote.tcp.source = remote_port;
		stream->remote.tcp.dest = local_port;
		stream->remote.tcp.doff = 5;
		stream->remote.tcp.window = 2;
		//printf("debug3: %d\n", ntohs(*stream->remote.len));
		*peer_len(stream->family, &stream->remote) = htons(ntohs(*peer_len(stream->family, &stream->remote)) + sizeof(stream->remote.tcp));
		//printf("debug4: %d\n", ntohs(*stream->remote.len));
	} else if(stream->protocol == IPPROTO_UDP) {
		stream->local.udp.source = local_port;
		stream->local.udp.dest = remote_port;
		stream->local.udp.len = htons(8);
		*peer_len(stream->family, &stream->local) = htons(ntohs(*peer_len(stream->family, &stream->local)) + sizeof(stream->local.udp));

		stream->remote.udp.source = remote_port;
		stream->remote.udp.dest = local_port;
		stream->remote.udp.len = htons(8);
		*peer_len(stream->family, &stream->remote) = htons(ntohs(*peer_len(stream->family, &stream->remote)) + sizeof(stream->remote.udp));
	} else {
		fprintf(stderr, "stream_init error: unknown protocol %d\n", stream->protocol);
		//abort();
	}

	stream->seed = time(NULL);
}

void stream_lock(struct stream *stream) {
	if(pthread_mutex_unlock(&stream->lock) != 0) {
		perror("pthread_mutex_unlock");
		abort();
	}
}

void stream_unlock(struct stream *stream) {
	if(pthread_mutex_unlock(&stream->lock) != 0) {
		perror("pthread_mutex_unlock");
		abort();
	}
}

int stream_compare(struct stream *stream1, struct stream *stream2) {
	int cmp;

	cmp = stream1->fd - stream2->fd;

	if(cmp != 0) {
		//printf("fd: %d != %d\n", stream1->fd, stream2->fd);
		return cmp;
	}

	cmp = (stream1->mode&S_IFMT) - (stream2->mode&S_IFMT);

	if(cmp != 0) {
		//printf("mode: %d != %d\n", stream1->mode, stream2->mode);
		return cmp;
	}

	cmp = stream1->family - stream2->family;

	if(cmp != 0) {
		//printf("family: %d != %d\n", stream1->family, stream2->family);
		return cmp;
	}

	cmp = stream1->type - stream2->type;

	if(cmp != 0) {
		//printf("type: %d != %d\n", stream1->type, stream2->type);
		return cmp;
	}

	cmp = stream1->protocol - stream2->protocol;

	if(cmp != 0) {
		//printf("protocol: %d != %d\n", stream1->protocol, stream2->protocol);
		return cmp;
	}

	switch(stream1->family) {
	case AF_INET:
		cmp = memcmp(&stream1->local.ip4.saddr, &stream2->local.ip4.saddr, sizeof(stream1->local.ip4.saddr));
		
		if(cmp != 0) {
			//printf("ip4.saddr: %s != %s\n", stream1->local_addr, stream2->local_addr);
			return cmp;
		}

		cmp = memcmp(&stream1->local.ip4.daddr, &stream2->local.ip4.daddr, sizeof(stream1->local.ip4.daddr));
		
		if(cmp != 0) {
			//printf("ip4.saddr: %s != %s\n", stream1->local_addr, stream2->local_addr);
			return cmp;
		}
	break;
	case AF_INET6:
		cmp = memcmp(&stream1->local.ip6.saddr, &stream2->local.ip6.saddr, sizeof(stream1->local.ip6.saddr));

		if(cmp != 0) {
			//printf("ip6.saddr: %s != %s\n", stream1->local_addr, stream2->local_addr);
			return cmp;
		}

		cmp = memcmp(&stream1->local.ip6.daddr, &stream2->local.ip6.daddr, sizeof(stream1->local.ip6.daddr));

		if(cmp != 0) {
			//printf("ip6.daddr: %s != %s\n", stream1->local_addr, stream2->local_addr);
			return cmp;
		}
	break;
	case AF_UNIX:
		return 0;
	default:
		//fprintf(stderr, "stream_compare error: unknown family %d\n", stream1->family);
		return 0;
	}

	if(stream1->protocol == IPPROTO_TCP) {
		cmp = stream1->local.tcp.source - stream2->local.tcp.source;

		if(cmp != 0) {
			//printf("tcp.source: %d != %d\n", stream1->local.tcp.source, stream2->local.tcp.source);
			return cmp;
		}

		cmp = stream1->local.tcp.dest - stream2->local.tcp.dest;

		if(cmp != 0) {
			//printf("tcp.dest: %d != %d\n", stream1->local.tcp.dest, stream2->local.tcp.dest);
			return cmp;
		}
	} else if(stream1->protocol == IPPROTO_UDP) {
		cmp = stream1->local.udp.source - stream2->local.udp.source;

		if(cmp != 0) {
			//printf("udp.source: %d != %d\n", stream1->local.udp.source, stream2->local.udp.source);
			return cmp;
		}

		cmp = stream1->local.udp.dest - stream2->local.udp.dest;

		if(cmp != 0) {
			//printf("udp.dest: %d != %d\n", stream1->local.udp.dest, stream2->local.udp.dest);
			return cmp;
		}
	} else {
		//fprintf(stderr, "stream_compare error: unknown protocol %d\n", stream1->protocol);
		//abort();
	}

	return 0;
}

void stream_connect(struct stream *stream, stream_dump_t *dump) {
	if(!stream->connected && stream->protocol == IPPROTO_TCP) {

		// SYN
		stream->local.tcp.syn = 1;
		stream->local.tcp.seq = rand_r(&stream->seed);
		stream_dump(stream->family, stream->protocol, &stream->local, NULL, 0, dump);
		stream->local.tcp.seq = htonl(ntohl(stream->local.tcp.seq) + 1);
		stream->local.tcp.syn = 0;

		// SYN+ACK
		stream->remote.tcp.syn = 1;
		stream->remote.tcp.seq = rand_r(&stream->seed);
		stream->remote.tcp.ack = 1;
		stream->remote.tcp.ack_seq = stream->local.tcp.seq;
		stream_dump(stream->family, stream->protocol, &stream->remote, NULL, 0, dump);
		stream->remote.tcp.ack = 0;
		stream->remote.tcp.seq = htonl(ntohl(stream->remote.tcp.seq) + 1);
		stream->remote.tcp.syn = 0;

		// SYN
		stream->local.tcp.ack = 1;
		stream->local.tcp.ack_seq = stream->remote.tcp.seq;
		stream_dump(stream->family, stream->protocol, &stream->local, NULL, 0, dump);
		stream->local.tcp.ack = 0;

	}

	stream->connected = true;
}

void stream_write(struct stream *stream, const uint8_t *data, size_t len, stream_dump_t *dump) {
	size_t dumped = 0;

	stream_connect(stream, dump);

	if(stream->protocol == IPPROTO_TCP) {

		while(len > 0) {
			stream->local.tcp.ack = 1;
			stream->local.tcp.psh = 1;
			dumped = stream_dump(stream->family, stream->protocol, &stream->local, data, len, dump);
			data += dumped;
			len -= dumped;
			stream->local.tcp.seq = htonl(ntohl(stream->local.tcp.seq) + dumped);
			stream->remote.tcp.ack_seq = stream->local.tcp.seq;
			stream->local.tcp.psh = 0;
			stream->local.tcp.ack = 0;

			stream->remote.tcp.ack = 1;
			stream_dump(stream->family, stream->protocol, &stream->remote, NULL, 0, dump);
			stream->remote.tcp.ack = 0;
		}

	} else if(stream->protocol == IPPROTO_UDP) {

		// TODO: handle ip fragmentation
		dumped = stream_dump(stream->family, stream->protocol, &stream->local, data, len, dump);

	} else {

		//fprintf(stderr, "stream_write error: unknown protocol %d\n", stream->protocol);
		//abort();

	}
}

void stream_read(struct stream *stream, const uint8_t *data, size_t len, stream_dump_t *dump) {
	size_t dumped = 0;

	stream_connect(stream, dump);

	if(stream->protocol == IPPROTO_TCP) {

		//printf("[%d] stream_read(%d, %p, %zu)\n", getpid(), stream->fd, data, len);

		while(len) {
			stream->remote.tcp.ack = 1;
			stream->remote.tcp.psh = 1;
			dumped = stream_dump(stream->family, stream->protocol, &stream->remote, data, len, dump);
			data += dumped;
			len -= dumped;
			stream->remote.tcp.seq = htonl(ntohl(stream->remote.tcp.seq) + dumped);
			stream->local.tcp.ack_seq = stream->remote.tcp.seq;
			stream->remote.tcp.psh = 0;
			stream->remote.tcp.ack = 0;

			stream->local.tcp.ack = 1;
			stream_dump(stream->family, stream->protocol, &stream->local, NULL, 0, dump);
			stream->local.tcp.ack = 0;
		}

	} else if(stream->protocol == IPPROTO_UDP) {

		// TODO: handle ip fragmentation
		dumped = stream_dump(stream->family, stream->protocol, &stream->remote, data, len, dump);

	} else {

		//fprintf(stderr, "stream_read error: unknown protocol %d\n", stream->protocol);
		//abort();
	}
}

void stream_close(struct stream *stream, stream_dump_t *dump) {

	if(stream->connected && stream->protocol == IPPROTO_TCP) {

		// FIN
		stream->local.tcp.ack = 1;
		stream->local.tcp.fin = 1;
		stream_dump(stream->family, stream->protocol, &stream->local, NULL, 0, dump);
		stream->local.tcp.seq = htonl(ntohl(stream->local.tcp.seq) + 1);
		stream->remote.tcp.ack_seq = stream->local.tcp.seq;
		stream->local.tcp.fin = 0;
		stream->local.tcp.ack = 0;

		// ACK+FIN
		stream->remote.tcp.ack = 1;
		stream->remote.tcp.fin = 1;
		stream_dump(stream->family, stream->protocol, &stream->remote, NULL, 0, dump);
		stream->remote.tcp.seq = htonl(ntohl(stream->remote.tcp.seq) + 1);
		stream->local.tcp.ack_seq = stream->remote.tcp.seq;
		stream->remote.tcp.fin = 0;
		stream->remote.tcp.ack = 0;

		// ACK
		stream->local.tcp.ack = 1;
		stream_dump(stream->family, stream->protocol, &stream->local, NULL, 0, dump);
		stream->local.tcp.ack = 0;

	}
}

<|start_filename|>include/netintercept.h<|end_filename|>
#ifndef _NETINTERCEPT_H_
#define _NETINTERCEPT_H_

#include "config.h"

typedef int socket_t(int domain, int type, int protocol);
typedef int connect_t(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
typedef int shutdown_t(int sockfd, int how);
typedef int close_t(int fd);
typedef ssize_t write_t(int fd, const void *buf, size_t count);
typedef ssize_t read_t(int fd, void *buf, size_t count);

typedef ssize_t recv_t(int sockfd, void *buf, size_t len, int flags);
typedef ssize_t recvfrom_t(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
typedef ssize_t recvmsg_t(int sockfd, struct msghdr *msg, int flags);

typedef ssize_t send_t(int sockfd, const void *buf, size_t len, int flags);
typedef ssize_t sendto_t(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
typedef ssize_t sendmsg_t(int sockfd, const struct msghdr *msg, int flags);
typedef int sendmmsg_t(int sockfd, struct mmsghdr *msgvec, unsigned int vlen, int flags);

#if OPENSSL_FOUND

#include <openssl/bio.h>
#include <openssl/ssl.h>

typedef int SSL_connect_t(SSL *ssl);
typedef int SSL_read_t(SSL *ssl, void *buf, int num);
typedef int SSL_write_t(SSL *ssl, const void *buf, int num);
typedef int SSL_get_fd_t(const SSL *ssl);

typedef int BIO_read_t(BIO *b, void *buf, int len);
typedef int BIO_write_t(BIO *b, const void *buf, int len);
#endif // OPENSSL_FOUND

#if NSPR_FOUND

#include <nspr/prio.h>
#include <nspr/private/pprio.h>

typedef PROsfd PR_FileDesc2NativeHandle_t(PRFileDesc *);
typedef const PRIOMethods* PR_GetTCPMethods_t(void);
typedef const PRIOMethods* PR_GetUDPMethods_t(void);
typedef PRStatus PR_Connect_t(PRFileDesc *fd, const PRNetAddr *addr, PRIntervalTime timeout);
typedef PRInt32 PR_Read_t(PRFileDesc *fd, void *buf, PRInt32 amount);
typedef PRInt32 PR_Write_t(PRFileDesc *fd,const void *buf,PRInt32 amount);
typedef PRInt32 PR_Writev_t(PRFileDesc *fd, const PRIOVec *iov, PRInt32 iov_size, PRIntervalTime timeout);
#endif // NSPR_FOUND

/* <NSS> */
/*
typedef struct {
	PRFileDesc *fd;
} sslSocket;
typedef int ssl_DefSend_t(sslSocket *ss, const unsigned char *buf, int len, int flags);
typedef int ssl_SecureSend_t(sslSocket *ss, const unsigned char *buf, int len, int flags);
typedef int ssl_DefRecv_t(sslSocket *ss, unsigned char *buf, int len, int flags);
typedef int ssl_SecureRecv_t(sslSocket *ss, unsigned char *buf, int len, int flags);
*/
/* </NSS>*/

#include "stream.h"

struct stream_node {
	struct stream stream;
	struct stream_node *prev;
	struct stream_node *next;
};

struct netintercept_context {
	pcap_t *pcap;
	char path[PATH_MAX];
	pcap_dumper_t *pcap_dumper;
	struct bpf_program filter;

	socket_t *socket;
	connect_t *connect;
	shutdown_t *shutdown;
	close_t *close;
	read_t *read;
	write_t *write;

	recv_t *recv;
	recvfrom_t *recvfrom;
	recvmsg_t *recvmsg;

	send_t *send;
	sendto_t *sendto;
	sendmsg_t *sendmsg;
	sendmmsg_t *__sendmmsg;

#if OPENSSL_FOUND
	SSL_connect_t *ssl_connect;
	SSL_read_t *ssl_read;
	SSL_write_t *ssl_write;
	SSL_get_fd_t *ssl_get_rfd;
	SSL_get_fd_t *ssl_get_wfd;

	BIO_read_t *bio_read;
	BIO_write_t *bio_write;
#endif // OPENSSL_FOUND

#if NSPR_FOUND
	PR_FileDesc2NativeHandle_t *pr_filedesc2nativehandle;
	PR_GetTCPMethods_t *pr_gettcpmethods;
	PRSendFN tcp_pt_send;
	PRRecvFN tcp_pt_recv;
	PR_Connect_t *pr_connect;
	PR_Read_t *pr_read;
	PR_Write_t *pr_write;
#endif // NSPR_FOUND

/*
	ssl_DefSend_t *ssl_defsend;
	ssl_SecureSend_t *ssl_securesend;
	ssl_DefRecv_t *ssl_defrecv;
	ssl_SecureRecv_t *ssl_securerecv;
*/
	pthread_mutex_t lock;
	struct stream_node *head;
};

#endif // _NETINTERCEPT_H_


<|start_filename|>include/stream.h<|end_filename|>
#ifndef _NETINTERCEPT_STREAM_H_
#define _NETINTERCEPT_STREAM_H_

#include <stdint.h>
#include <stdbool.h>

/* linux includes */
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/tcp.h>
#include <linux/udp.h>

enum stream_direction {
	STREAM_DIRECTION_UNKNOWN = 0,
	STREAM_DIRECTION_UPLOAD, // client->server
	STREAM_DIRECTION_DOWNLOAD, // server->client
};

struct stream_peer {

	union {
		struct iphdr ip4;
		struct ipv6hdr ip6;
	};
	union {
		struct tcphdr tcp;
		struct udphdr udp;
	};
};

typedef void stream_dump_t(uint8_t *data, size_t len);

struct stream {
	int fd;
	mode_t mode;
	int family;
	int type;
	int protocol;
	unsigned int seed;
	bool connected;

	struct stream_peer local;
	char local_addr[INET6_ADDRSTRLEN];
	struct stream_peer remote;
	char remote_addr[INET6_ADDRSTRLEN];

	pthread_mutex_t lock;
};

void stream_init(struct stream *stream, int fd, const struct sockaddr *remote_addr, socklen_t remote_addrlen);
int stream_compare(struct stream *stream1, struct stream *stream2);
void stream_lock(struct stream *stream);
void stream_unlock(struct stream *stream);

void stream_connect(struct stream *stream, stream_dump_t *dump);
void stream_write(struct stream *stream, const uint8_t *data, size_t len, stream_dump_t *dump);
void stream_read(struct stream *stream, const uint8_t *data, size_t len, stream_dump_t *dump);
void stream_close(struct stream *stream, stream_dump_t *dump);

#endif // _NETINTERCEPT_STREAM_H_
