GrabDuck

代理服务器示例代码 - IPTV爱好者

:

在中兴机顶盒中安装代理服务器,实现对EPG服务器返回内容的修改,可增加机顶盒的灵活性。以下代码只是抛砖引玉,你可以按照自己的想法去修改。

代码只在江苏电信IPTV 2.0平台测试通过,机顶盒分别为中兴B600 V3、V4、V4A,B700V2。

#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <mntent.h>
#include <pthread.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/klog.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/vfs.h>

static char host[256];
static char proxy[256];
static char userid[64];
static char channel[64];
static char epgdomain[64];
static int  port = 80;
static int  verbose = 0;

void logging(const char* format, ...)
{
	FILE *fp;
	va_list	argList;
	time_t now = time(NULL)+8*3600;
	struct tm *local = localtime(&now);

	if ((format == NULL) || (*format == '\0'))
		return;
	va_start(argList, format);

	printf("%02d-%02d %02d:%02d:%02d ", local->tm_mon+1, local->tm_mday, local->tm_hour, local->tm_min, local->tm_sec);
	vprintf(format, argList);

	fp = fopen("/var/log/proxy.log", "a+");
	if (fp == NULL)
		fp = fopen("/var/log/proxy.log", "w");
	if (fp != NULL)
	{
		fprintf(fp, "%02d-%02d %02d:%02d:%02d ", local->tm_mon+1, local->tm_mday, local->tm_hour, local->tm_min, local->tm_sec);
		vfprintf(fp, format, argList);
		fclose(fp);
	}
	va_end(argList);
}

int main(int argc, char* argv[])
{
	int		i;
	int		flag;
	int		server;
	int		client;
	int		port = 80;
	pthread_t thread;
	socklen_t addrlen;
	struct sockaddr_in addr;
	void *ProxyThread(void *p);

	if (argc > 1)
	{
		for (i=1; i<argc; i++)
		{
			if (!strcmp(argv[i], "-p"))			// TCP端口号
			{
				if (++i >= argc)
					break;
				if (sscanf(argv[i], "%d", &port) == 0)
					port = 80;
			}
			else if (!strcmp(argv[i], "-e"))	// EPG服务器
			{
				if (++i >= argc)
					break;
				strcpy(epgdomain, argv[i]);
			}
			else if (!strcmp(argv[i], "-c"))	// 直播频道
			{
				if (++i >= argc)
					break;
				strcpy(channel, argv[i]);
			}
			else if (!strcmp(argv[i], "-v"))	// 详细日志
			{
				verbose = 1;
			}
		}
	}
	else
	{
		printf(
			"Usage: proxy -p port -e epg -c channel -v\n\n"
			"       proxy -p 80 -e 58.223.107.133 -c 899 -v\n"
			);
	}
	mkdir("/var/log/save", 0755);

	server = socket(AF_INET, SOCK_STREAM, 0);
	if (server < 0)
	{
		logging("socket() error: %s", strerror(errno));
		return 1;
	}
	flag = 1;
	if (setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)) < 0)
	{
		logging("setsockopt() error: %s", strerror(errno));
		return 2;
	}
	for (i=0; i<6; i++)
	{
		memset(&addr, 0, sizeof(addr));
		addr.sin_family      = AF_INET;
		addr.sin_port        = htons(port);
		addr.sin_addr.s_addr = INADDR_ANY;
		addrlen = sizeof(addr);
		if (bind(server, (struct sockaddr *) &addr, addrlen) < 0)
		{
			logging("bind() error: %s", strerror(errno));
			sleep(10);
			continue;
		}
		if (listen(server, 5) < 0)
		{
			logging("listen() error: %s", strerror(errno));
			sleep(10);
			continue;
		}
		break;
	}
	if (i >= 6)
		return 3;

	logging("IPTV STB Proxy started on port %d.\n", port);
	for (;;)
	{
		addrlen = sizeof(addr);
		if ((client = accept(server, (struct sockaddr *) &addr, &addrlen)) >= 0)
		{
/*
			if (fcntl(client, F_SETFL, O_NONBLOCK) < 0)
			{
				logging("fcntl() error: %s", strerror(errno));
				close(client);
				continue;
			}
*/
			if (verbose)
				logging("%s:%d connected.\n", inet_ntoa(addr.sin_addr), addr.sin_port);
			if (pthread_create(&thread, NULL, ProxyThread, (void *)client))
			{
				logging("pthread_create error: %s", strerror(errno));
			}
		}
		usleep(10);
	}

	return 0;
}

