/force_bind.c (fd1b612d553844c6aa7ac0835efe61820f40ef8c) (29878 bytes) (mode 100644) (type blob)

/*
 * Description: Force bind on a specified address
 * Author: Catalin(ux) M. BOIE
 * E-mail: catab at embedromix dot ro
 * Web: http://kernel.embedromix.ro/us/
 */

#define __USE_GNU
#define	_GNU_SOURCE
#define __USE_XOPEN2K
#define __USE_LARGEFILE64
#define __USE_FILE_OFFSET64

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <syslog.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <time.h>
#include <errno.h>
#include <dirent.h>
#include <asm/unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <poll.h>

#include "force_bind_config.h"


/* glibc may not be up-to-date at compile time */
#ifndef SO_MARK
#define SO_MARK			36 /* only on some architectures */
#endif

#ifndef SOCK_DCCP
#define SOCK_DCCP		6
#endif

#ifndef IPV6_FLOWINFO_SEND
#define IPV6_FLOWLABEL_MGR	32
#define IPV6_FLOWINFO_SEND	33

#define IPV6_FL_F_CREATE	1
#define IPV6_FL_A_GET		0
#define IPV6_FL_S_ANY		255

#define IPV6_FLOWINFO_MASK	0x0FFFFFFFUL
#define IPV6_FLOWLABEL_MASK	0x000FFFFFUL

struct in6_flowlabel_req {
	struct in6_addr flr_dst;
	unsigned int	flr_label;
	unsigned char	flr_action;
	unsigned char	flr_share;
	unsigned short	flr_flags;
	unsigned short	flr_expires;
	unsigned short	flr_linger;
	unsigned int	__flr_pad;
};
#endif

#define FB_FLAGS_NETSOCK 		(1 << 0)
#define FB_FLAGS_BIND_CALLED		(1 << 1)
#define FB_FLAGS_FLOWINFO_CALLED	(1 << 2)


struct private
{
	int			domain;
	int			type;
	unsigned int		flags;
	struct sockaddr_storage	dest;
	socklen_t		dest_len;

	/* bandwidth */
	unsigned long long	limit;
	unsigned long long	rest;
	struct timeval		last;
};

struct node
{
	int		fd;
	struct private	priv;
	struct node	*next;
};

struct info
{
	struct node	*head, *tail;
};


