Improment to Carl Harris’ proxy

想要提高一个代理的性能,google了linux proxy的实现,发现 Carl Harris写的proxy据说很经典。看了一下确实很经典,基本涉及了linux网络编程的方方面面,虽然代理那块仅仅是最基本的功能。

这个程序的具体解释在这里有:http://fanqiang.chinaunix.net/a4/b7/20010810/1200001101_b.html

不过里面用的很多接口都比较老了,没法处理IPv6……虽然IPv6离我们还比较远,未雨绸缪嘛……于是我对其中的一些接口进行了更新,使用较新的接口以及一些C99的语法,同时也作为以后网络编程的一个example吧

#if 0
${CC:=clang} $0 -o `basename $0 .c` -g -Wall
exit $?
#endif

/****************************************************************************
program: proxyd
module: proxyd.c
summary: provides proxy tcp service for a host on an isolated network.

programmer: Carl Harris (ceharris@vt.edu)
date: 22 Feb 94

description:
This code implements a daemon process which listens for tcp connec-
tions on a specified port number. When a connection is established,
a child is forked to handle the new client. The child then estab-
lishes a tcp connection to a port on the isolated host. The child
then falls into a loop in which it writes data to the isolated host
for the client and vice-versa. Once a child has been forked, the
parent resumes listening for additional connections.

The name of the isolated host and the port to serve as proxy for,
as well as the port number the server listen on are specified as
command line arguments.
****************************************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <signal.h>
#include <assert.h>

#define TCP_PROTO "tcp"
#define N_CLIENTS 5

int proxy_port; /* port to listen for proxy connections on */
struct sockaddr_in hostaddr; /* host addr assembled from gethostbyname */

extern int errno;
/* extern char *sys_errlist[]; */

void parse_args (int argc, char **argv);
void daemonize (int servfd);
void do_proxy(int usersockfd);
void reap_status (int signo);
void errorout (char *msg);