// 网络输出
int netprintf(int s, const char *szFormat, ...)
{
	int n;
	int nLen;
	int nSent;
	va_list vaList;
	char buffer[1024];

	va_start(vaList, szFormat);
	if (szFormat)
	{
		vsprintf(buffer, szFormat, vaList);
		nLen = strlen(buffer);
		nSent = 0;
		do
		{
			n = send(s, &buffer[nSent], nLen-nSent, 0);
			if (n == 0)
				break;
			if (n < 0)
			{
				if (errno != EAGAIN)
					break;
				usleep(1);
			}
			nSent += n;
		} while (nSent < nLen);
		va_end(vaList);
		return nLen;
	}
	else
	{
		va_end(vaList);
		return 0;
	}

	return 0;
}

// 从网络读取一行数据
int netreadln(int s, char buffer[], int nMax, int nTimeOut)
{
	int  i;
	int  n;
	char c;
	time_t begin;

	i = 0;
	time(&begin);
	while (!nTimeOut || (time(NULL)-begin) < nTimeOut)
	{
		if ((n = recv(s, &c, 1, 0)) > 0)
		{
			if (c == '\n')
			{
				buffer[i] = '\0';
				return i;
			}
			else if (c == '\r')
				continue;
			buffer[i++] = c;
			if (i >= nMax-1)
			{
				buffer[i] = '\0';
				return i;
			}
		}
		else if (n == 0)
		{
			return -1;
		}
		else
		{
			if (errno != EAGAIN)
				return -1;
			usleep(1);
		}
	}

	return -1;
}

int netread(int s, char buffer[], int nMax, int nTimeOut)
{
	int  i;
	int  n;
	char c;
	time_t begin;

	i = 0;
	time(&begin);
	while (!nTimeOut || (time(NULL)-begin) < nTimeOut)
	{
		if ((n = recv(s, &c, 1, 0)) > 0)
		{
			buffer[i++] = c;
			if (i >= nMax)
			{
				return i;
			}
		}
		else if (n == 0)
		{
			return -1;
		}
		else
		{
			if (errno != EAGAIN)
				return -1;
			usleep(1);
		}
	}

	return -1;
}

int netopen(char *hostname, int port)
{
	int i;
	int sock;
	int length;
	u_long inetaddr;
	struct hostent *host;
	struct sockaddr_in sin;

	sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock < 0)
	{
		logging("socket() error: %s", strerror(errno));
		return -1;
	}

	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_port   = htons(port);

	inetaddr = inet_addr(hostname);
	if (inetaddr != INADDR_NONE)
	{
		memcpy(&(sin.sin_addr), (const void *)&inetaddr, sizeof(sin.sin_addr));
	}
	else
	{
		logging("inet_addr('%s') error: %s\n", hostname, strerror(errno));
	}
	if (connect(sock, (struct sockaddr*)&sin, sizeof(sin)) < 0) {
		logging("connect() error: %s\n", strerror(errno));
		close(sock);
		return -1;
	}

	return sock;
}

char* str_replace(char *old, char *rep, char *str)
{
	int len, size;
	char *p, *q, *tmp;

	p = str;
	len = 0;
	tmp = (char *)malloc(strlen(str)*2);
	if (tmp == NULL)
		return tmp;
	tmp[len] = '\0';
	while ((q = strstr(p, old)) != NULL)
	{
		size = (int)(q-p);
		memcpy(&tmp[len], p, size);
		len += size;
		size = strlen(rep);
		memcpy(&tmp[len], rep, size);
		len += size;
		p = q+strlen(old);
		tmp[len] = '\0';
	}
	strcat(&tmp[len], p);

	return tmp;
}

