macOS 如何让 socket 指定网卡发出数据(效果等同 Linux 的 SO_BINDTODEVICE)

2019-08-09 18:39:56 +08:00
 cs010

场景

现有无线网卡和虚拟网卡 utun,想实现通过更改路由表,所有数据由 utun 接收并处理后,由无线网卡发出。

如 data[1.1.1.1] --> utun --> en0 --> server[1.1.1.1]

问题

在 linux 下可通过socket.SO_BINDTODEVICE选项,将 socket 数据绑定到指定网卡,如 eth0,实现从 tun 设备读取数据,再由 eth0 发出,实验是成功的

而在 mac 上,没有等同于socket.SO_BINDTODEVICE的选项,搜索一圈,试了各种方法后都不可用!

试过但不可用的方法

  1. setsockopt(s, IPPROTO_IP, IP_BOUND_IF, &index, sizeof(index));
   int test_bind(int s, const char* dev){
       int index = if_nametoindex(dev);
       return setsockopt(s, IPPROTO_IP, IP_BOUND_IF, &index, sizeof(index));
   }
  1. bind(sock, pAdapterFound->ifa_addr, addrsize)
   int BindToDevice(int sock, int family, const char* devicename)
   {
       struct ifaddrs* pList = NULL;
       struct ifaddrs* pAdapter = NULL;
       struct ifaddrs* pAdapterFound = NULL;
       int bindresult = -1;

       int result = getifaddrs(&pList);

       if (result < 0)
           return -1;

       pAdapter = pList;
       while (pAdapter)
       {
           if ((pAdapter->ifa_addr != NULL) && (pAdapter->ifa_name != NULL) && (family      == pAdapter->ifa_addr->sa_family))
           {
               if (strcmp(pAdapter->ifa_name, devicename) == 0)
               {
                   pAdapterFound = pAdapter;
                   break;
               }
           }
           pAdapter = pAdapter->ifa_next;
       }

       if (pAdapterFound != NULL)
       {
           int addrsize = (family == AF_INET6)?sizeof(struct sockaddr_in6):sizeof(struct    sockaddr_in);
           bindresult = bind(sock, pAdapterFound->ifa_addr, addrsize);
       }

       freeifaddrs(pList);
       return bindresult;
   }

基本都是报错 Network is unreachable

So mac, what's your problem?

