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?

6002 次点击
所在节点    macOS
35 条回复
auser
2019-08-09 18:53:19 +08:00
楼主应该把想实现得需求描述出来。

万一自己的理解是错的或者现在的技术方案行不通呢?
cs010
2019-08-09 19:27:19 +08:00
@auser 感谢提醒,已 append
auser
2019-08-09 20:44:49 +08:00
需求说得好隐晦。

建议楼主看下苹果的 Network Extension,不从路由层面入手。即便路由层面搞定了,复杂的规则,比如规律需要使用域名而非 IP,没了类似 Linux 下 dnsmasq 的 ipset,macOS 下想做这件事就困难很多了,及时性也是个问题。

我在 mac 内核中干过这类事情,这是最佳解决方案,可以实现各种需求。不过苹果最新的系统加强了限制,驱动方向往应用层实现发展了,我估计连内核扩展的签名权限都很难申请了。

tun 的方式建议在 Linux 下来做,转发层面可以快速出成果。
auser
2019-08-09 20:47:47 +08:00
另外,macOS 的内核源码是开源的。这些选项建议配合源码来看,只看 socket 接口层即可。这样就内深入了解正文中的 problem 了。

这是各非常冷门的方向,所以源码是重要的信息来源。
cs010
2019-08-09 21:07:47 +08:00
@auser 谢谢大佬!潜水这么久,第一次在 v2 提问,看到这么认真的回答,很感动。

> tun 的方式建议在 Linux 下来做,转发层面可以快速出成果。

为什么说在 mac,因为我想做的是跨平台,tun 在 linux,mac,win 都有 tun 的实现,linux 已验证、mac 正在验证、win 待验证。

> 即便路由层面搞定了,复杂的规则,比如规律需要使用域名而非 IP

是的,根据域名本来也是考虑范围,但是鉴于在 tun 层只能拿到 ip,只在 tun 层是很难搞定的,遂放低优先级。

> 这些选项建议配合源码来看,只看 socket 接口层即可

谢谢,我会去尝试深入

但在此之前,我仍希望得到,像你这样热心的大佬们的指点,少走弯路 :)
cs010
2019-08-09 21:32:18 +08:00
@auser 补充一句,路由表目前在我的需求中,只是对数据包分流进行了粗分,我想在 tun 层进行细分
gggxxxx
2019-08-09 21:58:12 +08:00
网络开放方面 mac 比 linux 舒服多了。

如果是做 vpn 类似的东西,直接 pf (packet filter) 1 句话就搞定了。
例子: "nat on utun2 from en5:network to any -> (utun2)"

如果是多网卡情况下单纯指定某个物理网卡,直接 socket 的 bind 语句就行了。第二个参数填指定的物理网卡。
cs010
2019-08-09 22:09:21 +08:00
@gggxxx 感谢回复,无论直接 bind IP,还是在正文中,第二个不可用方法提到过的 bind,都是反复测试后,不可用的
cs010
2019-08-09 22:14:46 +08:00
@gggxxxx 而且,你可能指的是接收数据绑定,我这里是发送数据绑定哈。
换句话说,虽然我默认把 2.2.2.2 路由到 utun,但是从 utun 拿到目的地 2.2.2.2 的数据后,我现在想指定 2.2.2.2 的数据由 en0 发送而不是 utun
gggxxxx
2019-08-09 22:20:42 +08:00
1. pf 就是 linux 上的 iptables。难道还有什么需求是做不了?
2. 你使用 socket 的 bind 的姿势不对,当然不会成功。我很多年前的代码在今天依然正常工作。
cs010
2019-08-09 22:43:38 +08:00
@gggxxxx pf 只做映射当然可以,但是要动态映射,动态指定网卡,不知道可否。这里主要问题不是流量能不能走到 utun,而是走到 utun,我拿到数据,怎么再决定它由 en0 而不是 utun 发到外网
gggxxxx
2019-08-09 23:04:02 +08:00
你这不就是典型的 vpn 模型嘛。不知道你在纠结啥。
pf 资料搜搜一大把,程序内动态应用新的 pf 规则不就行了。
auser
2019-08-09 23:57:25 +08:00
使用 Network Extension 是苹果平台的最佳选择,手机与电脑都可以用。TUN 需要 root 权限,如果未来支持手机端这会是个问题。苹果那个开源的 tuntap 驱动跟 windows 在实现上是有的区别的。这方面建议了解下 open 开头那个产品文档,tun 方面有说明。

至于在 tun 层细分,那么打算如何把截获的 IP 报文重新“插入”道协议栈然后送到二层继续处理?此外,IP 报文一定要处理分片的情况,那么这两处需要在预研阶段就确认,否则做出来的程序是有致命缺陷的。至于后续的 TCP 握手阶段 MSS,ICMP 层的一些协议,都是后续会发现的“坑”。

等等等等细节有些多。题外话:为曾经的思路是在终端设备上搞,后来直接在网关上弄了,这样我在电视上都能看黑镜了,游戏主机也可以直接玩。然后还能给家里设备分到 ipv6 的地址只在在外网访问。如果楼主不是给公司做,建议在网关设备上摸索摸索。一劳永逸。

祝玩得开心。
cs010
2019-08-10 13:18:54 +08:00
@gggxxxx 纠结之处在于,能动态修改但要高效,所以没有尝试去动态修改路由表(否则直接启进程调用 pf 就行了)。在 linux 中,简单的 setopt SO_BINDTODEVICE 参数就行,这也是最佳方案,简单高效,但在 mac,无法直接 bind。你提到说 bind 是可行的,我的姿势不对,不知道你是否方便分享一下你的代码片段,非常感谢!
另外,我查了 pf 应该是可编程动态修改规则的,但又担心效率,我去试试。![man pf]( https://man.openbsd.org/pf) ![EXAMPLES]( https://man.openbsd.org/pf#EXAMPLES),如果 ok,我会 append
cs010
2019-08-10 13:41:50 +08:00
@auser 是的,分流的问题 linux 有 SO_BINDTODEVICE,Android 有 VPNService 的 protect,windows 似乎可以直接 bind,macos、ios 的无 root 通用方法似乎只有你提到的 Network Extension。

现在,我的其它部分差不多都 ok 了,不分流的情况下已经可以正常稳定的跑起来了,主要是分流的问题。协议层的处理已经用 lwip 完成。总的来说就是,由 lwip 完成 tcp 协议的事情,拿到数据后,再交由代理模块处理,代理模块决定走代理,还是直接选择 en0 发出去。另外 mac 的 tun 的操作,是借鉴 openvpn 和 github 上的 water 项目。

跨平台对我是必须要做的,不然不管 linux 的 tun/tap 还是 socket,甚至是 raw socket 都很方便,一路研究过来,坑比较少。
gggxxxx
2019-08-10 15:45:13 +08:00
@cs010 实在无法理解你的纠结点......也无法理解你的思路。要不你从普通用户角度描述下你这个东西的具体作用和功能


你顶楼说的情况,就一个物理网卡和 utun 而已。utun 唯一的作用是收集发出去的网络数据而已。真正对外收发数据的只有唯一的物理网卡。这不就是一个 nat 搞定的事么?
再啰嗦一点,前面有人提到的 Network Extension,实际上就是把 pf 的操作包装了下。
1. 创建一个 utun。2. pf 设置下 nat 规则
如果你是做常规 vpn,你只需要做这 2 个动作就足够了。你只有一个物理网卡,你希望 bind 的意义在哪里?
cs010
2019-08-10 19:48:43 +08:00
@gggxxxx 你没理解到我的意思,我这里的关键是 utun 拿到数据后,要动态路由的问题

考虑这个场景,假设我要全局劫持流量

1,设置默认路由到 utun,所有流量到 utun
2,从 utun 获取 IP 数据包,通过 lwip 获取 TCP 数据 data 后,然后不想通过 lwip 再次封装为 IP 包,而是直接通过 socket 将 data 发出去。这时问题是,默认路由是到 utun 的,如果默认路由发 data 出去,势必又会到 utun,造成死循环,为解决这个问题,可能的几种方法:

1,上面提到的 socket 选项,但只有 Linux 支持,mac 没有

2,是否可通过上面提到的 bind 等方案,我自己试验不行,如果你说可以,可否麻烦分享一下你的代码片段

3,每次发送 data 时,动态更改路由(或者用 pf 等),发送完后,再改回来。这里是重点,决定了你的方案不可用,虽然我这次发送,决定走代理出去,但下次同一个目的 IP 数据包,我可能想让它通过 en0 出去,所以我不能把路由表或者 pf 的规则写死为 en0,而是动态的,这就需要效率,如果 man pf(4)的方法效率没问题,那可以考虑动态更改。

如果不考虑需求,其实很简单可以描述,mac 上的 socket 是否有和 Linux 一样的选项,我可以指定这个 socket 发送数据时,由哪个网卡发出去,当然前提是我要动态和效率,不要写死路由表或者 pf 规则。如代码中,这样写 socket.sendto(data, 任意网卡名)

我不知道这样说,你了解了吗?
gggxxxx
2019-08-11 07:24:10 +08:00
@cs010 怎么又有代理的东西了?
你方便的话直接明盘具体功能细节吧。

1. utun 是 ip 数据包,是非常低的网络层了。你把 IP 数据包还原成 tcp 这步我看不懂思路和目的,这不是豆腐做成肉价钱了吗?要 tcp 数据,直接 socks proxy 不就行了吗?
2. nat 规则写好了,是等同 bind 指定网卡的。你的情况只有一个真实有效的网络卡,怎么还会有所谓动态调整的需求?你先不要考虑你所谓的“自研路由代理”一类的东西,就单纯说一个 utun 和一个真实网卡这种模型,数据只能从物理网卡走如果想正常上网的话。
Srar
2019-08-11 13:21:26 +08:00
再来个 Pcap ?初始化 Pcap 时候指定网卡对外发包
cs010
2019-08-11 14:14:53 +08:00
@gggxxxx
我觉得我解释得应该够清楚了:)

1,我要全局代理可以用 tun 劫持全局流量对吧,我要在 Android 上全局代理,必须用 vpnservice,在 IP 层,为了平台通用性肯定选型在 IP 层,没问题吧。想改 TCP 数据,必须读取 TCP 数据,改完了,想发出去而不用再次封为 IP 报文,肯定用 socket 直接转发 TCP 数据啊

2,往外发肯定走物理网卡毫无疑问,但是我要劫持流量啊,当然要用 tun 拿到全局流量,再往外发呀

各种 socks 代理当然可以代理劫持,关键是,全局劫持,通用性,所以选 tun,在 IP 层做了呀。

其实需求不用质疑,没问题,这里唯一的问题就是怎么用 socket 指定网卡发送的问题。

你可以把问题简化为,mac 上,两个物理网卡,socket 怎么选任意网卡往外发的问题,只有这个问题

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

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

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

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

© 2021 V2EX