Linux透明代理 —— 使用iptables实现TCP透明代理(nat方式,一个客户端对应一个服务器)

目录:

1、什么是透明代理?

2、透明代理的作用?

2.1 TCP代理服务器可以隐藏背后真正TCP服务器

2.2 保护TCP服务器免受应用层以下级别的协议栈攻击

2.3 TCP转址机

3、socket透明代理的实现原理?

4、在实现TCP代理服务器时,遵循以下几点原则

5、应用背景

6、关键技术?

7、如何建立透明代理?

7.1 Tcp透明代理实现的中心思想

7.2 搭建环境

7.3 nat方式(对需要代理的数据包设置iptables规则)

7.4 本地socket捕获数据包,为socket设置IP_TRANSPARENT选项:

客户端代码socket_client_tcp.c:

代理服务器端代码socket_proxy_server_tcp.c:

服务器端代码socket_server_tcp.c:

8、 结果显示:

客户端(请求两次):

代理服务器端(接受两次):

服务器端(接受两次):

9、调试代码时遇到的问题:

10、参考连接:

1、什么是透明代理?

客户端向真实服务器发起连接,代理机冒充服务器与客户端建立连接,并以客户端ip与真实服务器建立连接进行代理转发。因此对于客户端与服务器来说,代理机都是透明的。

2、透明代理的作用?

2.1 TCP代理服务器可以隐藏背后真正TCP服务器

如此便可以起到保护真正TCP服务器的作用。由于TCP代理服务器工作于应用层,所以,黑客对应用层以下级别的协议栈的攻击(比如TCP半连接攻击)就无法穿过TCP代理服务器,这样,即使TCP代理服务器挂了,我们真正的TCP服务器仍然可以正常运行。当然,如果黑客是针对应用层进行攻击的,这时,代理服务器就不起作用的。

2.2 保护TCP服务器免受应用层以下级别的协议栈攻击

当黑客攻击应用层以下级别的协议栈,我们可以在真正的TCP服务器前面部署N个代理服务器,并将它们分布于不同的地方,这样,当其中一个代理服务器因为遭遇攻击而挂掉时,受影响的只是连接到这台代理服务器的用户,而其它的代理服务器上的用户仍然是正常被服务的,就像下面这样:

Linux透明代理 —— 使用iptables实现TCP透明代理(nat方式,一个客户端对应一个服务器)_第1张图片

 

2.3 TCP转址机

TCP代理服务器还有另外一个重要的作用,就是转址机。比如,我们的机房位于广州,而当北京的用户来访问服务时,网络延迟很大。我们可以在北京的机房部署一台代理服务器,并将该代理服务器与广州服务器之间的网络路由调节到最优。如此,北京的用户就可以通过这台代理服务器来快速地访问我们提供的服务了。对于转址机这项功能而言,有很多现有的软件可以做到,它们通常工作于网络协议层次中的IP层(即网络层),所以它们除了可以做TCP转址外,还可以做UDP的转址。

3、socket透明代理的实现原理?

  • [需要代理方]向服务器发出请求信息;
  • [代理方]应答;
  • [需要代理方]接到应答后发送向[代理方]发送目的ip和端口;
  • [代理方]与目的连接;
  • [代理方]将[需要代理方]发出的信息传到目的方,将目的方发出的信息传到[需要代理方];
  • 代理完成。

Linux透明代理 —— 使用iptables实现TCP透明代理(nat方式,一个客户端对应一个服务器)_第2张图片

4、在实现TCP代理服务器时,遵循以下几点原则

  • 当客户端与代理服务器建立TCP连接成功时,代理服务器立即与TCP服务器建立连接,并将它们作为一个连接对管理起来。
  • 当连接对中的任何一个连接断开时,代理服务器都关闭另外一个连接,并释放该连接对所持有的任何资源。
  • 当接收到来自客户端的任何数据时,都原封不动地转发给TCP服务器;当接收到来自TCP服务器的任何数据时,都原封不动地转发给对应的客户端。
  • 通过客户端的端点地址IPEndPoint来识别不同的连接对。

5、应用背景

linux下tcp层透明代理简单实现, 在此基础上可以实现http层的透明代理以及,一切以tcp承载的协议的透明代理

