Elixir/Erlang :gen_udp 伪 header

Elixir/Erlang :gen_udp pseudo header

我想在 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 伪 header 的一部分。当我使用 tcpdump 时,我可以看到数据包的目标 IP。

有没有way/parameter获取UDP包的IP或者Pseudoheader?

目标端口肯定是19_999

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

顺便说一下,如果你的服务器在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。此外,它还增加了一点安全性。