static int			(*old_bind)(int sockfd, const struct sockaddr *addr, socklen_t addrlen) = NULL;
static int			(*old_setsockopt)(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
static int			(*old_socket)(int domain, int type, int protocol);
static int			(*old_close)(int fd);
static ssize_t			(*old_write)(int fd, const void *buf, size_t len);
static ssize_t			(*old_send)(int sockfd, const void *buf, size_t len, int flags);
static ssize_t			(*old_sendto)(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
static ssize_t			(*old_sendmsg)(int sockfd, const struct msghdr *msg, int flags);
static int			(*old_accept)(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
static int			(*old_accept4)(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);
static int			(*old_connect)(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
static int			(*old_poll)(struct pollfd *fds, nfds_t nfds, int timeout);

static char			*force_address_v4 = NULL;
static char			*force_address_v6 = NULL;
static int			force_port_v4 = -1;
static int			force_port_v6 = -1;
static unsigned int		force_tos = 0, tos;
static unsigned int		force_ttl = 0, ttl;
static unsigned int		force_keepalive = 0, keepalive;
static unsigned int		force_mss = 0, mss;
static unsigned int		force_reuseaddr = 0, reuseaddr;
static unsigned int		force_nodelay = 0, nodelay;
static unsigned long long	bw_limit_per_socket = 0;
static unsigned int		force_flowinfo = 0, flowinfo;
static unsigned int		force_fwmark = 0, fwmark;
static unsigned int		force_prio = 0, prio;
static struct private		bw_global;
static struct info		fdinfo;
static unsigned int		verbose = 0;
static char			*log_file = NULL;
static FILE			*Log = NULL;
static int			force_poll_timeout = -1000;
static char			*force_v4_conn_error;
static char			*force_v6_conn_error;


/* Helper functions */
/*
 * Transform an error string into the coresponding code
 */
static int error_to_code(const char *e)
{
	if (strcmp(e, "refused") == 0)
		return ECONNREFUSED;

	if (strcmp(e, "unreach") == 0)
		return ENETUNREACH;

	return -1;
}

static char *sdomain(const int domain)
{
	static char tmp[16];

	switch (domain) {
	case AF_INET: return "IPv4";
	case AF_INET6: return "IPv6";
	default: snprintf(tmp, sizeof(tmp), "%d", domain); return tmp;
	}
}

static char *stype(const int type)
{
	static char tmp[16];

	switch (type & 0xfff) {
	case SOCK_STREAM: return "stream";
	case SOCK_DGRAM: return "dgram";
	case SOCK_RAW: return "raw";
	case SOCK_SEQPACKET: return "seqpacket";
	case SOCK_DCCP: return "dccp";
	case SOCK_PACKET: return "packet";
	default: snprintf(tmp, sizeof(tmp), "%d", type); return tmp;
	}
}

static char *sprotocol(const int protocol)
{
	static char tmp[16];

	switch (protocol) {
	case IPPROTO_TCP: return "tcp";
	case IPPROTO_UDP: return "udp";
	default: snprintf(tmp, sizeof(tmp), "%d", protocol); return tmp;
	}
}

/*
 * Fills @out with domain/type/address/port string
 */
static void saddr(char *out, const size_t out_len,
	const struct sockaddr_storage *ss)
{
	struct sockaddr_in *s4;
	struct sockaddr_in6 *s6;
	char addr[40], port[8];

	switch (ss->ss_family) {
	case AF_INET:
		s4 = (struct sockaddr_in *) ss;
		inet_ntop(AF_INET, (void *) &s4->sin_addr.s_addr, addr, sizeof(struct sockaddr_in));
		snprintf(port, sizeof(port), "%d", s4->sin_port);
		break;
	case AF_INET6:
		s6 = (struct sockaddr_in6 *) ss;
		inet_ntop(AF_INET6, (void *) &s6->sin6_addr.s6_addr, addr, sizeof(struct sockaddr_in6));
		snprintf(port, sizeof(port), "%d", s6->sin6_port);
		break;
	default:
		strcpy(addr, "?");
		strcpy(port, "?");
		break;
	}

	snprintf(out, out_len, "%s/%s/%s",
		sdomain(ss->ss_family), addr, port);
}

static void xlog(const unsigned int level, const char *format, ...)
{
	va_list ap;

	if (Log == NULL)
		return;

	if (level > verbose)
		return;

	va_start(ap, format);
	vfprintf(Log, format, ap);
	va_end(ap);
}

static void dump(const unsigned int level, const char *title, const void *buf,
	const unsigned int len)
{
	unsigned int i;
	unsigned char *buf2 = (unsigned char *) buf;
	char out[1024];

	for (i = 0; i < len; i++)
		snprintf(out + i * 3, 4, " %02x", buf2[i]);

	xlog(level, "force_bind: dump %s:%s\n", title, out);
}

static struct node *get(const int fd)
{
	struct node *p;

	p = fdinfo.head;
	while (p != NULL) {
		if (p->fd == fd)
			return p;

		p = p->next;
	}

	return NULL;
}

/*
 * List all sockets
 */
static void list(const unsigned int level)
{
	struct node *q;
	struct private *p;
	char dest[128];

	xlog(level, "force_bind: list...\n");

	q = fdinfo.head;
	while (q != NULL) {
		if (q->fd == -1) {
			q = q->next;
			continue;
		}

		p = &q->priv;
		saddr(dest, sizeof(dest), &p->dest);
		xlog(level, "\tfd=%4d type=%s flags=%04x limit=%llu"
			" rest=%llu last=%u.%06u dest=%s\n",
			q->fd, stype(p->type), p->flags, p->limit,
			p->rest, p->last.tv_sec, p->last.tv_usec, dest);
		q = q->next;
	}
}

static void add(const int fd, const struct private *p)
{
	struct node *q;

	xlog(2, "force_bind: add(fd=%d, ...)\n", fd);

	/* do we have a copy? */
	q = get(fd);
	if (q == NULL) {
		/* Try to find a free location */
		q = fdinfo.head;
		while (q != NULL) {
			if (q->fd == -1) {
				q->fd = fd;
				break;
			}

			q = q->next;
		}

		if (q == NULL) {
			q = (struct node *) malloc(sizeof(struct node));
			if (q == NULL) {
				xlog(0, "force_bind: cannot alloc memory"
					"; ignore fd!\n");
				return;
			}

			q->next = NULL;
			q->fd = fd;

			if (fdinfo.tail == NULL) {
				fdinfo.head = q;
			} else {
				fdinfo.tail->next = q;
			}
			fdinfo.tail = q;
		}
	}
	memcpy(&q->priv, p, sizeof(struct private));

	/* Set bandwidth requirements */
	q->priv.limit = bw_limit_per_socket;
	if (bw_limit_per_socket > 0) {
		q->priv.rest = 0;
		gettimeofday(&q->priv.last, NULL);
	}

	list(10);
}

static void del(const int fd)
{
	struct node *p;

	xlog(2, "force_bind: del(fd=%d)\n", fd);

	p = fdinfo.head;
	while (p != NULL) {
		if (p->fd == fd) {
			p->fd = -1;
			break;
		}

		p = p->next;
	}

	list(2);
}


/* Functions */

static void init(void)
{
	static unsigned char inited = 0;
	char *x;

	if (inited == 1)
		return;

	inited = 1;

	fdinfo.head = NULL;
	fdinfo.tail = NULL;

	log_file = getenv("FORCE_NET_LOG");
	if (log_file != NULL) {
		Log = fopen(log_file, "w");
		if (Log)
			setlinebuf(Log);
	} else {
		Log = stderr;
	}

	x = getenv("FORCE_NET_VERBOSE");
	if (x != NULL)
		verbose = (unsigned int) strtoul(x, NULL, 10);

	xlog(1, "force_bind: init started...\n");
	xlog(1, "force_bind: version: %s\n", FORCE_BIND_VERSION);

	x = getenv("FORCE_BIND_ADDRESS_V4");
	if (x != NULL) {
		force_address_v4 = x;
		xlog(1, "force_bind: conf: binding to IPv4 address \"%s\".\n",
			force_address_v4);
	}

	x = getenv("FORCE_BIND_ADDRESS_V6");
	if (x != NULL) {
		force_address_v6 = x;
		xlog(1, "force_bind: conf: binding to IPv6 address \"%s\".\n",
			force_address_v6);
	}

	/* obsolete mode */
	x = getenv("FORCE_BIND_ADDRESS");
	if (x != NULL) {
		force_address_v4 = x;
		force_address_v6 = x;
		xlog(1, "force_bind: conf: binding to address \"%s\"."
			" Obsolete, use FORCE_BIND_ADDRESS_V4/6.\n",
			force_address_v4);
	}

	x = getenv("FORCE_BIND_PORT_V4");
	if (x != NULL) {
		force_port_v4 = (int) strtol(x, NULL, 10);
		xlog(1, "force_bind: conf: binding to port %d.\n",
			force_port_v4);
	}

	x = getenv("FORCE_BIND_PORT_V6");
	if (x != NULL) {
		force_port_v6 = (int) strtol(x, NULL, 10);
		xlog(1, "force_bind: conf: binding to port %d.\n",
			force_port_v6);
	}

	/* obsolete mode */
	x = getenv("FORCE_BIND_PORT");
	if (x != NULL) {
		force_port_v4 = (int) strtol(x, NULL, 10);
		force_port_v6 = (int) strtol(x, NULL, 10);
		xlog(1, "force_bind: conf: binding to port %d."
			" Obsolete, use FORCE_BIND_PORT_V4/6.\n",
			force_port_v4);
	}

	/* tos */
	x = getenv("FORCE_NET_TOS");
	if (x != NULL) {
		force_tos = 1;
		tos = (unsigned int) strtoul(x, NULL, 0);
		xlog(1, "force_bind: conf: forcing TOS to %hhu.\n",
			tos);
	}

	/* ttl */
	x = getenv("FORCE_NET_TTL");
	if (x != NULL) {
		force_ttl = 1;
		ttl = (unsigned int) strtoul(x, NULL, 0);
		xlog(1, "force_bind: conf: forcing TTL to %hhu.\n",
			ttl);
	}

	/* keep alive */
	x = getenv("FORCE_NET_KA");
	if (x != NULL) {
		force_keepalive = 1;
		keepalive = (unsigned int) strtoul(x, NULL, 0);
		xlog(1, "force_bind: conf: forcing KA to %u.\n",
			keepalive);
	}

	/* mss */
	x = getenv("FORCE_NET_MSS");
	if (x != NULL) {
		force_mss = 1;
		mss = (unsigned int) strtoul(x, NULL, 0);
		xlog(1, "force_bind: conf: forcing MSS to %u.\n",
			mss);
	}

	/* REUSEADDR */
	x = getenv("FORCE_NET_REUSEADDR");
	if (x != NULL) {
		force_reuseaddr = 1;
		reuseaddr = (unsigned int) strtoul(x, NULL, 0);
		xlog(1, "force_bind: conf: forcing REUSEADDR to %u.\n",
			reuseaddr);
	}

	/* NODELAY */
	x = getenv("FORCE_NET_NODELAY");
	if (x != NULL) {
		force_nodelay = 1;
		nodelay = (unsigned int) strtoul(x, NULL, 0);
		xlog(1, "force_bind: conf: forcing NODELAY to %u.\n",
			nodelay);
	}

	/* bandwidth */
	x = getenv("FORCE_NET_BW");
	if (x != NULL) {
		bw_global.limit = (unsigned int) strtoul(x, NULL, 0);
		gettimeofday(&bw_global.last, NULL);
		bw_global.rest = 0;
		xlog(1, "force_bind: conf: forcing bandwidth to %llub/s.\n",
			bw_global.limit);
	} else {
		bw_global.limit = 0;
	}

	/* bandwidth per socket */
	x = getenv("FORCE_NET_BW_PER_SOCKET");
	if (x != NULL) {
		if (bw_global.limit > 0) {
			xlog(1, "force_bind: conf: cannot set limit per socket"
				" when global one is set.\n");
		} else {
			bw_limit_per_socket = (unsigned int) strtoul(x, NULL, 0);
			xlog(1, "force_bind: conf: forcing bandwidth per socket to %llub/s.\n",
				bw_limit_per_socket);
		}
	}

	/* IPv6 flowinfo */
	x = getenv("FORCE_NET_FLOWINFO");
	if (x != NULL) {
		force_flowinfo = 1;
		flowinfo = (unsigned int) strtoul(x, NULL, 0) & IPV6_FLOWINFO_MASK;
		xlog(1, "force_bind: conf: forcing FLOWINFO to 0x%x.\n",
			flowinfo);
	}

	/* fwmark */
	x = getenv("FORCE_NET_FWMARK");
	if (x != NULL) {
		force_fwmark = 1;
		fwmark = (unsigned int) strtoul(x, NULL, 0);
		xlog(1, "force_bind: conf: forcing fwmark to 0x%x.\n",
			fwmark);
	}

	/* prio */
	x = getenv("FORCE_NET_PRIO");
	if (x != NULL) {
		force_prio = 1;
		prio = (unsigned int) strtoul(x, NULL, 0);
		xlog(1, "force_bind: conf: forcing prio to %u.\n",
			prio);
	}

	/* poll timeout */
	x = getenv("FORCE_NET_POLL_TIMEOUT");
	if (x != NULL) {
		force_poll_timeout = (int) strtoul(x, NULL, 0);
		xlog(1, "force_bind: conf: forcing poll timeout to %d.\n",
			force_poll_timeout);
	}

	x = getenv("FORCE_NET_V6_CONN_ERROR");
	if (x != NULL) {
		force_v6_conn_error = x;
		xlog(1, "force_bind: conf: forcing ipv6 connection refused to %s.\n",
			force_v6_conn_error);
	}

	x = getenv("FORCE_NET_V4_CONN_ERROR");
	if (x != NULL) {
		force_v4_conn_error = x;
		xlog(1, "force_bind: conf: forcing ipv4 connection refused to %s.\n",
			force_v4_conn_error);
	}

	/******** Now, hijack system calls ********/

	old_bind = dlsym(RTLD_NEXT, "bind");
	if (old_bind == NULL) {
		xlog(0, "force_bind: cannot resolve 'bind'!\n");
		exit(1);
	}

	old_setsockopt = dlsym(RTLD_NEXT, "setsockopt");
	if (old_setsockopt == NULL) {
		xlog(0, "force_bind: cannot resolve 'setsockopt'!\n");
		exit(1);
	}

	old_socket = dlsym(RTLD_NEXT, "socket");
	if (old_socket == NULL) {
		xlog(0, "force_bind: cannot resolve 'socket'!\n");
		exit(1);
	}

	old_close = dlsym(RTLD_NEXT, "close");
	if (old_close == NULL) {
		xlog(0, "force_bind: cannot resolve 'close'!\n");
		exit(1);
	}

	old_write = dlsym(RTLD_NEXT, "write");
	if (old_write == NULL) {
		xlog(0, "force_bind: cannot resolve 'write'!\n");
		exit(1);
	}

	old_send = dlsym(RTLD_NEXT, "send");
	if (old_send == NULL) {
		xlog(0, "force_bind: cannot resolve 'send'!\n");
		exit(1);
	}

	old_sendto = dlsym(RTLD_NEXT, "sendto");
	if (old_sendto == NULL) {
		xlog(0, "force_bind: cannot resolve 'sendto'!\n");
		exit(1);
	}

	old_sendmsg = dlsym(RTLD_NEXT, "sendmsg");
	if (old_sendmsg == NULL) {
		xlog(0, "force_bind: cannot resolve 'sendmsg'!\n");
		exit(1);
	}

	old_accept = dlsym(RTLD_NEXT, "accept");
	if (old_accept == NULL) {
		xlog(0, "force_bind: cannot resolve 'accept'!\n");
		exit(1);
	}

	old_accept4 = dlsym(RTLD_NEXT, "accept4");
	if (old_accept4 == NULL) {
		xlog(0, "force_bind: cannot resolve 'accept4'!\n");
		exit(1);
	}

	old_connect = dlsym(RTLD_NEXT, "connect");
	if (old_connect == NULL) {
		xlog(0, "force_bind: cannot resolve 'connect'!\n");
		exit(1);
	}

	old_poll = dlsym(RTLD_NEXT, "poll");
	if (old_poll == NULL) {
		xlog(0, "force_bind: cannot resolve 'poll'!\n");
		exit(1);
	}

	xlog(1, "force_bind: init ended.\n");
}

static int set_ka(int sockfd)
{
	int flag, ret;

	if (force_keepalive == 0)
		return 0;

	flag = (keepalive > 0) ? 1 : 0;
	ret = old_setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &flag, sizeof(flag));
	xlog(1, "force_bind: changing SO_KEEPALIVE to %d (ret=%d(%s)) [%d].\n",
		flag, ret, strerror(errno), sockfd);

	return ret;
}

static int set_ka_idle(int sockfd)
{
	int ret;

	if (force_keepalive == 0)
		return 0;

	ret = old_setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, &keepalive, sizeof(keepalive));
	xlog(1, "force_bind: changing TCP_KEEPIDLE to %us (ret=%d(%s)) [%d].\n",
		keepalive, ret, strerror(errno), sockfd);

	return ret;
}

static int set_mss(int sockfd)
{
	int ret;

	if (force_mss == 0)
		return 0;

	ret = old_setsockopt(sockfd, IPPROTO_TCP, TCP_MAXSEG, &mss, sizeof(mss));
	xlog(1, "force_bind: changing MSS to %u (ret=%d(%s)) [%d].\n",
		mss, ret, strerror(errno), sockfd);

	return ret;
}

static int set_tos(int sockfd)
{
	int ret;

	if (force_tos == 0)
		return 0;

	ret = old_setsockopt(sockfd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
	xlog(1, "force_bind: changing TOS to %hhu (ret=%d(%s)) [%d].\n",
		tos, ret, strerror(errno), sockfd);

	return ret;
}

static int set_ttl(int sockfd)
{
	int ret;

	if (force_ttl == 0)
		return 0;

	ret = old_setsockopt(sockfd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl));
	xlog(1, "force_bind: changing TTL to %hhu (ret=%d(%s)) [%d].\n",
		ttl, ret, strerror(errno), sockfd);

	return ret;
}

static int set_reuseaddr(int sockfd)
{
	int ret;

	if (force_reuseaddr == 0)
		return 0;

	ret = old_setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr));
	xlog(1, "force_bind: changing reuseaddr to %u (ret=%d(%s)) [%d].\n",
		reuseaddr, ret, strerror(errno), sockfd);

	return ret;
}

