Æðe*_*tan 6 sockets linux proxy
我正在为LAN上的Raspberry Pi上的所有端口(1-65535)实现透明的TCP / UDP代理。我目前正在测试将目标端口为80的TCP数据包路由到Raspberry Pi。这个想法是,一个接口(参见“代理ip”)捕获到来的流量,而另一个接口(参见“服务器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
Run Code Online (Sandbox Code Playgroud)
受此页面启发。这种架构意味着外部IP地址和Raspberry PI的代理接口之间是一对一的端口映射。数据包到达Raspberry Pi上正确的端口和目的地(已通过tcpdump验证),但是代理不接受连接:没有为传入的SYN发送SYN-ACK。代理侦听套接字主要配置有
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*/;
Run Code Online (Sandbox Code Playgroud)
后跟epoll_ctl()和epoll_wait()。已对代理进行了测试,可以直接将HTTP请求和NBNS流量发送到PROXY_IP,而无需进行上述路由,并且可以正确接收和处理这些连接。
不幸的是,我发现很少有关的文档或示例IP_TRANSPARENT。我原来的Windows相关的问题之前,我可以做在Linux上的任何测试。内核版本为4.1.13-v7 +。如何实现这种代理?
编辑: 我相信我可能会缺少Raspberry Pi上的某些路由设置,例如此处所述,但我对iptables的了解很少,因此尽管我读过非本地流量,但我不太了解那里描述的规则。被内核拒绝,除非设置了某些特定的路由,因为它不知道套接字。
我还测试了直接绑定到外部IP地址的尝试,并尝试侦听具有此目标地址的数据包,但是症状保持不变。
该解决方案实际上非常简单。为了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
Run Code Online (Sandbox Code Playgroud)
Linux 为此具有一种称为tproxy的特殊机制。
对于TCP
从这里开始,accept返回的套接字将自动绑定到原始目标并连接到源,因此使用它进行透明代理无需在代理的这一端进行任何工作。
为了以sockaddr_in结构的形式获取套接字的原始目标,请照常在由accept()返回的套接字上调用getsockname()。
对于UDP
为了能够获得原始目的地,请在UDP套接字上,在绑定之前设置此选项:
int enable = 1;
setsockopt(sockfd, SOL_IP, IP_RECVORIGDSTADDR, (const char*)&enable, sizeof(enable));
Run Code Online (Sandbox Code Playgroud)
然后,接收数据并获取原始目的地
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);
}
Run Code Online (Sandbox Code Playgroud)
然后,如果我们要回复数据报,则需要创建一个新的UDP套接字(因为UDP是无连接的)并将其绑定到数据报的原始目的地(存储在中)dstIpAddr。第一次尝试使用时IP_FREEBIND,我在这里遇到了一些麻烦,但是此选项似乎不适用于通过UDP发送数据,我认为它仅适用于TCP侦听套接字,因此IP_TRANSPARENT在绑定之前我们再次使用,以便能够绑定到非-本地地址。
| 归档时间: |
|
| 查看次数: |
3179 次 |
| 最近记录: |