BSD Sockets Addressing
idea.popcount.org
idea.popcount.org
10.1 == 10.0.0.1
192.168.1 == 192.168.0.1
While it has practically no use in today's CIDR [1] internet, it's still a useful shortcut for home-networks, etc (I have my home network configured as 10.0/24) $ ping 10.1
PING 10.1 (10.0.0.1) 56(84) bytes of data.
64 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=0.181 ms
I've also recently stumbled on some software that no longer parses this address form. Can't remember where though.[1] https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing
https://tools.ietf.org/html/rfc2553#page-31
https://pubs.opengroup.org/onlinepubs/9699919799.2018edition...
Anything that continues to support the ancient syntax is in serious need of fixing with extreme prejudice.
So for webapp development, http://0:3333 is about as convenient a URL you can get, except it doesn't work in Chrome
Is there a problem with localhost:3333 (or whatever)?
(1) Creating sockets
(2) Addressing
soon to come
(3) AF_INET6 archeology: scope id and flowinfo
(4) bind/connect
I'm trying to build an audience, so make sure to follow me on twitter https://twitter.com/majek04
(I don't like harvesting emails, so opted for twitter)
Food for extra thought I suppose; there's probably a lot of knowledge and trivia to be gleaned from the historical evolution of the interfaces, and I'd imagine you're in a better position to actually sink your fangs into this.
Incidentally, your post is time travelling and claims being from Dec 6, 2019.
I also don't like abstract sockets at all. They're vulnerable to squatting attacks. The nice thing about non-abstract unix sockets is that they take advantage of the filesystem permissions that everyone already understands. When possible, I prefer putting things into a single namespace.
Unix sockets (of the non-abstract sort) also need a way to atomically unlink an existing socket and bind in its place. Doing that as two operations is pretty common, but it's racy.
I agree, it's not pretty. Some unixes have an extremely short limit, so short you can't really use absolute paths portably unless in somewhere like /tmp.
But there is a workaround.
The path doesn't have to be absolute, so if you chdir(2) first and use a relative name with bind() and connect(); that gets you a socket in any directory.
(See also bindat() and connectat() in FreeBSD).
If you don't want to chdir(), you can make a temporary symbolic link in /tmp to your directory of choice for bind(), and directly to the socket for connect().
If the last path component is too long for bind(), renaming afterwards may work (or hard linking on the same filesystem then unlinking).
All the above assumes that AF_UNIX sockets are indexed by the inode on the filesystem, so different paths, rename etc work. I'm not sure if that is true on every old and weird kernel, if you're going for high portability.
To atomically unlink an existing socket and bind another in place, without races, you should be able to first bind a new socket to a temporary name in the same directory, and then use rename(2) to atomically replace the existing name.
There may be queued connections that need accept(2) in the old socket after the rename, but that's not a race condition.
If you want the rebind to fail only when the socket is bound by another process, it may be the other process is about to close the socket, and your atomic-rebind would result in the socket not bound by either process.
That outcome is identical to the outcome from the available method, where you attempt connect() first and only bind-and-rename if the connect() fails with an appropriate error. When there's no race, you always get the desired outcome of the socket bound by exactly one process, but if the processes are racing, the result can be the socket not bound by either process.
Because both methods produce the same outcome in the race case, and the only difference is an unobservable difference in each process' logical clocks (you can't tell which events really happened first), both methods actually have the same race properties.
If you want to always end up with exactly one server running, and to always avoid taking away the socket from an existing server which is running just fine, I think you need to involve some protocol. You can try connect() and then if it succeeds, send a request "are you shutting down?". An appropriate type of connect() error or "yes" means it's safe to bind a new socket and rename over, otherwise leave the socket alone.
Abstract sockets have no permissions. Not having to deal with the filesystem at all is nice, but having absolutely no security model whatsoever also seems dangerous.
You have the ugly issue of having to deal with handling zeroes in your name, as noted in the article. This can and will break something, somewhere on the first attempt of writing it.
When you want microkernel IPC, you usually tend to want a message-oriented reply-and-receive primitive to main loop around them. Emulating this with sockets is error-prone since send(2) and recv(2) make extremely weak promises about their behavior on error. sendmsg(2) and recvmsg(2), which are necessary to pass kernel objects around (i.e. file descriptors), are very difficult to use.
Sockets are nice because you can select(2) etc. on them, however. I'd expect a replacement would interoperate smoothly with at least those system calls.
If you want object IPC with a reply and receive operation, you can use Android's binder, which is already in mainline.
This sounds interesting, and as a regular Linux dev, I’ve never heard of it. Is there a tutorial for this that doesn’t assume you’re coming from an Android background?
There is no limit except the typical file path limit. The kernel does support longer paths. It will obey the third, socklen_t, parameter to both connect(2) and bind(2). The size of .sun_path from the kernel's perspective extends to the end of the sockaddr structure declared by the socklen_t parameter.
This applies not only to Linux but all Unix-like systems except, IIRC, Minix.
The flip side is that you have to check the return values of accept(2), getsockname(2), and getpeername(2) for truncation. The full path of the socket may not fit in the size of the structure you pass.
TCP and UDP port numbers are the same. Anybody can bind any port number >= 1024, which blocks another server from binding that port.
OTOH it isn't really hard to simulate the same with win32 named pipes.
Which means everyone who needs their client to support connecting to an RPC server over both AF_INET/6 sockets and AF_UN sockets, needs to invent some custom metaformat above URLs for specifying that they want a connection through a Unix socket path. Kind of ridiculous, really.
To do what you wanted it would require to redesign the whole networking. Named Data Networking[1] (NDN) which unlike TCP/IP addresses data would be able to support URLs like that natively.
Or do you want the same URI scheme to support all your different transport protocols? It kind of feels like missing the point of URIs, after all the whole idea is that your application can interchangeably handle different kinds of URIs for different use cases.
This is somewhat, but not perfectly, analogous to how 9P mounts work in Plan9. In both models, you start off with your local namespace, which has servers mounted on it like /mnt/web or /mnt/ftp. These are the "schemes" available on the initial resolver object. You then build a path to a virtual file within one of these mountpoints (/net/tcp, say), where that path turns out to point to a resource that describes (in a format another 9P server understands) how to create another mount representing that connection.
The difference between the two models is that, with 9P, you'd have to take this descriptor file and mount it yourself; whereas in my model, you wouldn't see a descriptor file, but rather another already-mounted server, with its own schemes as its root-level exposed virtual directories.
Pretending 9P worked this way, you'd be able to have a namespace path that looks like, e.g.:
/net/tcp/foo.com/80/http/patha/pathb/a=b/c=d
Back in URL land, this kind of path would correspond to a "meta-URL" that looks like e.g.: tcp:foo.com:80#http:/patha/pathb?a=b&c=d
where the "#" symbol now takes on new significance as a command for the URL resolver, to address the next part of the path to the resolver-object discovered by resolving everything on the meta-URL so far. (Which perfectly subsumes #'s current use, since browsers interpret a # as identifying a particular DOM-tree node within a document—i.e. as using the resource acquired from the previous URL-step as a resolver to find the next object.)You could do all sorts of crazy things with this, too, like creating URLs that represent files available within archives that exist as HTTP streams, e.g.:
tcp:/foo.com/80#http:/foo/bar.tar#file:/index.html
Or adding DNS or TLS arbitrarily into protocol stacks that weren't designed for them: ip:/dns/foo.com#tcp#tls#irc:%23room