IP_TRANSPARENT用法 [英] IP_TRANSPARENT usage

查看:176
本文介绍了IP_TRANSPARENT用法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在为LAN上的Raspberry Pi上的所有端口(1-65535)实现透明的TCP/UDP代理.我目前正在测试将目标端口为80的TCP数据包路由到Raspberry Pi.这个想法是,一个接口(cf代理ip")捕获传入的流量,而另一个接口(cf服务器ip")则将其发送到Internet并进行处理,然后再将原始接口发送给客户端.路由器上的必要路由是通过

I am implementing a transparent TCP/UDP proxy for all ports (1-65535) on a Raspberry Pi on LAN. I am currently testing routing TCP packets with destination port 80 to the Raspberry Pi. The idea is that one interface (cf "proxy ip") captures incoming traffic and the other (cf "server ip") sends it to the internet and processes it before the original one sends the response to the client. The necessary routing on the router is done via

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-ACK.代理侦听套接字主要配置为

inspired by this page. This architecture implies a one-to-one port mapping between external IP addresses and the Raspberry PI's proxy interface. The packets arrive with the correct port and destination on the Raspberry Pi (verified with tcpdump), however the proxy doesn't accept the connections: no SYN-ACK is sent for the incoming SYN's. The proxy listening sockets are mainly configured with

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,而无需进行上述路由,并且可以正确接收和处理这些连接.

Followed by epoll_ctl() and epoll_wait(). The proxy has been tested with sending HTTP requests and NBNS traffic directly to PROXY_IP without the aforementioned routing in place and it is receiving and processing these connections properly.

不幸的是,我发现很少有与IP_TRANSPARENT相关的文档或示例.我的与Windows相关的原始问题在我可以在Linux上进行任何测试之前.内核版本为4.1.13-v7 +.如何实现这种代理?

Unfortunately, I have found very little documentation or examples related to IP_TRANSPARENT. My original Windows-related question before I could do any testing on Linux. Kernel version is 4.1.13-v7+. How can I achieve this type of proxying?

我相信我可能会丢失Raspberry Pi上的某些路由设置,例如

I believe I may be missing some routing settings on the Raspberry Pi such as perhaps described here, but I have very little experience with iptables so I don't quite understand the rules described there, although I have read that non-local traffic is rejected by the kernel unless some specific routing is set up since it doesn't know about sockets.

我还测试了直接绑定到外部IP地址的尝试,并尝试侦听具有此目标地址的数据包,但症状保持不变.

I have also tested binding directly to an external IP address and attempted to listen for packets with this destination address, but the symptoms remain unchanged.

推荐答案

该解决方案实际上非常简单.为了将IP_TRANSPARENT用于此目的,您需要将单个侦听套接字绑定到某个端口X.然后,您需要设置以下规则,假设您想重定向通过任何(我认为)接口的所有流量,排除代理本身产生的流量.在这里,代理的IP为192.168.1.100,我们将TCP重定向到端口82,将UDP重定向到端口83.

The solution was pretty simple actually. In order to use IP_TRANSPARENT for this purpose, you need to have a single listening socket bound to some port X. Then you need to setup the following rules, assuming you want to redirect ALL traffic going through any (I believe) interface, excluding traffic generated for/by the proxy itself. Here the proxy's IP is 192.168.1.100 and we redirect TCP to port 82 and UDP to port 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 的特殊机制.

Linux has a special mechanism called tproxy for this.

对于TCP

从这里开始,accept返回的套接字将自动绑定到原始目标并连接到源,因此使用它进行透明代理不需要在代理的这一端进行任何工作.

From here on, the socket returned by accept is automatically bound to the original destination and connected to the source, so using it for transparent proxying requires no more work on this side of the proxy.

为了以sockaddr_in结构的形式获取套接字的原始目标,请照常在调用accept()返回的套接字上调用getsockname().

In order to get the original destination of the socket as a sockaddr_in structure, call getsockname() on the socket returned by accept() as usual.

对于UDP

要获得原始目的地,请在UDP套接字上,在绑定之前设置此选项:

To be able to get the original destination, on the UDP socket, set this option before binding:

int enable = 1;
setsockopt(sockfd, SOL_IP, IP_RECVORIGDSTADDR, (const char*)&enable, sizeof(enable));

然后,接收数据并获取原始目的地

Then, to receive the data and get the original destination

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能够绑定到非本地地址.

Then, if we want to reply to the datagram, we need to make a new UDP socket (as UDP is connectionless) and bind it to the original destination of the datagram, stored in dstIpAddr. I had some trouble here as I first tried using IP_FREEBIND, but this option does not seem to work for sending data through UDP, I think it is only intended for TCP listening sockets, so we use IP_TRANSPARENT again before binding to be able to bind to a non-local address.

这篇关于IP_TRANSPARENT用法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