void *ProxyThread(void *param)
{
	int  i, n;
	int  len;
	int  post;
	int  server;
	int  length;
	char c, *p, *q;
	char url[2560];
	char urlname[256];
	char filename[256];
	char content[256];
	char replace[256];
	char request[10240];
	char buffer[512000];
	char *response = NULL;
	int  sock = (int)param;
	FILE *fp;

	post = -1;
	strcpy(url, "");
	if (verbose)
		logging("Enter ProxyThread\n");
	while (netreadln(sock, buffer, sizeof(buffer), 0) >= 0)
	{
		if ((strncasecmp(buffer, "GET ", 4) == 0)
		||  (strncasecmp(buffer, "POST ", 5) == 0)
		   )
		{
			if (strncasecmp(buffer, "GET ", 4) == 0)
			{
				strcpy(request, "GET ");
				p = buffer+4;
			}
			else if (strncasecmp(buffer, "POST ", 5) == 0)
			{
				post = 1;
				strcpy(request, "POST ");
				p = buffer+5;
			}
			if (strncasecmp(p, "/_/", 3) == 0)			// 获取真实服务器的IP和端口
			{
				p += 3;
				if ((q = strstr(p, ":")) != NULL)		// 带端口号
				{
					*q = '\0';
					strcpy(host, p);
					p = q+1;
					if (sscanf(p, "%d", &port) == 0)
						port = 80;
					if ((p = strstr(p, "/")) != NULL)	// 取URL中的路径部分
						strcpy(url, p);
					else
						strcpy(url, "/ HTTP/1.1");
				}
				else
				{
					port = 80;
					if ((q = strstr(p, "/")) != NULL)	// 取URL中的路径部分
					{
						n = (int)(q-p);
						strncpy(host, p, n);
						host[n] = '\0';
						strcpy(url, q);
					}
					else
					{
						strcpy(host, p);
						strcpy(url, "/ HTTP/1.1");
					}
				}
				strcat(request, url);
			}
			else
			{
				strcpy(url, p);
				strcat(request, p);
			}
			if (strlen(userid) == 0)
			{
				if (p = strstr(url, "UserID="))
				{
					p += 7;
					if ((q = strstr(p, "&")) != NULL)
					{
						strncpy(userid, p, (int)(q-p));
						logging("UserID: [%s]\n",userid);
					}
				}
			}
			strcat(request, "\r\n");
		}
		else
		{
			if (strncasecmp(buffer, "Host: ", 6) == 0)
			{
				strcpy(proxy, buffer+6);
				sprintf(buffer, "Host: %s:%d\r\n", host, port);
				strcat(request, buffer);
				strcat(request, "Connection: close\r\n");
			}
			else if (strncasecmp(buffer, "Referer: ", 9) == 0)
			{
				if ((p = strstr(buffer, "http://")) && (q = strstr(buffer, "/_/")))
				{
					strcat(request, "Referer: http://");
					strcat(request, q+3);
					strcat(request, "\r\n");
				}
				else
				{
					strcat(request, buffer);
					strcat(request, "\r\n");
				}
			}
			else if (strncasecmp(buffer, "Connection: ", 12) == 0)
			{
			}
			else
			{
				strcat(request, buffer);
				strcat(request, "\r\n");
				if (post != 0 && strncasecmp(buffer, "Content-Length: ", 16) == 0)
				{
					sscanf(&buffer[16], "%d", &post);
				}
			}
		}
		if (strlen(buffer) == 0)
			break;
	}

	if (post > 0)
	{
		netread(sock, buffer, post, 0);
		buffer[post] = 0;

		if (p = strstr(buffer, "AccessUserName="))
		{
			if (strstr(p, userid) == NULL)
			{
				strcat(buffer, userid);
				strcat(buffer, "@vod");
				post += strlen(userid)+4;

				if ((p = strstr(request, "Content-Length:")) != NULL
				&&  (q = strstr(p, "\r")) != NULL
				   )
				{
					memset(replace, 0, sizeof(replace));
					strncpy(replace, p, (int)(q-p));
					sprintf(urlname, "Content-Length: %d", post);
					p = str_replace(replace, urlname, request);
					if (p != NULL)
					{
						strcpy(request, p);
						free(p);
					}
				}
			}
		}
	}
	if ((p = strstr(url, " HTTP/1.")) != NULL)
		*p = '\0';
	if (post > 0)
		logging("POST http://%s:%d%s\n", host, port, url);
	else
		logging("GET http://%s:%d%s\n", host, port, url);

	server = netopen(host, port);
	if (server > 0)
	{
		if (verbose)
			logging("Send request to server\n%s", request);
		send(server, request, strlen(request), 0);
		if (post > 0)
		{
			buffer[post] = 0;
			if (verbose)
				logging("Send post to server\n%s\n", buffer);
			send(server, buffer, post, 0);
		}
		length = -1;
		strcpy(request, "");
		while (netreadln(server, buffer, sizeof(buffer), 0) >= 0)
		{
			if (strncasecmp(buffer, "Content-Length: ", 16) == 0)
			{
				strcat(request, buffer);
				sscanf(&buffer[16], "%d", &length);
			}
			else if (strncasecmp(buffer, "Content-Type: ", 14) == 0)
			{
				strcat(request, buffer);
				strcpy(content, buffer+14);
			}
			else if (strncasecmp(buffer, "Set-Cookie: ", 12) == 0)
			{
				if ((p = strstr(buffer, "Path=/")) != NULL)
					*(p+6) = '\0';
				strcat(request, buffer);
			}
			else
			{
				strcat(request, buffer);
			}
			strcat(request, "\r\n");
			if (strlen(buffer) == 0)
				break;
		}
		sprintf(replace, "http://%s/_/", proxy);
		p = str_replace("http://", replace, request);
		if (p != NULL)
		{
			strcpy(request, p);
			free(p);
		}

		len = 0;
		buffer[len] = 0;
		if (length != 0)
		{
			if (length > 0)
			{
				if (length > sizeof(buffer)-1)
					length = sizeof(buffer)-1;
			}
			else
			{
				length = sizeof(buffer)-1;
			}
			while ((n = recv(server, &buffer[len], length-len, 0)) != 0)
			{
				if (n < 0)
				{
					if (errno != EAGAIN)
						break;
					usleep(1);
					continue;
				}
				len += n;
				buffer[len] = 0;
				if (len >= length)
					break;
			}
			if (verbose)
				logging("Read %d bytes\n", len);
			if ((p = strstr(url, "/index.jsp")) != NULL
			||  (p = strstr(url, "/funcportalauth.jsp")) != NULL
			||  (p = strstr(url, "/frame.jsp")) != NULL
			||  (p = strstr(url, "/frameset_judger.jsp")) != NULL
			||  (p = strstr(url, "/frameset_builder.jsp")) != NULL
			||  (p = strstr(url, "/channel_start.jsp")) != NULL
			||  (p = strstr(url, "/control_transit_play.jsp")) != NULL
			||  (p = strstr(url, "/portal.jsp")) != NULL
			||  (p = strstr(url, "/portal_trailer.jsp")) != NULL
			||  (p = strstr(url, "/heartbeat.jsp")) != NULL
			||  (p = strstr(url, "/auth?")) != NULL
			||  (p = strstr(url, "/uploadAuthInfo")) != NULL
			||  (p = strstr(url, "/getChannelList")) != NULL
			||  (p = strstr(url, "/getServiceList")) != NULL
			   )
			{
				p++;
				if ((q = strstr(p, ".jsp")) != NULL)
				{
					strncpy(urlname, p, (int)(q+4-p));
					urlname[(int)(q+4-p)] = '\0';
				}
				else if ((q = strstr(p, "?")) != NULL)
				{
					strncpy(urlname, p, (int)(q-p));
					urlname[(int)(q-p)] = '\0';
				}
				else
				{
					strcpy(urlname, url);
				}
				if (strchr(urlname, '.') == NULL)
					strcat(urlname, ".html");
				sprintf(filename, "/var/log/save/%s", urlname);
				fp = fopen(filename, "wb");
				if (fp == NULL)
				{
					logging("File %s open error.\n", filename);
					mkdir("/var/log/save", 0755);
					fp = fopen(filename, "wb");
				}
				if (fp != NULL)
				{
					length = len;
					for (i=0; i<len; i+=32768)
					{
						n = fwrite(&buffer[i], 1, (length>32768)?32768:length, fp);
						length -= n;
					}
					fclose(fp);
				}
				if (strcasecmp(p, "portal_trailer.jsp") == 0)	// 去掉首页的节目介绍小窗口
				{
					strcpy(buffer, 
						"<html><head></head><body bgcolor=\"#FFFFFF\">"
						"<br><br><p align=\"center\"><font size=\"+2\">IPTV爱好者</font></p>"
						"</body></html>");
					len = strlen(buffer);
				}

				if ((strlen(epgdomain) > 0)		// 使用单播EPG服务器
				&&  (strstr(buffer, "BalancedEPG") != NULL)
				   )
				{
					if ((p = strstr(buffer, "http://")) != NULL
					&&  ((q = strstr(p+7, ":")) != NULL || (q = strstr(p+7, "/")) != NULL)
					   )
					{
						p += 7;
						memset(replace, 0, sizeof(replace));
						strncpy(replace, p, (int)(q-p));
						p = str_replace(replace, epgdomain, buffer);
						if (p != NULL)
						{
							strcpy(buffer, p);
							len = strlen(buffer);
							free(p);
							logging("EPG: %s => %s\n", replace, epgdomain);
						}
					}
				}

				if ((p = strstr(url, "portal.jsp")) != NULL
				&&  (p = strstr(buffer, "top.jsDoStopVideo();")) != NULL
				&&  strlen(channel) > 0)		// 直播频道
				{
					sprintf(buffer, 
						"<html>\r\n"
						"<head>\r\n"
						"<title>frame portal page</title>\r\n"
						"<script language=\"javascript\">\r\n"
						"  document.onkeypress = top.doKeyPress;\r\n"
						"  top.jsClearKeyFunction();\r\n"
						"  var url = location.href;\r\n"
						"  top.jsSetControl(\"lastUrl\",url);\r\n"
						"  top.jsSetControl(\"stopUrl\",url);\r\n"
						"  top.jsDoStopVideo();\r\n"
						"  top.jsRedirectChannel(\"%s\");\r\n"
						"</script>\r\n"
						"</head>\r\n"
						"<body bgcolor=\"transparent\" style=\"background-Repeat:no-repeat\" onUnload=\"top.doStop();\">\r\n"
						"</body>\r\n"
						"</html>\r\n",
						channel);
					logging("Channel: %s\n", channel);
				}
			}
			if ((strncasecmp(content, "image", 5) != 0)
			&&  (strstr(buffer, "mainWinSrcForm") == NULL)
			   )
			{
				sprintf(replace, "http://%s/_/", proxy);
				p = str_replace("http://", replace, buffer);
				if (p != NULL)
				{
					strcpy(buffer, p);
					len = strlen(buffer);
					free(p);
				}
/*
				if ((p = strstr(buffer, "rtsp://")) != NULL
				&&  ((q = strstr(p, "'")) || (q = strstr(p, "\"")))
				   )
				{
					strncpy(url, p, (int)(q-p));
					url[(int)(q-p)] = '\0';
					logging("%s\n", url);
				}
*/
			}
		}
		close(server);

		if ((p = strstr(request, "Content-Length:")) != NULL
		&&  (q = strstr(p, "\r")) != NULL
		   )
		{
			memset(replace, 0, sizeof(replace));
			strncpy(replace, p, (int)(q-p));
			sprintf(urlname, "Content-Length: %d", len);
			p = str_replace(replace, urlname, request);
			if (p != NULL)
			{
				strcpy(request, p);
				free(p);
			}
		}
		else
		{
			sprintf(urlname, "\r\nContent-Length: %d\r\nDate:", len);
			p = str_replace("\r\nDate:", urlname, request);
			if (p != NULL)
			{
				strcpy(request, p);
				free(p);
			}
		}
		if (verbose)
			logging("Send reply to STB\n%s\n", request);
		send(sock, request, strlen(request), 0);

		int sent = 0;
		while (sent < len)
		{
			n = send(sock, &buffer[sent], len-sent, 0);
			if (n <= 0)
				break;
			sent += n;
			if (verbose)
				logging("Send %d Total: %d\n", sent, len);
		}
	}
	else
	{
		logging("Connect %s:%d error.\n", host, port);
	}
	if (verbose)
		logging("Exit ProxyThread\n");
	close(sock);
	pthread_exit(0);

	return NULL;
}