6、关键技术?

c/c++技术,使用IP_TRANSPARENT的使用, 如何用非本地的ip地址与服务器建立连接, 如何收取非本服务器的数据

7、如何建立透明代理?

7.1 Tcp透明代理实现的中心思想

对于网络数据的转发可以使用Linux的iptables命令进行设置;

在路由器上使用iptables设置过转发规则后,路由器可以将收到的client程序的tcp数据转发给特定端口号,代理程序可在该端口号上使用tcp socket进行监听,获取连接后即可像普通socket程序一样进行与client端进行通讯;

代理程序与server端连接时,需要获取到server端的ip地址与端口号,该信息可以通过getsockopt的方式获取

getsockopt (clifd, SOL_IP, SO_ORIGINAL_DST, &orig_addr, &sin_size);

SO_ORIGINAL_DST是一个socket参数(SOL_IP层的),使用时需要包含

#include头文件。

如果成功orig_addr将是客户真正需要去的方向

之后代理程序可以将client的tcp转发server端,然后再将server端的数据转发给client端,即可实现透明代理

 

换种方式说明:

 Linux下面使用netfilter(iptables)可以很容易的实现一个TCP透明代理,

 使用iptables的REDIRECT选项无需编写内核模块就可以把连接重定向到本地代理进程。

 然后代理进程accept到连接时通过SO_ORIGINAL_DST这个socket选项就可以得到原始目的地址,

 非常方便。

 不过这只是对TCP而言的,对应UDP而言,还是需要编写一个内核模块,自己做一些处理。

 对于UDP,可以用nf_register_hook注册一个Hook,然后保存socket目的地址。

 另外注册一个字符设备,在字符设备的IOCTL处理函数中把socket的原始目的地址发送给代理进程。

7.2 搭建环境

 

ip地址

网关

监听的端口

备注

客户端

192.168.2.230

192.168.2.56

23

客户端与服务器端不是一个网段

代理服务器

eth0:192.168.1.56

192.168.1.255

2223

eth1 192.168.2.56

192.168.2.255

服务器

192.1681.230

192.168.1.56

23

7.3 nat方式(对需要代理的数据包设置iptables规则)

1043 iptables -t nat -nvL #查看nat链

1044 iptables -t nat -N MY_TCP #在nat表上新建名为MY_TCP自定义链 

1045 iptables -t nat -nvL #查看nat链

1046 iptables -t nat -A PREROUTING -j MY_TCP  #将MY_TCP加入到PREROUTING链后 

1047 iptables -t nat -nvL #查看nat链

1048 iptables -t nat -A MY_TCP -p tcp -d 192.168.1.230 -j REDIRECT --to-ports 23  #在端口为80, 目的地址是192.168.1.230时执行MY_TCP链 

[root@localhost tool]# iptables -N MY_TCP   #在filter表上新建名为MY_TCP自定义链 

[root @localhost tool]# iptables -A INPUT -j MY_TCP #把MY_TCP加入到INPUT链

[root@localhost tool]# iptables -A MY_TCP -p tcp --dport 2223 -j ACCEPT #在端口为2223时执行MY_TCP链 

7.4 本地socket捕获数据包,为socket设置IP_TRANSPARENT选项:

在设置完iptables规则之后,还须为socket设置IP_TRANSPARENT选项。

设置之后可以bind一个不属于本机的IP地址,作为客户端,它可以使用一个不属于本机地址的IP地址作为源IP发起连接,作为服务端,它可以侦听在一个不属于本机的IP地址上,而这正是透明代理所必须的。面对真实的客户端,透明代理明知道目标地址不是自己,却还是要接受连接,对于真实的服务器,透明代理明显不是真实的客户端,却还要使用真实客户端的地址发起连接。

setsockopt(fd,SOL_IP, TRANSPARENT,&opt,sizeof(opt));

setsockopt之后,作为代理服务器bind真实服务器addr,作为代理客户端bind真实客户端addr。

 

客户端代码socket_client_tcp.c:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define PORT 23			//目标地址端口号
#define ADDR "192.168.1.230" //目标地址IP