static int set_nodelay(int sockfd)
{
	int ret;

	if (force_nodelay == 0)
		return 0;

	ret = old_setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay));
	xlog(1, "force_bind: changing nodelay to %u (ret=%d(%s)) [%d].\n",
		nodelay, ret, strerror(errno), sockfd);

	return ret;
}

/*
 * Set IPv6 flowinfo
 */
static void set_flowinfo(int sockfd, struct private *p)
{
	int ret;
	int yes;
	struct in6_flowlabel_req mgr;
	struct sockaddr_in6 *sa6;

	if (force_flowinfo == 0)
		return;

	if (p->domain != AF_INET6)
		return;

	if ((p->flags & FB_FLAGS_FLOWINFO_CALLED) != 0)
		return;

	/* In case of error we cannot do anything, anyway */
	p->flags |= FB_FLAGS_FLOWINFO_CALLED;

	/* Prepare flow */
	memset(&mgr, 0, sizeof(mgr));
	sa6 = (struct sockaddr_in6 *) &p->dest;
	memcpy(&mgr.flr_dst, &sa6->sin6_addr, sizeof(struct in6_addr));
	mgr.flr_label = htonl(flowinfo & IPV6_FLOWLABEL_MASK);
	mgr.flr_action = IPV6_FL_A_GET;
	mgr.flr_share = IPV6_FL_S_ANY;
	mgr.flr_flags = IPV6_FL_F_CREATE;
	ret = old_setsockopt(sockfd, SOL_IPV6, IPV6_FLOWLABEL_MGR, &mgr, sizeof(mgr));
	xlog(1, "force_bind: flow mgr (ret=%d(%s)) [%d].\n",
		ret, strerror(errno), sockfd);

	yes = 1;
	ret = old_setsockopt(sockfd, SOL_IPV6, IPV6_FLOWINFO_SEND, &yes, sizeof(yes));
	xlog(1, "force_bind: changing flowinfo to 'yes' (ret=%d(%s)) [%d].\n",
		ret, strerror(errno), sockfd);
}

