失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > Linux 实现透明代理(使用开源项目 tproxy-example)

Linux 实现透明代理(使用开源项目 tproxy-example)

时间:2020-03-02 00:37:48

相关推荐

Linux 实现透明代理(使用开源项目 tproxy-example)

一、透明代理概述

根据百度百科的资料:透明代理的意思是客户端根本不需要知道有代理服务器的存在,它改变你的 request fields(报文),并会传送真实IP,多用于路由器的NAT转发中。

透明代理技术中的透明是指客户端感觉不到代理的存在,不需要在浏览器中设置任何代理,客户只需要设置缺省网关,客户的访问外部网络的数据包被发送到缺省网关,而这时缺省网关运行着一个代理服务器,数据实际上被被重定向到代理服务器的代理端口(如8080),即由本地代理服务器向外请求所需数据然后拷贝给客户端。理论上透明代理可以对任何协议通用。

二、实验目标

如上图所示,本实验的目标主要是将一个主机 B 配置成一个 TCP 透明代理服务器,使不在同一个网段的客户主机 A 和客户主机 B 可以通过这个代理服务器进行通信,且客户主机 A、C 对这个透明代理服务器是无感知的,即它们是直接将数据包发送给对方的真实 IP 的。

主机 A 直接发送数据给不同网段的 IP (192.168.2.12) ,主机 B(即代理服务器)可以将数据转发到目的地。同样,主机 C 到主机 A 的方向也成立。

注:

上述实验所采用的系统均为 Ubuntu。三台机器之间需要通过交换机连接,不能直连。

三、环境搭建

在本实验中,要实现 TCP 透明代理功能,首先需要配置好路由转发功能。

3.1 代理服务器配置

3.1.1 开启内核路由转发

首先,在作为代理服务器的主机 B 上开启内核路由转发功能。

打开/etc/sysctl.conf文件,在文件中添加如下配置:

net.ipv4.ip_forward = 1

在终端中输入如下命令,使配置生效:

sysctl -p

如此,主机 B 的内核路由转发功能便被开启了。

3.1.2 配置转发规则

接下来,需要配置作为代理服务器的主机 B 的转发规则。

从我们上面的目标的示意图来看,要求代理服务器 B 可以将192.168.1.20的数据包转发到192.168.2.12,同时还要求数据包也可以反着转发。 因此,我们需要配置代理服务器 B 可以将 1 网段的数据转发到 2 网段,同样也可以将 2 网段的数据转发到 1 网段。

配置将 1 网段的数据转发到 2 网段的规则,即数据从 eth0 网卡入,从 eth1 网卡出:

iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -d 192.168.2.0/24 -o eth1 -j MASQUERADE

配置将 2 网段的数据转发到 1 网段的规则,即数据从 eth1 网卡入,从 eth0 网卡出:

iptables -t nat -A POSTROUTING -s 192.168.2.0/24 -d 192.168.1.0/24 -o eth0 -j MASQUERADE

3.2 客户主机配置

3.2.1 客户主机 A 配置

连接客户主机 A 与代理服务器 B

首先需要将客户主机 A 通过交换机与代理服务器 B 连接,然后将客户主机 A 的 IP 设置为192.168.1.20(不要求必须是这个,只要与代理服务器 B 的 eth0 网卡在同一个网段即可)。

上述步骤完成之后,在客户主机 A 上 Ping 一下代理服务器 B 的 eth0 网卡看看能否连通,只有能够连通才可以进行下面的步骤。

添加静态路由

在主机 A 的终端上输入如下命令以添加静态路由:

route add -net 192.168.2.0 netmask 255.255.255.0 gw 192.168.1.21

上述命令的作用是:所有发往192.168.2.0地址段的 IP 数据包,全部由192.168.1.21路径转发。

添加静态路由之后,在客户主机 A 上 Ping 一下代理服务器的 eth1 网卡,看看静态路由是否配置成功。

3.2.2 客户主机 C 配置

连接客户主机 C 与代理服务器 B

