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}, ...]}
]
因为我们只需要 addr
和 netmask
,我们可以在列表上进一步 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。此外,它还增加了一点安全性。
我想在 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}, ...]}
]
因为我们只需要 addr
和 netmask
,我们可以在列表上进一步 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。此外,它还增加了一点安全性。