static int set_fwmark(int sockfd)
{
	int ret;

	if (force_fwmark == 0)
		return 0;

	ret = old_setsockopt(sockfd, SOL_SOCKET, SO_MARK, &fwmark, sizeof(fwmark));
	xlog(1, "force_bind: changing fwmark to 0x%x (ret=%d(%s)) [%d].\n",
		fwmark, ret, strerror(errno), sockfd);

	return ret;
}

static int set_prio(int sockfd)
{
	int ret;

	if (force_prio == 0)
		return 0;

	ret = old_setsockopt(sockfd, SOL_SOCKET, SO_PRIORITY, &prio, sizeof(prio));
	xlog(1, "force_bind: changing fwmark to 0x%x (ret=%d(%s)) [%d].\n",
		prio, ret, strerror(errno), sockfd);

	return ret;
}

/*
 * Alters a struct sockaddr, based on environment variables
 * Returns 1 if the struct was altered, 0 otherwise.
 */
static int alter_sa(const int sockfd, struct sockaddr *sa)
{
	struct sockaddr_in *sa4;
	struct sockaddr_in6 *sa6;
	unsigned short *pport = NULL;
	void *p;
	char *force_address = NULL;
	int force_port;
	int err, ret = 0;

	xlog(2, "force_bind: alter_sa(sockfd=%d, ...)\n", sockfd);

	switch (sa->sa_family) {
	case AF_INET:
		sa4 = (struct sockaddr_in *) sa;
		p = &sa4->sin_addr;
		pport = &sa4->sin_port;
		force_address = force_address_v4;
		force_port = force_port_v4;
		break;

	case AF_INET6:
		sa6 = (struct sockaddr_in6 *) sa;
		p = &sa6->sin6_addr.s6_addr;
		pport = &sa6->sin6_port;
		force_address = force_address_v6;
		force_port = force_port_v6;
		break;

	default:
		xlog(1, "force_bind: unsupported family=%u [%d]!\n",
			sa->sa_family, sockfd);
		return 0;
	}

	if (force_address != NULL) {
		err = inet_pton(sa->sa_family, force_address, p);
		if (err != 1) {
			xlog(1, "force_bind: cannot convert [%s] (%d) (%s) [%d]!\n",
				force_address, err, strerror(errno), sockfd);
			return 0;
		}
		ret = 1;
	}

	if (force_port != -1) {
		*pport = htons(force_port);
		ret = 1;
	}

	return ret;
}

/*
 * Alter destination sa
 */
static void alter_dest_sa(int sockfd, struct sockaddr_storage *ss, socklen_t len)
{
	struct node *q;
	struct sockaddr_in6 *sa6;
	char addr[128];

	init();

	saddr(addr, sizeof(addr), ss);
	xlog(2, "force_bind: alter_dest_sa(sockfd=%d, addr=%s)\n",
		sockfd, addr);

	/* We do not touch non network sockets */
	q = get(sockfd);
	if ((q == NULL) || ((q->priv.flags & FB_FLAGS_NETSOCK) == 0))
		return;

	switch (ss->ss_family) {
	case AF_INET6:
		sa6 = (struct sockaddr_in6 *) ss;
		if (force_flowinfo == 1) {
			xlog(1, "force_bind: changing flowinfo from 0x%x to 0x%x [%d]!\n",
				ntohl(sa6->sin6_flowinfo), flowinfo, sockfd);
			sa6->sin6_flowinfo = htonl(flowinfo);
		}
		break;
	}

	memcpy(&q->priv.dest, ss, len);
	q->priv.dest_len = len;

	set_flowinfo(sockfd, &q->priv);
}

/*
 * Alter local binding by doing a forced 'bind' call.
 * This is called before calling connect and before using sendto/sendmsg.
 */