同样,主机 C 与主机 B 也需要交换机连接,然后将主机 C 的 IP 设置为192.168.2.12(只要与主机 B 的eth1 在同一个网段即可)。

同样,在主机 C 上 Ping 一下主机 B 看能否连通。

添加静态路由

在主机 C 上输入如下命令以添加静态路由:

route add -net 192.168.1.0 netmask 255.255.255.0 gw 192.168.2.11

上述命令的作用是:所有发往192.168.1.0地址段的 IP 数据包,全部由192.168.2.11路径转发。同样,在主机 C 上 Ping 一下代理服务器 B 的 eth0 网卡,能够连通则说明静态路由配置成功。

四、实现 TCP 透明代理

本实验中采用开源项目 tproxy-example ,将其运行在代理服务器 B 上来实现 TCP 透明代理功能。

按照下面的步骤来测试透明代理:

先说一下测试步骤:

首先在主机 C 上运行自定义的 server 程序,该程序可以将从主机 A 收到的数据回显给主机 A。然后在代理服务器 B 上运行开源项目 tproxy-example ,该程序可以将主机 A 的数据转发给主机 C,同样也可以将主机 C 的数据转发到主机 A。最后在主机 A 上运行自定义的 client 程序,该程序可以发送自定义的信息到主机 C。

如果一切配置正常,那么在客户主机 A 上运行的 client 程序发送的文本消息可以顺利发送到客户主机 C 的 server 上,同时主机 C 会将数据回显到主机 A 上。

最后附上我自定义的 server 和 client 的代码。

server.cpp

/*使用 epoll 的服务端程序*/#include <iostream>#include <sys/socket.h>#include <unistd.h>#include <netinet/in.h>#include <strings.h>#include <sys/epoll.h>#include <fcntl.h>#define SERVER_PROT 9876#define MESSAGE_LEN 1024#define MAX_EVENTS 20#define TIMEOUT 500int main(int argc, char* argv[]){int socket_fd, accept_fd;int on = 1;int ret = -1;struct sockaddr_in localaddr, remoteaddr;socklen_t addrlen;char recv_buf[1024];int epoll_fd;struct epoll_event ev, events[MAX_EVENTS];int event_number;int flags;// 1. 创建套接字文件描述符,并设置 socket 选项socket_fd = socket(AF_INET, SOCK_STREAM, 0);if(socket_fd == -1){std::cout << "Failed to create socket !" << std::endl;exit(-1);}ret = setsockopt(socket_fd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));if(ret == -1){std::cout << "Failed to set socket !" << std::endl;}/* 要把文件描述符设置为非阻塞的 */flags = fcntl(accept_fd, F_GETFL, 0);fcntl(accept_fd, F_SETFL, flags | O_NONBLOCK);// 2. 绑定端口和地址localaddr.sin_family = AF_INET;localaddr.sin_port = htons(SERVER_PROT);localaddr.sin_addr.s_addr = INADDR_ANY;bzero(&(localaddr.sin_zero), 8);// 填充 0 到数组中ret = bind(socket_fd,(struct sockaddr *)&localaddr,sizeof(struct sockaddr_in));if(ret == -1){std::cout << "Failed to bind the socket !" << std::endl;exit(-1);}// 3. 开始监听ret = listen(socket_fd, 10); //由于 listen 一次只能接受一个,所以需要一个队列来存放并发的请求,长度为10够了if(ret == -1){std::cout << "Failed to listen... !" << std::endl;exit(-1);}std::cout << "will to listen port: " << SERVER_PROT << std::endl;/* epoll_1:创建一个 epoll 文件描述符 */epoll_fd = epoll_create(256);ev.events = EPOLLIN;ev.data.fd = socket_fd;/* epoll_2:将监听文件描述符添加到 epoll_fd 中 */epoll_ctl(epoll_fd, EPOLL_CTL_ADD, socket_fd, &ev);addrlen = sizeof(struct sockaddr_in);for(;;){/* epoll_3:等待事件,相当于 select 函数 */event_number = epoll_wait(epoll_fd, events, MAX_EVENTS, TIMEOUT);for(int i=0; i<event_number; i++){/* 判断是数据的,还是侦听的 socket */if(events[i].data.fd == socket_fd){// 如果是侦听的,说明有新连接来了accept_fd = accept(socket_fd,(struct sockaddr *)&remoteaddr,&addrlen);/* 要把文件描述符设置为非阻塞的 */flags = fcntl(accept_fd, F_GETFL, 0);fcntl(accept_fd, F_SETFL, flags | O_NONBLOCK);/* 设置边缘触发 */ev.events = EPOLLIN | EPOLLET;ev.data.fd = accept_fd;/* 将新的文件描述符添加到 epoll_fd 中 */epoll_ctl(epoll_fd, EPOLL_CTL_ADD, accept_fd, &ev);}else if(events[i].events & EPOLLIN){// 如果是一个读入事件do{ret = recv(events[i].data.fd,(void *)recv_buf,MESSAGE_LEN,0);}while(ret < 0 && errno == EINTR); // 如果由于中断出错,说明数据未读完,就继续读/* 如果对端关闭连接 */if(ret == 0){close(events[i].data.fd);}/* 如果缓冲区满了 */if(ret == MESSAGE_LEN){std::cout << "may be have more data" << std::endl;}/* 如果数据已经读完了,出错了 */if(ret < 0 ){switch(errno){case EAGAIN:continue;default:continue;}}/* 如果有数据 */if(ret > 0){/* 打印数据 */recv_buf[ret] = '\0';std::cout << "Server Recv:-----> " << recv_buf << std::endl;/* 数据回显 */ret = send(events[i].data.fd,(void *)recv_buf,MESSAGE_LEN,0);}}} }close(socket_fd);return 0;}