int main()
{
	int iSocketFD = 0; //socket句柄
	unsigned int iRemoteAddr = 0;
	struct sockaddr_in stRemoteAddr = {0}; //对端,即目标地址信息,服务器端地址
	socklen_t socklen = 0;  	
	char buf[4096] = {0}; //存储接收到的数据

	iSocketFD = socket(AF_INET, SOCK_STREAM, 0); // 建立客户端socket
	if(0 > iSocketFD)
	{
		printf("创建socket失败!\n");
		return 0;
	}	

	//定义要连接的代理服务器端地址
	stRemoteAddr.sin_family = AF_INET;
	stRemoteAddr.sin_port = htons(PORT);
	inet_pton(AF_INET, ADDR, &iRemoteAddr);
	stRemoteAddr.sin_addr.s_addr=iRemoteAddr;
	
	//连接到代理服务器:连接方法: 传入句柄,目标地址,和大小
	if(0 > connect(iSocketFD, (void *)&stRemoteAddr, sizeof(stRemoteAddr)))
	{
		printf("连接失败!\n");
		//printf("connect failed:%d",errno);//失败时也可打印errno
	}else{
		printf("连接成功!\n");
		//向代理服务器发送数据
		send(iSocketFD, "客户端-客户端-客户端", strlen("客户端-客户端-客户端"), 0);
		//从代理服务器接收数据
		recv(iSocketFD, buf, sizeof(buf), 0); ////将接收数据打入buf,参数分别是句柄,储存处,最大长度,其他信息(设为0即可)。 
		printf("Received:%s\n", buf);
	}
	
	close(iSocketFD);//关闭socket	
	return 0;
}

代理服务器端代码socket_proxy_server_tcp.c:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include           /* See NOTES */
#include 
#include 
#define PORT 2223		//端口号
#define BACKLOG 5	//最大监听数

int main()
{
	int iSocketFD = 0;  //socket句柄
	int iRecvLen = 0;   //接收成功后的返回值
	int new_fd = 0; 	//建立连接后的句柄
	char buf[4096] = {0}; //
	int n = 0;
	int ret = 0;
	struct sockaddr_in stLocalAddr = {0}; //本地地址信息结构图,下面有具体的属性赋值
	struct sockaddr_in stRemoteAddr = {0}; //对方地址信息
	socklen_t socklen = 0;  

	iSocketFD = socket(AF_INET, SOCK_STREAM, 0); //建立socket
	if(0 > iSocketFD)
	{
		printf("创建socket失败!\n");
		return 0;
	}	

	stLocalAddr.sin_family = AF_INET;  /*该属性表示接收本机或其他机器传输*/
	stLocalAddr.sin_port = htons(PORT); /*端口号*/
	stLocalAddr.sin_addr.s_addr=htonl(INADDR_ANY); /*IP,括号内容表示本机IP*/

	//绑定地址结构体和socket
	if(0 > bind(iSocketFD, (void *)&stLocalAddr, sizeof(stLocalAddr)))
	{
		printf("绑定失败!\n");
		return 0;
	}

	//开启监听 ,第二个参数是最大监听数
	if(0 > listen(iSocketFD, BACKLOG))
	{
		printf("监听失败!\n");
		return 0;
	}

	printf("iSocketFD: %d\n", iSocketFD);	
	//在这里阻塞知道接收到消息,参数分别是socket句柄,接收到的地址信息以及大小
	while(1)
	{
		new_fd = accept(iSocketFD, (void *)&stRemoteAddr, &socklen);
		printf("new_fd: %d\n", new_fd);	
		if(0 > new_fd)
		{
			printf("接收失败!\n");
			return 0;
		}else{
			printf("接收成功!\n");
			n = sizeof(struct sockaddr_in);
			ret = getsockopt(new_fd, SOL_IP, SO_ORIGINAL_DST, &stRemoteAddr, &n);
			if(0 != ret)
			{
				printf ("error getting original destination address.\n");
				close (new_fd);
				return -1;
			}
			stRemoteAddr.sin_family = AF_INET; 
			printf("original destination address %u:%hu\n", ntohl(stRemoteAddr.sin_addr.s_addr), ntohs(stRemoteAddr.sin_port));	
			//发送内容,参数分别是连接句柄,内容,大小,其他信息(设为0即可) 
			recv(new_fd, buf, sizeof(buf), 0);
			printf("从客户端上接收到的信息是:%s\n", buf);
			//send(new_fd, "这是代理服务器接收成功后发回的信息!", sizeof("这是代理服务器接收成功后发回的信息!"), 0);

			/* 连接服务器*/
			int iSockClientFD = 0;
			iSockClientFD = socket(AF_INET, SOCK_STREAM, 0);
			if(0 > iSockClientFD)					
			{
				printf("代理向服务器建立连接失败!\n");
				return 0;
			}
			if(0 > connect(iSockClientFD, (void *)&stRemoteAddr, sizeof(stRemoteAddr)))
			{
				printf("代理向服务器建立连接失败!\n");
				return 0;
			}
			send(iSockClientFD, buf, sizeof(buf), 0);//向服务器发送消息
			recv(iSockClientFD, buf, sizeof(buf), 0);//接收来自服务器的消息
			printf("从服务器端收到的消息:%s\n", buf);//打印接收到的来自服务器的消息
			send(new_fd, buf, sizeof(buf), 0);//向客户端发送消息		
			sleep(5);	
		}
	}
	close(new_fd);
	close(iSocketFD);

	return 0;
}

