Elixir/Erlang:gen_udp伪标头

b5lpy0ml  于 2022-12-08  发布在  Erlang
关注(0)|答案(1)|浏览(152)

I want to receive UDP messages in Elixir and need to get the destination IP where the packets have been sent to.
In iex i can open a socket with this command:

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

When i receive a message i will get it in the following format:

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

The destination IP is not part of the message. According to UDP specification the source IP is not part of the UDP packet, but it is part of the IP pseudo header. When i use tcpdump i can see the target IPs of the packets.
Is there any way/parameter to get the IP or Pseudo header of the UDP packet?

8hhllhi2

8hhllhi21#

The destination port is 19_999 for sure.
To figure out which IP is receiving the current packet, you need to know the configuration of all your network adapters, including the virtual ones. After that, you can get the destination IP from the source IP and network configuration because only the devices in the same network can talk to each other. You just need to find the IP that is in the same network as the source IP, among all the network adapters. You can judge whether 2 devices are in the same network by applying the netmask to the 2 IP addresses, and see if the results are the same.
By the way, if your server is behind NAT, you can't get the real source IP address of the client if it's not in the same network as the server. You only get the source IP address of the last hop (which is usually the IP address of your network gateway).

Getting the network configuration

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

You'll get a list that looks like

[
  {'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}, ...]}
]

Since we only need the addr and the netmask , we can just do a further Enum.map on the list:

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

Apply a netmask to an IP address

Just bitwise and them together.

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

Find the destination IP

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

UPDATE

If possible I suggest that you open the UDP socket on only one of your network adapters. You can use the {ifaddr, inet:socket_address()} option to achieve that, for example:

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

In this way, you can 100% be sure that the destination IP is 192.168.0.10. Besides, it also adds a little bit of security.

相关问题