static void change_local_binding(int sockfd)
{
	int err;
	struct node *q;
	struct sockaddr_storage tmp;
	socklen_t tmp_len;

	init();

	xlog(2, "force_bind: change_local_binding(sockfd=%d)\n", sockfd);

	/* We do not touch non network sockets */
	q = get(sockfd);
	if ((q == NULL) || ((q->priv.flags & FB_FLAGS_NETSOCK) == 0))
		return;

	/* We do not touch already binded sockets */
	if ((q->priv.flags & FB_FLAGS_BIND_CALLED) != 0)
		return;

	tmp_len = sizeof(struct sockaddr_storage);
	err = getsockname(sockfd, (struct sockaddr *) &tmp, &tmp_len);
	if (err != 0) {
		xlog(1, "force_bind: cannot get socket name err=%d (%s) [%d]!\n",
			err, strerror(errno), sockfd);
		return;
	}

	if (alter_sa(sockfd, (struct sockaddr *) &tmp) == 0)
		return;

	err = old_bind(sockfd, (struct sockaddr *) &tmp, tmp_len);
	q->priv.flags |= FB_FLAGS_BIND_CALLED;
	if (err != 0)
		xlog(1, "force_bind: cannot bind err=%d (%s) [%d]!\n",
			err, strerror(errno), sockfd);
}

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{
	struct node *q;
	struct sockaddr_storage new;
	char *force_address = "";
	char tmp[128];

	init();

	saddr(tmp, sizeof(tmp), (struct sockaddr_storage *) addr);
	xlog(1, "force_bind: bind(sockfd=%d, %s)\n", sockfd, tmp);

	memcpy(&new, addr, addrlen);

	/* We do not touch non network sockets */
	q = get(sockfd);
	do {
		if (q == NULL)
			break;

		if ((q->priv.flags & FB_FLAGS_NETSOCK) == 0)
			break;

		switch (q->priv.domain) {
		case AF_INET: force_address = force_address_v4; break;
		case AF_INET6: force_address = force_address_v6; break;
		}

		if (force_address == NULL)
			break;

		/* Test if we should deny the bind */
		if (force_address && (strcmp(force_address, "deny") == 0)) {
			xlog(1, "force_bind: deny binding to %s\n", tmp);
			errno = EACCES;
			return -1;
		}

		if (force_address && (strcmp(force_address, "fake") == 0)) {
			xlog(1, "force_bind: fake binding to %s\n", tmp);
			return 0;
		}

		alter_sa(sockfd, (struct sockaddr *) &new);
		q->priv.flags |= FB_FLAGS_BIND_CALLED;
	} while (0);

	return old_bind(sockfd, (struct sockaddr *) &new, addrlen);
}

int setsockopt(int sockfd, int level, int optname, const void *optval,
	socklen_t optlen)
{
	init();

	if (level == SOL_SOCKET) {
		if (optname == SO_KEEPALIVE)
			return set_ka(sockfd);
		if (optname == SO_REUSEADDR)
			return set_reuseaddr(sockfd);
		if (optname == SO_MARK)
			return set_fwmark(sockfd);
		if (optname == SO_PRIORITY)
			return set_prio(sockfd);
	}

	if (level == IPPROTO_IP) {
		if (optname == IP_TOS)
			return set_tos(sockfd);
		if (optname == IP_TTL)
			return set_ttl(sockfd);
	}

	if (level == IPPROTO_TCP) {
		if (optname == TCP_KEEPIDLE)
			return set_ka_idle(sockfd);
		if (optname == TCP_MAXSEG)
			return set_mss(sockfd);
		if (optname == TCP_NODELAY)
			return set_nodelay(sockfd);
	}

	return old_setsockopt(sockfd, level, optname, optval, optlen);
}

/*
 * Helper called when a socket is created: socket, accept
 */
static void socket_create_callback(const int sockfd, int domain, int type)
{
	struct private p;

	init();

	xlog(2, "force_bind: socket_create_callback(%d, %s, %s)\n",
		sockfd, sdomain(domain), stype(type));

	set_tos(sockfd);
	set_ttl(sockfd);
	set_ka(sockfd);
	if (type == SOCK_STREAM)
		set_ka_idle(sockfd);
	set_mss(sockfd);
	set_reuseaddr(sockfd);
	set_nodelay(sockfd);
	set_fwmark(sockfd);
	set_prio(sockfd);

	p.domain = domain;
	p.type = type;
	p.flags = FB_FLAGS_NETSOCK;
	memset(&p.dest, 0, sizeof(struct sockaddr_storage));
	p.dest_len = 0;
	p.limit = 0;
	p.rest = 0;
	p.last.tv_sec = p.last.tv_usec = 0;
	add(sockfd, &p);
}

/*
 * 'socket' is hijacked to be able to call setsockopt on it.
 */
int socket(int domain, int type, int protocol)
{
	int sockfd;

	init();

	xlog(1, "force_bind: socket(domain=%s, type=%s, protocol=%s)\n",
		sdomain(domain), stype(type), sprotocol(protocol));

	sockfd = old_socket(domain, type, protocol);
	if (sockfd == -1)
		return -1;

	socket_create_callback(sockfd, domain, type);

	return sockfd;
}

/*
 * Enforce bandwidth
 */