服务器端代码socket_server_tcp.c:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define PORT 23		//端口号
#define BACKLOG 5	//最大监听数

int main()
{
	int iSocketFD = 0;  //socket句柄
	int iRecvLen = 0;   //接收成功后的返回值
	int new_fd = 0; 	//建立连接后的句柄
	char buf[4096] = {0}; 
	struct sockaddr_in stLocalAddr = {0}; //本地地址信息结构图,下面有具体的属性赋值
	struct sockaddr_in stRemoteAddr = {0}; //对方地址信息
	socklen_t socklen = 0;  	

	iSocketFD = socket(AF_INET, SOCK_STREAM, 0); //建立socket
	if(0 > iSocketFD)
	{
		printf("创建socket失败!\n");
		return 0;
	}	

	stLocalAddr.sin_family = AF_INET;  /*该属性表示接收本机或其他机器传输*/
	stLocalAddr.sin_port = htons(PORT); /*绑定端口号*/
	stLocalAddr.sin_addr.s_addr=htonl(INADDR_ANY); /*IP,INADDR_IP表示任何IP地址*/

	//绑定地址结构体和socket
	if(0 > bind(iSocketFD, (void *)&stLocalAddr, sizeof(stLocalAddr)))
	{
		printf("绑定失败!\n");
		return 0;
	}

	//开启监听 ,第二个参数是最大监听数
	if(0 > listen(iSocketFD, BACKLOG))
	{
		printf("监听失败!\n");
		return 0;
	}

	printf("iSocketFD: %d\n", iSocketFD);	
	//在这里阻塞知道接收到消息,参数分别是socket句柄,接收到的地址信息以及大小 
	//会阻塞进程,直到有客户端连接上来为止
	while(1){
		new_fd = accept(iSocketFD, (void *)&stRemoteAddr, &socklen);
		if(0 > new_fd)
		{
			printf("接收失败!\n");
			return 0;
		}else{
			printf("接收成功!\n");
			//发送内容,参数分别是连接句柄,内容,大小,其他信息(设为0即可) 
			//send(new_fd, "这是服务器接收成功后发回的信息!", sizeof("这是服务器接收成功后发回的信息!"), 0);
		}

		printf("new_fd: %d\n", new_fd);	
		iRecvLen = recv(new_fd, buf, sizeof(buf), 0);	
		if(0 >= iRecvLen)    //对端关闭连接 返回0
		{	
			printf("接收失败或者对端关闭连接!\n");
		}else{
			//接收并打印代理服务器传过来的数据
			printf("从客户端接收到的消息: %s\n", buf);
		}

		//向代理服务器发送数据
		send(new_fd, "服务器-服务器-服务器" , strlen("服务器-服务器-服务器"), 0);
	}
	sleep(5);

	close(new_fd);
	close(iSocketFD);

	return 0;
}

8、 结果显示:

客户端(请求两次):

[root@localhost 桌面]# ./socket_client_tcp