int main (int argc, char *argv[])
{
	socklen_t clilen;
	int childpid;
	int sockfd, newsockfd;
	struct sockaddr_in servaddr, cliaddr;

	parse_args (argc, argv);

	/* prepare an address struct to listen for connections */
	memset (&servaddr, 0, sizeof (servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl (INADDR_ANY);
	servaddr.sin_port = proxy_port;

	/* get a socket... */
	if ((sockfd = socket (AF_INET, SOCK_STREAM, 0)) < 0)
	{
		fputs ("failed to create server socket", stderr);
		exit (1);
	}

	/* ...and bind our address and port to it */
	if (bind (sockfd, (struct sockaddr *)(&servaddr), sizeof (servaddr)) < 0)
	{
		printf ("failed to bind server socket to specified port: %d, %s\n", htons(proxy_port), strerror (errno));
		exit (1);
	}

	/* get ready to accpet with N_CLIENTS waitting to connect */
	listen (sockfd, N_CLIENTS);

	/* turn ourselves into a daemon */
#ifdef NDEBUG
	daemonize (sockfd);
#endif /* NDEBUG */

	/* fall into a loop to accpet new connections and spawn children */
	while (1)
	{
		/* accpet the next connection */
		clilen = sizeof (struct sockaddr_in);
		newsockfd= accept (sockfd, (struct sockaddr *) (&cliaddr), &clilen);

		if ((newsockfd < 0) && (errno == EINTR))
		{
			continue;
		}
		else if (newsockfd < 0)
		{
			/* something quite amiss -- kill the server */
			errorout ("failed to accept connection");
		}

		/* fork a chlid to handle this connection */
		if ((childpid = fork ()) == 0)
		{
			close (sockfd);
			do_proxy (newsockfd);
			exit (0);
		}

		/* if fork() failed, this connection is silently dropped -- oope */
		close (newsockfd);
	}
}

void parse_args (int argc, char *argv[])
{
	int i;
	struct addrinfo *res;
	struct addrinfo hints =
	{
		.ai_flags = AI_CANONNAME,
		.ai_family = AF_INET,
		.ai_socktype = SOCK_STREAM,
	};

	struct {
		char proxy_port[16];
		char isolated_host[65];
		char service_name[32];
	}pargs;

	if (argc < 4)
	{
		printf ("usage: %s <listen port> <isolated host> <service>\n", argv[0]);
		exit (1);
	}

	strncpy (pargs.proxy_port, argv[1], sizeof (pargs.proxy_port));
	strncpy (pargs.isolated_host, argv[2], sizeof (pargs.isolated_host));
	strncpy (pargs.service_name, argv[3], sizeof (pargs.service_name));

	for (i = 0; i < strlen (pargs.proxy_port); i++)
	{
		if (!isdigit (pargs.proxy_port[i]))
		{
			break;
		}
	}

	if (i == strlen (pargs.proxy_port))
	{
		proxy_port = htons (atoi (pargs.proxy_port));
	}
	else
	{
		printf ("%s: invalid proxy port", pargs.proxy_port);
		exit (0);
	}

	if (getaddrinfo (pargs.isolated_host, pargs.service_name, &hints, &res) != 0)
	{
		printf ("Unable to getaddrinfo for host: %s service: %s\n",pargs.isolated_host, pargs.service_name);
		exit (0);
	}

	memcpy (&hostaddr, res->ai_addr, res->ai_addrlen);

#if 1
	char address[INET_ADDRSTRLEN];
	assert (inet_ntop (res->ai_family, (void *)&(hostaddr.sin_addr), address, sizeof (address)) != NULL);
	printf ("address: %s port: %d\n", address, htons (hostaddr.sin_port));
#endif

	freeaddrinfo (res);

#if 0
	int ret;
	struct hostent *hostp;
	struct servent *servp;
	memset (&hostaddr, 0, sizeof (hostaddr));
	hostaddr.sin_family = AF_INET;

	if ((ret = inet_pton (AF_INET, pargs.isolated_host, &(hostaddr.sin_addr))) != 1)
	{
		if ((hostp = gethostbyname (pargs.isolated_host)) != NULL) /* FIXME: use getaddrinfo instead */
		{
			memcpy (&hostaddr.sin_addr, hostp->h_addr, hostp->h_length);
		}
		else
		{
			printf ("%s: unknown host", pargs.isolated_host);
			exit (1);
		}
	}

	if ((servp = getservbyname (pargs.service_name, TCP_PROTO)) != NULL)
	{
		hostaddr.sin_port = servp->s_port;
	}
	else if (atoi(pargs.service_name) > 0)
	{
		hostaddr.sin_port = htons (atoi (pargs.service_name));
	}
	else
	{
		printf ("%s: invalid/known service name or port number", pargs.service_name);
		exit (1);
	}
#endif

}

void daemonize (int servfd)
{
	int childpid, fd, fdtablesize;

	signal (SIGTTOU, SIG_IGN);
	signal (SIGTTIN, SIG_IGN);
	signal (SIGTSTP, SIG_IGN);

	if ((childpid = fork ()) < 0)
	{
		exit (1);
	}
	else if (childpid > 0)
	{
		exit (0); /* terminate parent, continue in child */
	}

	/* dissociate from process group */
#if _SVID_SOURCE || _XOPEN_SOURCE >= 500 || _XOPEN_SOURCE && _XOPEN_SOURCE_EXTENDED
	setpgrp();
#else
	if (setpgrp(0,getpid()) < 0)
	}
		fputs("failed to become process group leader",stderr);
		exit(1);
	}
#endif

	/* lose controlling terminal */
	if ((fd = open ("/dev/tty", O_RDWR)) >= 0)
	{
		ioctl (fd, TIOCNOTTY, NULL);
		close (fd);
	}

	for (fd = 0, fdtablesize = getdtablesize(); fd < fdtablesize; fd++)
	{
		if (fd != servfd)
		{
			close (fd);
		}
	}

	chdir ("/");

	umask (0);

	signal (SIGCHLD, reap_status);
}

void errorout (char *msg)
{
	FILE *console;

	console = fopen ("/dev/console", "a");
	fprintf (console, "proxyd: %s", msg);
	fclose (console);
	exit (1);
}

void reap_status (int signo)
{
	int pid;
	union wait status;

	while ((pid = wait3 (&status, WNOHANG, NULL)) > 0);
}