static void bw(const int sockfd, const ssize_t bytes)
{
	struct timeval now;
	struct timespec ts, rest;
	unsigned long long allowed;
	long long diff_ms, sleep_ms;
	int err;
	struct node *q;
	struct private *p;

	xlog(2, "force_bind: bw(sockfd=%d, bytes=%zd)\n", sockfd, bytes);

	if (bytes <= 0)
		return;

	/* Is a network socket? */
	q = get(sockfd);
	if (q == NULL)
		return;

	p = &q->priv;
	if ((p->flags & FB_FLAGS_NETSOCK) == 0)
		return;

	if (p->limit == 0) {
		if (bw_global.limit == 0)
			return;
		p = &bw_global;
	}

	gettimeofday(&now, NULL);

	diff_ms = (now.tv_sec - p->last.tv_sec) * 1000
		+ (now.tv_usec - p->last.tv_usec) / 1000;
	if (diff_ms < 0)
		return;

	allowed = p->rest + p->limit * diff_ms / 1000;
	p->last = now;

	/*
	printf("diff_ms=%lld rest=%llu bytes=%u allowed=%llub\n",
		diff_ms, p->rest, bytes, allowed);
	*/

	if (bytes <= (ssize_t) allowed) {
		p->rest = allowed - bytes;
		/*printf("\tInside limit, rest=%llu.\n", p->rest);*/
		return;
	}

	p->rest = 0;
	sleep_ms = (bytes - allowed) * 1000 / p->limit;

	/* Do not count, next time, the time spent in sleep! */
	p->last.tv_sec += sleep_ms / 1000;
	p->last.tv_usec += (sleep_ms % 1000) * 1000;

	ts.tv_sec = sleep_ms / 1000;
	ts.tv_nsec = (sleep_ms % 1000) * 1000 * 1000;
	/*printf("\tWe will sleep %lus %lunsec.\n", ts.tv_sec, ts.tv_nsec);*/

	/* We try to sleep even if we are interrupted by signals */
	while (1) {
		err = nanosleep(&ts, &rest);
		if (err == -1) {
			if (errno == EINTR) {
				ts = rest;
				continue;
			}

			xlog(1, "force_bind: nanosleep returned error"
				" (%d) (%s).\n",
				err, strerror(errno));
		}

		break;
	}
}

int close(int fd)
{
	init();

	xlog(1, "force_bind: close(fd=%d)\n", fd);

	del(fd);

	return old_close(fd);
}

ssize_t write(int fd, const void *buf, size_t len)
{
	ssize_t n;

	init();

	xlog(1, "force_bind: write(fd=%d, ...)\n", fd);

	n = old_write(fd, buf, len);
	bw(fd, n);

	return n;
}

ssize_t send(int sockfd, const void *buf, size_t len, int flags)
{
	ssize_t n;

	init();

	xlog(1, "force_bind: send(sockfd=%d, buf, len=%zu, flags=0x%x)\n",
		sockfd, len, flags);

	n = old_send(sockfd, buf, len, flags);
	bw(sockfd, n);

	return n;
}

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
	const struct sockaddr *dest_addr, socklen_t addrlen)
{
	ssize_t n;
	struct sockaddr_storage new_dest;

	init();

	xlog(1, "force_bind: sendto(sockfd, %d, buf, len=%zu, flags=0x%x, ...)\n",
		sockfd, len, flags);

	change_local_binding(sockfd);

	memcpy(&new_dest, dest_addr, addrlen);
	alter_dest_sa(sockfd, &new_dest, addrlen);

	n = old_sendto(sockfd, buf, len, flags, (struct sockaddr *) &new_dest, addrlen);
	bw(sockfd, n);

	return n;
}

/*
 * TODO: Add sendmmsg
 */
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags)
{
	ssize_t n;
	/* see below
	struct sockaddr_storage new_dest;
	*/

	init();

	xlog(1, "force_bind: sendmsg(sockfd=%d, ..., flags=0x%x)\n",
		sockfd, flags);

	change_local_binding(sockfd);

	/* TODO: how do we alter flowinfo in this case?!
	memcpy(&new_dest, dest, addrlen);
	alter_dest_sa(sockfd, &new_dest, addrlen);
	*/

	n = old_sendmsg(sockfd, msg, flags);
	bw(sockfd, n);

	return n;
}

/*
 * We have to hijack accept because the program may be a daemon.
 */
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
{
	int new_sock;
	struct node *q;
	struct private *p;

	init();

	xlog(2, "force_bind: accept(sockfd=%d, ...)\n", sockfd);

	new_sock = old_accept(sockfd, addr, addrlen);
	if (new_sock == -1)
		return -1;

	/* We must find out domain and type for accepting socket */
	q = get(sockfd);
	if (q != NULL) {
		p = &q->priv;

		socket_create_callback(new_sock, p->domain, p->type);
	}

	return new_sock;
}

/*
 * We have to hijack accept4 because the program may be a daemon.
 */
int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags)
{
	int new_sock;
	struct node *q;
	struct private *p;

	init();

	xlog(2, "force_bind: accept4(sockfd=%d, ...)\n", sockfd);

	new_sock = old_accept4(sockfd, addr, addrlen, flags);
	if (new_sock == -1)
		return -1;

	/* We must find out domain and type for accepting socket */
	q = get(sockfd);
	if (q != NULL) {
		p = &q->priv;

		socket_create_callback(new_sock, p->domain, p->type);
	}

	return new_sock;
}

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{
	struct sockaddr_storage new_dest;
	struct node *q;
	char *error = NULL;
	int my_errno;

	init();

	xlog(2, "force_bind: connect(sockfd=%d, ...)\n", sockfd);

	q = get(sockfd);
	while (q != NULL) {
		switch (q->priv.domain) {
			case AF_INET:	error = force_v4_conn_error; break;
			case AF_INET6:	error = force_v6_conn_error; break;
		}
		if (!error)
			break;

		my_errno = error_to_code(error);
		if (my_errno != -1) {
			errno = my_errno;
			return -1;
		}

		xlog(0, "force_bind: [%s] error set by"
			" FORCE_NET_V*_CONN_ERROR is unknown! Ignoring it.\n");
		break;
	}

	change_local_binding(sockfd);

	memcpy(&new_dest, addr, addrlen);
	alter_dest_sa(sockfd, &new_dest, addrlen);

	return old_connect(sockfd, (struct sockaddr *) &new_dest, addrlen);
}

