我正在为局域网上的Raspberry Pi上的所有端口(1-65535)实现透明的TCP/UDP代理。我目前正在测试用目的地端口80路由TCP数据包到Raspberry Pi。其思想是,一个接口(cf“代理ip")捕获传入通信量,另一个接口(cf”服务器ip")将其发送到internet,并在原始接口向客户端发送响应之前对其进行处理。路由器上的必要路由是通过
iptables -t mangle -A PREROUTING -p tcp -s SERVER_IP -j ACCEPT
iptables -t mangle -A PREROUTING -p tcp -s SOME_TEST_CLIENT_IP --dport 80 -j MARK --set-mark 3
ip rule add fwmark 3 table 2
ip route add default via PROXY_IP dev br0 table 2
受此页的启发。该体系结构意味着外部IP地址与Raspberry PI的代理接口之间的一对一端口映射。数据包以正确的端口和目的地到达Raspberry Pi (用tcpdump验证),但是代理不接受连接:传入的SYN没有发送SYN。代理侦听套接字主要配置为
const char PROXY_IP_ADDR[] = "192.168.1....";
const char SERVER_IP_ADDR[] = "192.168.1....";
...
struct sockaddr_in saProxy = {0};
saProxy.sin_family = AF_INET;
saProxy.sin_port = htons(80);
inet_pton(AF_INET, PROXY_IP_ADDR, &(saProxy.sin_addr.s_addr));
int enable = 1;
int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(-1 == setsockopt(sockfd, SOL_IP, IP_TRANSPARENT, (const char*)&enable, sizeof(enable)) /*error processing*/;
if(-1 == bind(sockfd, (sockaddr*)&saProxy, sizeof(saProxy))) /* error processing*/;
if(-1 == listen(sockfd, 1)) /* error processing*/;
后面跟着epoll_ctl()和epoll_wait()。在没有上述路由的情况下,代理已经通过将HTTP请求和NBNS通信直接发送到PROXY_IP进行了测试,并且它正在正确地接收和处理这些连接。
不幸的是,我很少找到与IP_TRANSPARENT
相关的文档或示例。在我可以在Linux上做任何测试之前,我的原始Windows相关问题。内核版本是4.1.13-v7+。怎样才能实现这种代理呢?
编辑:--我相信我可能错过了Raspberry上的一些路由设置,比如可能描述过的这里,但是我对iptables没有什么经验,所以我不太理解这里描述的规则,尽管我已经读到内核拒绝接受非本地通信量,除非设置了一些特定的路由,因为它不知道套接字。
我还测试了直接绑定到外部IP地址的绑定,并试图侦听带有此目的地地址的数据包,但症状保持不变。
发布于 2017-05-26 16:59:20
解决办法其实很简单。为了将IP_TRANSPARENT
用于此目的,您需要将单个侦听套接字绑定到某个端口X。然后,您需要设置以下规则,假设您想重定向通过任何接口(我相信)的所有通信量,不包括代理本身生成的通信量。这里代理的IP是192.168.1.100,我们将TCP重定向到端口82,UDP重定向到端口83。
iptables -t mangle -A PREROUTING ! -d 192.168.1.100 -p tcp -j TPROXY --on-port 82 --on-ip 0.0.0.0 --tproxy-mark 0x1/0x1
iptables -t mangle -A PREROUTING ! -d 192.168.1.100 -p udp -j TPROXY --on-port 83 --on-ip 0.0.0.0 --tproxy-mark 0x1/0x1
ip rule add fwmark 1 lookup 100
ip route add local 0.0.0.0/0 dev lo table 100
Linux有一种特殊的机制,称为tproxy。
For TCP
从这里开始,accept返回的套接字将自动绑定到原始目的地并连接到源,因此使用它进行透明代理不需要在代理的这一边做更多的工作。
为了将套接字的原始目标作为sockaddr_in结构来获取,请像往常一样在accept()返回的套接字上调用getsockname()。
UDP
要获得UDP套接字上的原始目标,请在绑定之前设置此选项:
int enable = 1;
setsockopt(sockfd, SOL_IP, IP_RECVORIGDSTADDR, (const char*)&enable, sizeof(enable));
然后,接收数据并获取原始目的地。
char cmbuf[100];
unsigned char bytes[16*1024];
sockaddr_in srcIpAddr, dstIpAddr;
int dstPort;
iovec iov;
iov.iov_base = bytes;
iov.iov_len = sizeof(bytes)-1;
msghdr mh;
mh.msg_name = &srcIpAddr;
mh.msg_namelen = sizeof(sockaddr_in);
mh.msg_control = cmbuf;
mh.msg_controllen = 100;
mh.msg_iovlen = 1;
mh.msg_iov = &iov;
int res = recvmsg(sock, &mh, 0);
sem_post(&udpSem); //I use a semaphore to indicate when incoming data is read and socket is ready for new datagram to be processed
for(cmsghdr *cmsg = CMSG_FIRSTHDR(&mh); cmsg != NULL; cmsg = CMSG_NXTHDR(&mh, cmsg))
{
if(cmsg->cmsg_level != SOL_IP || cmsg->cmsg_type != IP_ORIGDSTADDR) continue; //normally we use IP_PKTINFO if not using tproxy, but this would yield 192.168.1.100:83 in the example
std::memcpy(&dstIpAddr, CMSG_DATA(cmsg), sizeof(sockaddr_in));
dstPort = ntohs(dstIpAddr.sin_port);
}
然后,如果我们想回复数据报,我们需要创建一个新的UDP套接字(因为UDP是无连接的),并将它绑定到存储在dstIpAddr
中的数据报的原始目的地。我在这里遇到了一些麻烦,因为我第一次尝试使用IP_FREEBIND
,但是这个选项似乎不适用于通过UDP发送数据,我认为它只适用于TCP侦听套接字,所以我们在绑定之前再次使用IP_TRANSPARENT
来绑定到一个非本地地址。
https://stackoverflow.com/questions/42738588
复制相似问题