void do_proxy (int usersockfd)
{
	int isosockfd;
	fd_set rdfdset;
	int connstat;
	int iolen;
	char buf[2048];

	/* open a socket to connect to the isolated host */
	if ((isosockfd = socket (AF_INET, SOCK_STREAM, 0)) < 0)
	{
		errorout ("failed to create socket to host");
	}

	connstat = connect (isosockfd, (struct sockaddr *)&hostaddr, sizeof (hostaddr));

	switch (connstat)
	{
		case 0:
			break;
		case ETIMEDOUT:
		case ECONNREFUSED:
		case ENETUNREACH:
			strcpy (buf, strerror (errno));
			strcat (buf, "/r/n");
			write (usersockfd, buf, strlen (buf));
			close (usersockfd);
			exit (1);
			break;
		default:
			errorout ("failed to connect to host");
	}

	while (1)
	{
		/* select for readbility on either of our two sockets */
		FD_ZERO (&rdfdset);
		FD_SET (usersockfd, &rdfdset);
		FD_SET (isosockfd, &rdfdset);

		if (select (FD_SETSIZE, &rdfdset, NULL, NULL, NULL) < 0)
		{
			errorout ("Select failed");
		}

		/* is the client sending data? */
		if (FD_ISSET(usersockfd, &rdfdset))
		{
			/* (zero length means the client disconnected */
			if ((iolen = read (usersockfd, buf, sizeof (buf))) <= 0)
			{
				break;
			}
			write (isosockfd, buf, iolen);
		}

		/* is the host sending data? */
		if (FD_ISSET (isosockfd, &rdfdset))
		{
			if ((iolen = read (isosockfd, buf, sizeof (buf))) <= 0)
			{
				break;
			}
			write (usersockfd, buf, iolen);
		}
	}
	close (isosockfd);
	close (usersockfd);

}

主要的修改包括:

1.在parse_args中使用getaddrinfo() 替代原来的inet_addr(), gethostbyname(),以及getservbyname三个接口。

在原来的逻辑中 (#if 0 注释掉的一段),首先通过if ((inaddr = inet_addr(pargs.isolated_host)) != INADDR_NONE)判断传入的主机名是否为IPv4的点分十进制表示,是的话则转换为网络字节序表示;否则认为这是一个域名,尝试通过gethostbyname()获取IPv4地址对应的网络字节序表示。端口的处理也采用同样的方式,首先假设service是个标准的TCP服务,通过    if ((servp = getservbyname(pargs.service_name,TCP_PROTO)) != NULL)尝试获得对应的端口;如果失败则认为这是一个数字端口号。但是这些接口仅能处理IPv4.

而getaddrinfo()不仅能够同时处理IPv4和IPv6,而且还能同时完成IP地址和端口的转换,并且能正确的处理IP地址的处理点分十进制/网络字节序表示,以及service名称和数字端口,如下:

struct addrinfo *res;
	struct addrinfo hints =
	{
		.ai_flags = AI_CANONNAME,
		.ai_family = AF_INET,
		.ai_socktype = SOCK_STREAM,
	};

	if (getaddrinfo (pargs.isolated_host, pargs.service_name, &hints, &res) != 0)
	{
		printf ("Unable to getaddrinfo for host: %s service: %s\n",pargs.isolated_host, pargs.service_name);
		exit (0);
	}

	memcpy (&hostaddr, res->ai_addr, res->ai_addrlen);
  1. daemonize 中的 setpgrp()函数在linux和bsd中的接口是不同的,linux中没有参数;bsd中包含两个参数,第一个表示进程ID(0表示本身),第二个参数表示进程组。 修改如下:
#if _SVID_SOURCE || _XOPEN_SOURCE >= 500 || _XOPEN_SOURCE && _XOPEN_SOURCE_EXTENDED
	setpgrp();
#else
	if (setpgrp(0,getpid()) < 0)
	{
		fputs("failed to become process group leader",stderr);
		exit(1);
	}
#endif

程序的用法

usage: ./proxy <listen port> <isolated host> <service>

其中isolated可以是目标机器的FQDN或者IP地址,例如poetpalace.org或者122.248.235.180;service可是某个服务的服务名称或者端口,例如ssh或者22,服务名称和端口的对应关系请查看/etc/services。举例来说:

./proxy 2345 poetpalace.org 22

这样任何程序连接机器上的2345端口,就相当于连接了poetpalace.org 22 端口

理论上来说,只要在do_proxy的read/write中做个加密,client和server上各布置一个proxy并连起来,server上的proxy指向squid的代理端口,就能作为一个翻墙的代理……原先我一直想这么干,可是后来我有了goagent,ssh -D, autorproxy……

updatedupdated2022-02-222022-02-22