int poll(struct pollfd *fds, nfds_t nfds, int timeout)
{
	init();

	xlog(2, "force_bind: poll(fds, %d, %d) old_poll=%p\n", nfds, timeout, old_poll);

	if (force_poll_timeout != -1000)
		timeout = force_poll_timeout;

	return old_poll(fds, nfds, timeout);
}



Mode Type Size Ref File
100644 blob 140 35830e127b44696a7248171faab4378de2a69c7e .gitignore
100644 blob 30 d987fa5df957830331139935d517009e2911b0cf INSTALL
100644 blob 35147 94a9ed024d3859793618152ea559a168bbcbb5e2 LICENSE
100644 blob 1281 649b1ec7f7cabbd7023afd475d81d79f93889f6d Makefile.in
100644 blob 5008 1d1d3e899fc8ab15171086de21f9636b2141f8a9 README
100644 blob 1690 c1e2ed80cb60389cac0201fdbde5c42deaa028b6 TODO
100755 blob 30 92c4bc48245c00408cd7e1fd89bc1a03058f4ce4 configure
100755 blob 16967 e058c68e85b03661803926184406b16fe0b0e089 duilder
100644 blob 292 c20881a3719cdaba1a9ca8a049ee04445a81d0f6 duilder.conf
100644 blob 29878 fd1b612d553844c6aa7ac0835efe61820f40ef8c force_bind.c
100644 blob 1190 9ff243feb33534f55026c5e8ad26d57df2659059 force_bind.spec.in
100644 blob 35 6fa1dc02f112e09cbe388b61590c412dd1aae134 force_bind_config.h.in
100644 blob 1656 39482737be7bbbf3357a3750e79fec34dd323541 send_udp.c
100755 blob 198 69df55fb33fd2f66c8563d40f8e94c4a050d71f0 test1.sh
100755 blob 178 2d9a688355eeb88be7ab177ba95952a155c9e217 test2.sh
100755 blob 350 711d78469121dce161d13d338b0d281e0e646ef6 test_all.sh
100644 blob 1138 504ddf640ca53898c0d147b17cc70860c0290e61 test_bind.c
100644 blob 1382 f874e2199e08c65be67223c47373071d0e483e72 test_bind6.c
100755 blob 305 7f1903c485612a1bd82535e406afb842a9e60755 test_bind6.sh
100755 blob 193 964f25f5ab76011470436d4fb8894b975e0a1cba test_bw1.sh
100755 blob 236 484f1016e84d41ad4d393b5f6f23e5cfa9071f08 test_bw2.sh
100755 blob 286 805a280956a5a00dd52f54a8803efc6776739314 test_bw3.sh
100755 blob 448 088e71224a13f412fec15399f7bd1c0701160119 test_bw4.sh
100644 blob 1497 13d4c8bfde7655151199dc0d4ba9f5acea6512e0 test_client.c
100755 blob 179 39bb823c1a0f4c32c35141422abc61a19084f384 test_client1.sh
100755 blob 171 f988d1903cd9c89720fd6ea12b487a9e8189ef5d test_client2.sh
100755 blob 164 32459ed2d2194fa83f99ec724bf195c86dab8716 test_client3.sh
100755 blob 253 eeea81bc7eb348714945b7a5794a21f9dd813275 test_client6-1.sh
100755 blob 308 2bd1070ea27b4e6f80d6500d2d0005c2b7aaa4ec test_client6-2.sh
100644 blob 1735 7e679738e033c2425677cdfeed543cb4d32ddd11 test_client6.c
100755 blob 292 a225478d8f6b8df5cbfa115192b839bbae0276cf test_deny.sh
100755 blob 285 acf74df00776908d43b38b910140074668e9fc66 test_fake.sh
100755 blob 138 15e7a1b1bf39128dc737ba3df39498ad91e56044 test_ka1.sh
100755 blob 136 2f0bf21c57db70f4a61e1b4337f41a3c548d099b test_mss1.sh
100644 blob 488 0e31b618fc9df7f831e9396a8614a40e791d689d test_poll.c
100755 blob 292 c76f4237565a9b4012eb1129cb0ac4bc9e608829 test_poll.sh
100755 blob 144 e9620dd6d54a12e3582d38ca4fb74861fa540b88 test_tos1.sh
100755 blob 246 cdeba6d1ee85f938c06e548e3f70d07d2c6db9ff test_udp_local_bind.sh
Hints:
Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://rocketgit.com/user/catalinux/force_bind

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@ssh.rocketgit.com/user/catalinux/force_bind

Clone this repository using git:
git clone git://git.rocketgit.com/user/catalinux/force_bind

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a merge request:
... clone the repository ...
... make some changes and some commits ...
git push origin main