6025 次点击
所在节点    macOS
35 条回复
gggxxxx
2019-08-11 22:43:05 +08:00
@cs010 我知道你的问题所在了。
Mac 下的 socket 用 bind 指定 interface 的代码网上随便搜。我之前回帖说过,就是第二个参数填指定的 interface。
你这问题 pf 轻松解决,常用做法就是编辑 pf 规则根据不同 ip source 做不同处理,还是不理解你为啥要舍近求远。还有就是为啥又要修改 tcp 数据.......修改 tcp 数据的什么内容呢?很像是国内搞酸酸乳圈子里那种思路,我个人觉得意义不是很大。
cs010
2019-08-11 23:22:46 +08:00
@gggxxxx 我已经 Google 过 n 次了,几乎能找到的方法都试过,我主题里面也提到过,都不可用,你说的随便搜,不礼貌的说法,有点想当然了,不管如何,谢谢你
jedihy
2019-08-12 00:03:56 +08:00
你 bind 到指定网卡的 IP 不行吗?
cs010
2019-08-12 09:20:52 +08:00
@jedihy Of course not
jedihy
2019-08-12 09:58:19 +08:00
@cs010 为什么
cs010
2019-08-12 11:03:36 +08:00
@jedihy 因为你没试过
jedihy
2019-08-12 13:01:06 +08:00
@cs010 不明白你在说什么
cs010
2019-08-12 16:59:44 +08:00
@Srar 如果 socket 没法直接指定,libpcap 是个替代,但需要再重新把 tcp 数据封装为 ip 报文,太重了,会疯滴
njzy
2019-08-12 19:16:14 +08:00
利用 tun 实现透明代理 部分软件直连?
FH0
2020-03-15 08:42:21 +08:00
@cs010 楼主,绑定网卡是需要 root 权限的,跨平台的话不怎么行。还有我注意到你说用 lwip 接收来自 tun 的 tcp 数据,然后用 socket 重新写回 tun,这个怎么实现?
cs010
2020-03-15 11:52:34 +08:00
@FH0 不是 socket 直接写回 tun,与 tun 的所有数据交互,都是通过 lwip 完成的
FunnyCat
2020-05-21 15:28:24 +08:00
@cs010 不知道楼主现在问题解决了没有,有机会的话可以沟通交流一下。我做了一个跨平台的 VPN 软件,其中很多遇到的坑应该都和你一样踩过。看了很多回复,很多人真的太过于想当然了。从路由层面上来说,修改路由表是跨平台方案绝对不可行的一条路,在 ios/android 平台基本上属于死路。首先 VPN 的话,如果想做到尽可能多的事情,Android 的 VPNService,macos/ios 的 Network Extension 或 Network system Extension(PacketTunnelProvider ),windows 的 Wintun 都是不错的选择。至于在 default route 走 tun 情况下,其他流量怎么出去;首先系统为了防止到 VPN server 不再命中 default ,都提供了一种添加明细的方式(当然,大部分都是开启时指定,不能动态修改),如 Android 里 VPNService 的 protect, macos/ios 里 NE 的 SeverIP/Exculde routes 等可以帮助你做到这点。如果不满足的话,就像你说的必须经过协议栈(lwip,实际上有一种巧妙的方法可以让系统协议栈帮我们做这件事而不用嵌入用户态协议栈)拿到报文,你自己作为一个 TCP/UDP 代理再绑定接口把包发出去。socket.SO_BINDTODEVICE 这个选项如果不行,你可以直接绑定接口的 src ip 尝试一下,我实测两种方式都是 work 的,后者怀疑是 macos 有相关的策略路由 :)
cs010
2020-06-19 18:14:44 +08:00
@FunnyCat
感谢你的认真回复!
前段时间一直有事暂时放下了,后面有空会继续

> 修改路由表是跨平台方案绝对不可行的一条路

是的,修改路由表只是为了,在桌面上劫持全局流量.移动端如 Android 本身就是默认全局劫持的,不用修改路由表


> 首先系统为了防止到 VPN server 不再命中 default ,都提供了一种添加明细的方式

我了解到 android 有 protect,在桌面端除了 socket.SO_BINDTODEVICE 来绑定指定网卡发数据,没找到其它方法,而且 mac 居然不支持,这也是发本帖的原因


>实际上有一种巧妙的方法可以让系统协议栈帮我们做这件事而不用嵌入用户态协议栈

方便放些资料吗?是不是个平台都可以实现呢?
FunnyCat
2020-06-29 19:44:51 +08:00
@cs010
利用系统协议栈收的方法是这样的,默认全平台是由三层的 tun 设备来引流的情况下,tun 上的 ip 我们可以利用。在 tun 上这个 ip 起 tcp 和 udp 的 server,外部命中 tun 路由的流量首先会被你从 tun 上 read 到,通过读到的 ip 报文修改目的 ip 和目的端口号(当然,包括首部检验和),将其修改为你 tun 上的端口和地址再写回 tun 。然后系统自然就会帮你组包,你就可拿到原始数据,再通过对应的 tcp 或 udp socket 绑定接口往外发(当然,你还得记录这些数据是送往哪个地址哪个端口的)。

当然,这种能力还是需要绑定接口不命中默认路由的。这是两个问题,一个是 tcp 和 udp 数据处理重组的问题,因为你在移动端发出去一定会带额外的头部。 另一个是不命中路由的这个问题, 这个 socket.SO_BINDTODEVICE 是可行的,你可以再试试。
cs010
2020-07-11 11:07:25 +08:00
@FunnyCat 的确是个好思路,bind src ip 在 mac 上试过,能生效的前提是,dst ip 在路由表里本身能路由到 src ip 绑定的网卡,但是这显然不是和 protect 一样的效果,不知道你那边是 ok 的吗
另外这是我的微信,大佬方便的话可以加一下,我们可以交流一下 base64: Y3NjczAxMDAxMAo=
邮箱 base64:Y3MwMTBAaG90bWFpbC5jb20K

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/590555

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX