Elixir/Erlang:gen_udp 伪头部

4

我希望在Elixir中接收UDP消息,并需要获取数据包发送到的目标IP。

在iex中,可以使用以下命令打开一个套接字:

{:ok, socket} = :gen_udp.open(19_999, [{:active, true}])

当我收到一条消息时,它将以以下格式呈现:

{:udp, port, source_ip, source_port, data}

目标IP地址不是消息的一部分。根据UDP协议规定,源IP地址不属于UDP数据包的一部分,但它是IP伪头部的一部分。当我使用tcpdump时,可以看到数据包的目标IP地址。
是否有任何方法/参数可以获取UDP数据包的IP或伪头部?

我在某个地方读到了一条评论,说伪头永远不会被发送,而是用于计算校验和,这意味着你无法获取伪头。 - 7stud
这里是:https://networkengineering.stackexchange.com/questions/6679/why-no-source-and-destination-ips-in-udp-datagram - 7stud
我有点困惑你的问题。如果你的应用程序正在接收一个数据包,那么该数据包的目标地址就是你的主机地址。你是想找到目标地址以便在接收数据包后回复吗? - Venkatakumar Srinivasan
我自己从未使用过,但是 socket 模块似乎支持获取有关接收数据包的更多信息:http://erlang.org/doc/man/socket.html - legoscia
目标端口是您在 :gen_udp.open 中设置的端口(在您的代码中为19_999)。然而,目标IP有点棘手,因为 :gen_udp.open 默认侦听 0.0.0.0,这是指所有可用的网络适配器,包括像 lo 这样的虚拟适配器。由于不同网络中的设备无法相互通信,因此您可以通过查看源IP来确定哪个IP地址正在接收当前数据包。 - Aetherus
1个回答

2

目标端口肯定是 19_999

要找出哪个IP地址接收当前数据包,您需要了解所有网络适配器的配置,包括虚拟适配器。然后,您可以从源IP和网络配置中获取目标IP,因为只有在同一网络中的设备才能相互通信。您只需在所有网络适配器中找到与源IP在同一网络中的IP即可。您可以通过将子网掩码应用于两个IP地址,并查看结果是否相同来判断两个设备是否在同一网络中。

顺便说一下,如果您的服务器在NAT后面,则无法获取客户端的真实源IP地址,如果它与服务器不在同一网络中,则只能获得最后一跳的源IP地址(通常是您的网络网关的IP地址)。

获取网络配置

{:ok, ifaddrs} = :inet.getifaddrs()

你会得到一个看起来像这样的列表。
[
  {'lo', [addr: {127, 0, 0, 1}, netmask: {255, 0, 0, 0}, ...]},
  {'eth0', [addr: {192, 168, 0, 10}, netmask: {255, 255, 255, 0}, ...]},
  {'docker_gwbridge', [addr: {172, 18, 0, 1}, netmask: {255, 255, 0, 0}, ...]}
]

由于我们只需要addrnetmask,所以我们可以在列表上进一步进行Enum.map

ifaddrs = Enum.map(ifaddrs, fn{_, opts}-> {opts[:addr], opts[:netmask]} end)

应用网络掩码到IP地址上

将它们进行位运算与操作即可。

defp apply_netmask({a1, b1, c1, d1} = _ip, {a2, b2, c2, d2} = _netmask) do
  # Don't forget `use Bitwise`
  {
    a1 &&& a2,
    b1 &&& b2,
    c1 &&& c2,
    d1 &&& d2
  }
end

查找目标IP地址

dest_ip = ifaddrs
          |> Enum.find(fn{addr, netmask}-> 
               apply_netmask(addr, netmask) == apply_netmask(source_ip, netmask) 
          end)
          |> elem(0)

更新

如果可能的话,建议您只在一台网络适配器上打开UDP套接字。您可以使用{ifaddr, inet:socket_address()}选项来实现,例如:

{:ok, socket} = :gen_udp.open(19_999, [active: true, ifaddr: {192, 168, 0, 10}])

通过这种方式,您可以100%确定目标IP地址是192.168.0.10。此外,它还增加了一点安全性。


谢谢你的回答。我最终采用了你在更新中建议的方法。看起来使用:gen_udp无法获取伪标题。 - kuffel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接