以上代码可在这里下载saveproxy.c,在安装stlinux的Linux操作系统中用下面的命令编译、连接,已编译好的程序在这里下载saveproxy

sh4-linux-uclibc-gcc -o saveproxy -lpthread saveproxy.c

将生成的saveproxy文件复制到机顶盒的/usr/bin目录中,并设置为自动启动。saveproxy的命令行参数说明:

saveproxy -p port -e epg -c channel -v

-p port    设置监听端口,缺省为:80;
-e epg     设置替代EPG服务器IP地址,用于把单播方式的EPG服务器替代组播方式的EPG服务器;
-c channel 设置开机自动播放的直播频道号,这种方式将无法显示EPG首页,用于某些特殊场合;
-v         详细日志模式

saveproxy -p 80 -e 58.223.107.133 -c 899 -v

自动启动的方法有两种:

1、在/etc/rc.d/rcS文件中增加一行:

/usr/bin/saveproxy -p 80 &

2、在/etc/inittab文件中增加一行:

ttyAS1::respawn:/usr/bin/saveproxy -p 80 >/dev/null


把机顶盒中的主用认证服务器地址前加上127.0.0.1/_/,例如:原来的服务器地址为:

http://58.223.251.139:8298/auth

则新的服务器地址为:

http://127.0.0.1/_/58.223.251.139:8298/auth

该代理服务器把机顶盒访问的URL记录在/var/log/proxy.log文件中,同时把几个主要的EPG页面内容保存到/var/log/save目录中,供研究EPG用。