Able to send udp traffic, but not able to receive replies

I have been experimenting with fly.io and decided to try deploying a simple Elixir app (non Phoenix) that simply echos back udp messages to the sender. I got it deployed successfully using the following set up. Logging shows that my server is receiving the udp traffic, but the clients have not been able to receive replies. I also tested the same thing, but with tcp traffic (over a non standard port other than 80 or 443) and it actually worked. I also set up port forwarding on my home router, which also allowed the udp traffic to come back to the sender, so port forwarding made the udp version work.

So, I was pretty sure the problem was due to NAT on my home router, but then the other day I deployed to a Digital Ocean droplet and it worked fine (without port forwarding on my home router). So now I am just confused. Is there some configuration that I am missing that would allow the udp traffic to make it back to the sender? Perhap the server I deployed is receiving udp traffic at one address, but sending it back out with another somehow? Note: My code replies to senders using the same ip and port, so it should not be a problem with most NAT configurations.

FROM bitwalker/alpine-elixir:latest

EXPOSE 5051/udp
EXPOSE 5051/tcp

# tar -zcvf app.tar.gz .
COPY app.tar.gz .
RUN tar -xzvf app.tar.gz

RUN export MIX_ENV=prod && \
    rm -rf _build && \
    mix deps.get && \
    mix release

CMD _build/prod/rel/udp_playground/bin/udp_playground start

app = "small-shadow-3276"

kill_signal = "SIGINT"
kill_timeout = 5

[env]
  port = 5051

[experimental]
  allowed_public_ports = ["5051"]
  auto_rollback = true

[[services]]
  internal_port = 5051
  protocol = "udp"

  [[services.ports]]
    port = 5051

1 Like

Ok, I think I’ve managed to get this to work. I saw a lot of talk about fly-global-services in the forums here, but I didn’t really understand what it was or how to use it. I ended up finding it in /etc/hosts after ssh-ing into my app.

Next I made the IP address that it refers to available in my Elixir app. I wasn’t sure the best way to accomplish this, but here’s how I ended up doing it:

In the Dockerfile:

# ...

CMD FLY_GLOBAL_SERVICES_IP="$(getent hosts fly-global-services | awk '{ print $1 }')" _build/prod/rel/udp_playground/bin/udp_playground start

In my application code:

    fly_global_services_ip =
      System.get_env("FLY_GLOBAL_SERVICES_IP")
      |> String.split(".")
      |> Enum.map(&String.to_integer(&1))
      |> List.to_tuple()
    
    # then open the socket using this special IP address instead of the default.
    :gen_udp.open(5051, [ip: fly_global_services_ip])
    # ...
   

After deploying with those changes I was able to get the echos back as I expected:

$ nc -u <instance-public-ip> 5051
yay
yay # <-- echo'd back
1 Like

Nice! Sounds like a fun project!