client.cpp

#include <iostream>#include <sys/socket.h>#include <unistd.h>#include <netinet/in.h>#include <strings.h>#include <string.h>#include <arpa/inet.h>#define SERVER_PORT 9876#define MESSAGE_LEN 1024int main(int argc, char* argv[]){int socket_fd;int on = 1;int ret = -1;struct sockaddr_in serveraddr;char send_buf[MESSAGE_LEN] = "Hello ! This is Client";char recv_buf[MESSAGE_LEN];/* 1. 创建文件描述符 */socket_fd = socket(AF_INET, SOCK_STREAM, 0);if(socket_fd == -1){std::cout << "Failed to create socket !" << std::endl;exit(-1);}// 设置 socket 选项ret = setsockopt(socket_fd,SOL_SOCKET,SO_REUSEADDR,(void *)&on,sizeof(on));if(ret == -1){std::cout << "Failed to set socket !" << std::endl;exit(-1);}/* 2. 连接服务器 */// 配置服务端信息serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(SERVER_PORT);serveraddr.sin_addr.s_addr = inet_addr("192.168.2.12");// 主机 C 的 IPbzero(&(serveraddr.sin_zero), 8); // 将预留位赋值为0// 连接ret = connect(socket_fd,(struct sockaddr *)&serveraddr,sizeof(struct sockaddr));if(ret == -1){std::cout << "Failed to connect !" << std::endl;exit(-1);}/* 3. 发送数据 */while(1){// 从控制台输入要发送的数据memset((void *)send_buf , 0, MESSAGE_LEN);gets(send_buf);// 发送数据ret = send(socket_fd, (void *)send_buf, strlen(send_buf), 0);if(ret <= 0){std::cout << "Failed to send data !" << std::endl;break;}std::cout << "Client Send --------> \n" << send_buf << std::endl;/* 4. 接收数据 */ret = recv(socket_fd, (void *)recv_buf, MESSAGE_LEN, 0);if(ret <= 0){break;}// 要把接收到的数据的数组中此条长度的后面切掉,否则会出现拼接上一次数据的现象recv_buf[ret] = '\0';std::cout << "Recv From Server------------> \n" << recv_buf << std::endl;bzero((void *)recv_buf, MESSAGE_LEN);}close(socket_fd);return 0;}

如果觉得《Linux 实现透明代理(使用开源项目 tproxy-example)》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。