连接成功!

Received:服务器-服务器-服务器

[root@localhost 桌面]# ./socket_client_tcp

连接成功!

Received:服务器-服务器-服务器

代理服务器端(接受两次):

[root@localhost 桌面]# ./socket_proxy_server_tcp.c

iSocketFD: 3

new_fd: 4

接收成功!

original destination address 3232236006:23

从客户端上接收到的信息是:客户端-客户端-客户端

从服务器端收到的消息:服务器-服务器-服务器



new_fd: 6

接收成功!

original destination address 3232236006:23

从客户端上接收到的信息是:客户端-客户端-客户端

从服务器端收到的消息:服务器-服务器-服务器

服务器端(接受两次):

[root@localhost 桌面]# ./socket_server_tcp.c

iSocketFD: 3

接收成功!

new_fd: 4

从客户端接收到的消息: 客户端-客户端-客户端



接收成功!

new_fd: 5

从客户端接收到的消息: 客户端-客户端-客户端

9、调试代码时遇到的问题:

[root@localhost tool]# make socket_server_tcp

cc socket_server_tcp.c -o socket_server_tcp

socket_server_tcp.c: 在函数‘main’中:

socket_server_tcp.c:63:41: 错误:‘SO_ORIGINAL_DST’未声明(在此函数内第一次使用)

ret = getsockopt(new_fd, IPPROTO_TCP, SO_ORIGINAL_DST, &stRemoteAddr, &n);

^

socket_server_tcp.c:63:41: 附注:每个未声明的标识符在其出现的函数内只报告一次

解决连接:

https://www.jianshu.com/p/bb540ef76c38

透明代理的原理如下:
假设A为内部网络客户机,B为外部网络服务器,C为防火墙。当A对B有连接请求时,TCP连接请求被防火墙截取并加以监控。截取后当发现连接需要使用代理服务器时,A和C之间首先建立连接,然后防火墙建立相应的代理服务通道与目标B建立连接,由此通过代理服务器建立A 和目标地址B的数据传输途径。从用户的角度看,A和B的连接是直接的,而实际上A 是通过代理服务器C和B建立连接的。反之,当B对A有连接请求时原理相同。由于这些连接过程是自动的,不需要客户端手工配置代理服务器,甚至用户根本不知道代理服务器的存在,因而对用户来说是透明的。

以上为百度百科的解释,此处防火墙也可以为路由器,路由器可以截获通过的网络数据,而将其转发到代理服务

对于网络数据的转发可以使用Linux的iptables命令进行设置
在路由器上使用iptables设置过转发规则后,路由器可以将收到的client程序的tcp数据转发给特定端口号,代理程序可在该端口号上使用tcp socket进行监听,获取连接后即可像普通socket程序一样进行与client端进行通讯;
代理程序与server端连接时,需要获取到server端的ip地址与端口号,该信息可以通过getsockopt的方式获取

getsockopt (clifd, SOL_IP, SO_ORIGINAL_DST, &orig_addr, &sin_size);

SO_ORIGINAL_DST是一个socket参数(SOL_IP层的),使用时需要包含#include 头文件。
如果成功orig_addr将是客户真正需要去的方向

之后代理程序可以将client的tcp转发server端,然后再将server端的数据转发给client端,即可实现透明代理

小礼物



作者:mayudong1
链接:https://www.jianshu.com/p/bb540ef76c38
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

解决方式:

增加头文件。使用时需要包含#include 头文件。

 

10、参考连接:

https://blog.csdn.net/u011431128/article/details/77481678

https://blog.csdn.net/zhuweisky/article/details/25787787

https://www.jianshu.com/p/bb540ef76c38

https://www.cnblogs.com/zhangdongsheng/p/3425929.html

http://www.codeforge.cn/article/256149?go_blog_box=1

https://blog.csdn.net/someonea/article/details/5864946

https://blog.lilydjwg.me/2018/7/16/transparent-proxy-for-tcp-and-udp-with-iptables.213139.html

【书籍】https://www.frozentux.net/iptables-tutorial/cn/iptables-tutorial-cn-1.1.19.html#DNATTARGET

你可能感兴趣的:(C,语言,协议,iptables)