charlie-foxtrot / RTLSDR-Airband

Multichannel AM/NFM demodulator
GNU General Public License v3.0
733 stars 133 forks source link

[BUG]udp stream packet size too large for end-to-end route #400

Open alecmyers opened 11 months ago

alecmyers commented 11 months ago

symptom: UDP stream not received at remote host. TCP dump shows:

10:21:56.442031 IP radiostream.59703 > xx.xx.xx.xx.XX: UDP, length 4000 10:21:56.596971 IP radiostream.59703 > xx.xx.xx.xx.XX: UDP, length 4000

MTU for ethernet interface is (of course) 1500, therefore externally routed UDP packets are (may be) silently discarded instead of fragmented.

MTU for loopback interface is 65536 - so (guessing) the packet size of 4000 is set internally? Data is received on loopback interface.

Can I suggest a new option for udp_stream of "packet_size"?

config follows:

devices: ({ type = "rtlsdr"; index = 0; gain = 45; centerfreq = 118.15; correction = 0; sample_rate = 1.0; channels: ( { freq = 118.2; lowpass = 5000; highpass = 200; squelch_threshold = -44; outputs: ( { type = "icecast"; server = "127.0.0.1"; port = 8000; mountpoint = "xxx.mp3"; name = "xxx"; genre = "ATC"; username = "xxx"; password = "xxx"; }, { type="file"; directory="/opt/audio"; filename_template="xxx"; continuous=false; include_freq=false; append=true; }, { type = "udp_stream"; dest_address = "xxx.xxx.com"; dest_port = 8123; continuous = true; }, { type = "udp_stream"; dest_address = "127.0.0.1"; dest_port = 9001; continuous = true; } ); } ); } );

alecmyers commented 11 months ago

Following further research, I think the title of this issue isn't accurate - the kernel should (and perhaps does) fragment IP packets to match the mtu, but there's no path mtu negotiation available here end-to-end and hosts are permitted silently to drop udp datagrams longer than 576 bytes.

As a nasty hack I changed rtl_airband.h: #define WAVE_BATCH WAVE_RATE / 32 //was 8

outgoing UDP packets are now 1000 bytes long and reach their destination correctly.

So although it's not a defect in rtl_airband per-se, a configurable udp packet size would still be helpful.

charlie-foxtrot commented 11 months ago

This is odd, I would expect the network stack to segment as necessary . . .

Is there something special / unique about your setup, or would you anticipate everyone using udp off of localhost to run into this?

I'm reluctant to put in a configuration for MTU, but, if this is something that everyone not using localhost is going to see, perhaps some different logic based on the destination.

Could you try modifying udp_stream_write() to call sendto() multiple times, each with chunks of 500 bytes (because that evenly divides the write) and see if that resolves your issue? If so then maybe something like "if destination isn't localhost then chop outputting writes into 500 byte chunks"

alecmyers commented 11 months ago

I don't think there's anything particularly unusual about my setup: the source is running on a commercial broadband link, the destination is a cloud virtual server; there are various firewalls in between, but only what is usual for such a set up.

I did a manual MTU discovery with this at the destination: nc -ulp 8123

and this at the source: printf 'x%.0s' {1..1412} > /dev/udp/xxx.xxx.com/8123

The largest packet that arrives at the destination is a payload of 1412 (as shown) - increase to 1413 and the packet is dropped.

would you anticipate everyone using udp off of localhost to run into this?

With a source and destination on the same subnet this would be down to the physical layer MTU and fragmentation policy of the sending host, so likely not a problem. For streaming UDP packets over a routed connection and particularly for via the Internet, where various tunnelling and VPN policies could be in place, without an MTU discovery process I think it would be wiser to stick to short UDP packets.

Could you try modifying udp_stream_write() to call sendto()....

Will do.

donwade commented 11 months ago

I’m surprised that any network isp would allow jumbo packets without special provisioning and if your first hop is thru wifi, that will probably enforce fragmentation on your connections endp  unless you mod the wifi firmware.On Aug 6, 2023, at 11:58 AM, alecmyers @.***> wrote: I don't think there's anything particularly unusual about my setup: the source is running on a commercial broadband link, the destination is a cloud virtual server; there are various firewalls in between, but only what is usual for such a set up. I did a manual MTU discovery with this at the destination: nc -ulp 8123 and this at the source: printf 'x%.0s' {1..1412} > /dev/udp/xxx.xxx.com/8123 The largest packet that arrives at the destination is a payload of 1412 (as shown) - increase to 1413 and the packet is dropped.

would you anticipate everyone using udp off of localhost to run into this?

With a source and destination on the same subnet this would be down to the physical layer MTU and fragmentation policy of the sending host, so likely not a problem. For streaming UDP packets over a routed connection and particularly for via the Internet, where various tunnelling and VPN policies could be in place, without an MTU discovery process I think it would be wiser to stick to short UDP packets.

Could you try modifying udp_stream_write() to call sendto()....

Will do.

—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you are subscribed to this thread.Message ID: @.***>

alecmyers commented 11 months ago

In theory, there's nothing wrong with a 4k UDP payload. Per RFC791, any IP datagram (including UDP) can be up to 64k long. Unless the 'do not fragment' bit is set, intermediate hosts can fragment packets, but they are not required to. RFC791 also says "It is recommended that hosts only send datagrams larger than 576 octets if they have assurance that the destination is prepared to accept the larger datagrams."

I believe under IPv6, only the source is permitted to fragment datagrams. In which case an intermediate hop with an MTU less than that of the source network will cause the data to be silently discarded, therefore only a datagram of 576 bytes or fewer is safe.

83Lima commented 7 months ago

Newbie here. I second the request for a packet size option. I am trying to use VLC to listen to the UDP stream of my local airport before becoming a source to LiveATC and RadarBox. My source, a Raspberry Pi4 model B, is fragmenting the packets to its outgoing MTU of 1500, the receiving client listening to the stream (MacOS on the same LAN) reassembles the packets to 4000 bytes (mono) or 8000 bytes (stereo), then passes the payload up to VLC. There, the data is dropped because VLC 3.0 and later only supports up to 1316 bytes.

I tried implementing the suggestion of chunking the data inside of udp_stream_write() by calling sendto() repeatedly, but I'm getting choppy audio and only seeing about 2000 bytes out of an 8000 byte payload. The generated packets do have a payload of 500 bytes as expected. I also tried 1000 bytes but with the same result.

Here's what I did, but keep in mind I am NOT a programmer, let alone know much about C++

void udp_stream_write(udp_stream_data *sdata, const float *data, size_t len) {
        if (sdata->send_socket != -1) {
                for (size_t i = 0; i < len/500; ++i) {
                        // Send without blocking or checking for success
                        sendto(sdata->send_socket, data + i*500, 500, MSG_DONTWAIT | MSG_NOSIGNAL, &sdata->dest_sockaddr, sdata->dest_sockaddr_len);
                }
        }
}