* Port 0 and DDoS: This is usually just a measurement artifact. Reflection-Amplification attacks create UDP packets larger than the current MTU, which leads to fragmentation and hence packet fragments without a UDP header -- however they are still identified as UDP because the ip.proto field is still 17, i.e. UDP). Interpreting such packets often leads to "port 0" instead of "port None", eg in IPFIX.
* Port 0 in the actual traffic. This has to be done via RAW sockets, since this usually signals the OS to use ANY port (especially if binding). This technique has been used ages ago (20 years? see Gobbler tool) to fingerprint operating systems, because they all responded differently to such packets.
> When you open a listening socket on a host and request port 0 from the operating system, this call is typically interpreted as a request to return a single, currently unused port to the application
But... why? Surely some kind of separate BIND_UNUSED_PORT operation would have been fine, without needlessly overloading the meaning of "port 0".
Having a specific port number to mean "pick a random unused port" is very useful when you run any application that opens a listening TCP socket and takes the port number from the config. More often than not, the default behaviour (when the port is not specified) is to use some hardcoded port number like 5555 or something. But what if you want to run two/three/many copies of this app, dynamically, without them interefering with each other? One possibility is to do manual free port detection--but it's inherently racy. Another one is to specify port 0 and get the actual port number from the application by some side channel (say, by reading it from stderr/log file).
Now, I assure you that if this "some kind of separate BIND_UNUSED_PORT" operation was available in the Berkeley socket API from the start, it would be mostly unused, people would've just used normal bind() instead (it's way simpler), so the latter option simply becomes impossible: you have to manually pick the free ports for temporary servers. In fact, there are some existing applications that actively refuse to listen on port zero, for example, "ssh -D 0" doesn't work for some reason (patching it to remove the zero check breaks nothing, everything keeps working properly), and they're somewhat annoying to use transiently.
I would have just made sin_port be a 32 bit number, with UNUSED_PORT being some value in the otherwise invalid range, and the others giving you EINVAL on bind(3).
Or you could spare those 2 bytes at a cost of being unable to listen on one out of 65536 ports. In 1983, this tradeoff absolutely made sense: 64K ports should be enough for everyone.
Even in 1983, the kinds of machines expecting to be networked with IP could spare the occasional couple bytes here and there if it meant being able to signal out of band information to the stack without carving out a set of the wire formats as unencodable.
Yeah, people would just write the second version. And mind you, the sockaddr argument for bind_unused() still would have a local address to bind to, so what exactly is gained? Difference between 65535 or 65536 ports available doesn't warrant complicating API in such a manner, IMO.
They could have done something like that, but then you need to pass more than a 16-bit value in for port number. Back when that was a concern, limiting to only 65535 usable ports wasn't a big deal. Now, you probably could use a larger integer and pass a flag to really bind to port zero, other than there's probably a lot of network equipment that will drop it. If you also run on an IPv4 address with a final octet of 0 or 255, you'll have a really hard to reach service.
I’m surprised port zero packets aren’t just randomly dropped by various internet infrastructure devices. Perhaps they’ve been burned by reserved things suddenly becoming used.
* Port 0 and DDoS: This is usually just a measurement artifact. Reflection-Amplification attacks create UDP packets larger than the current MTU, which leads to fragmentation and hence packet fragments without a UDP header -- however they are still identified as UDP because the ip.proto field is still 17, i.e. UDP). Interpreting such packets often leads to "port 0" instead of "port None", eg in IPFIX.
* Port 0 in the actual traffic. This has to be done via RAW sockets, since this usually signals the OS to use ANY port (especially if binding). This technique has been used ages ago (20 years? see Gobbler tool) to fingerprint operating systems, because they all responded differently to such packets.