Fixing BSD sockets
Posted: Sat Oct 24, 2020 2:46 am
Hi all,
it seems established at this point that BSD sockets aren't well designed. They don't fit too well into UNIX, requiring their own system calls to handle even reading and writing, because read() and write() are aren't powerful enough to handle everything recv() and send() can do. ioctl() is a wart on all UNIX FD communication, but sockets don't improve things by not only participating in that mess, but adding setsockopt() and getsockopt() into the mix. And the interface on the C level is an attempt at object-oriented programming in a language not designed for it, and therefore is error-prone to the last. I mean, basically struct sockaddr is meant to be an abstract base class, from which all the actual socket addresses are derived. The abstraction was so leaky, they had to introduce struct sockaddr_storage as another abstract socket address that is large enough to handle all socket address types the system has to offer.
So I was wondering (and the search function didn't turn up anything useful) if anyone had made an attempt at fixing the things. My problem is I want my OS to be POSIX compatible, and for that the POSIX library must at least be implementable on the system calls my OS will provide. Ideally the syscall layer would be something sane, and then the POSIX library would provide compatibility.
My problems with BSD sockets:
Anyway, as far as attempts to fix it go, I have seen dial() on Plan9, which essentially takes a string description of what the user wants and returns a suitable socket. My problem is that it is possible to implement dial() on BSD sockets, but the other way round would be pretty messy. Although I do like the cleanliness of the design, if I did add it to the kernel. Problem is, I would have to add a string parser to the kernel (I mean, another one, after the path name reader). Not the end of the world, tho.
Microsoft added their own spin on it with their *Ex() functions (like AcceptEx()), but those don't fix the API, only extend it. Myself, I thought of making the calls less general (e.g. not having a socket() call, but socket_tcp4(), socket_tcp6(), etc., and replacing connect() with connect_tcp4(), or connect_tcp6()). SInce each of those could have their own signatures, it would not be necessary to even have a struct sockaddr, you could just give address and port directly as arguments to connect_tcp4(). A compatibility library with POSIX would be messy, but feasible. But all of this only tackles points 1, 2, and 4, leaving out the 3, and I have no idea what to do there. read() is pretty much required for other things (although I though of making preadv() the system call, and letting the other ones be compatibility functions. But then, not every FD is seekable, and I don't know if I can put the burden of tracking the file position onto the POSIX library, or if something internal would break), but what to do about the multitude of other functions?
Anyway, what are your thoughts on these issues? Do you have any other solutions? Completely ridding the system of BSD sockets is only allowed, though, if BSD sockets can still be implemented on top of your proposal. I simply don't have the time to rewrite all networking code I would like to run on my system in terms of a different API.
it seems established at this point that BSD sockets aren't well designed. They don't fit too well into UNIX, requiring their own system calls to handle even reading and writing, because read() and write() are aren't powerful enough to handle everything recv() and send() can do. ioctl() is a wart on all UNIX FD communication, but sockets don't improve things by not only participating in that mess, but adding setsockopt() and getsockopt() into the mix. And the interface on the C level is an attempt at object-oriented programming in a language not designed for it, and therefore is error-prone to the last. I mean, basically struct sockaddr is meant to be an abstract base class, from which all the actual socket addresses are derived. The abstraction was so leaky, they had to introduce struct sockaddr_storage as another abstract socket address that is large enough to handle all socket address types the system has to offer.
So I was wondering (and the search function didn't turn up anything useful) if anyone had made an attempt at fixing the things. My problem is I want my OS to be POSIX compatible, and for that the POSIX library must at least be implementable on the system calls my OS will provide. Ideally the syscall layer would be something sane, and then the POSIX library would provide compatibility.
My problems with BSD sockets:
- It is overly general. Is there ever any need to bind() active TCP sockets? And what the hell does listen() do?
- Common operations take too many steps. Opening an active TCP socket requires a call to socket() and connect(). Opening a passive one requires a call to socket(), bind(), listen(), and accept().
- Too many too similar functions. Is there a need for read(), recv(), recvfrom(), and recvmsg()?
- struct sockaddr. In its entirety. From all the pointer conversions down to the fact that someone thought it would be a good idea to bring byte order into the mix.
Anyway, as far as attempts to fix it go, I have seen dial() on Plan9, which essentially takes a string description of what the user wants and returns a suitable socket. My problem is that it is possible to implement dial() on BSD sockets, but the other way round would be pretty messy. Although I do like the cleanliness of the design, if I did add it to the kernel. Problem is, I would have to add a string parser to the kernel (I mean, another one, after the path name reader). Not the end of the world, tho.
Microsoft added their own spin on it with their *Ex() functions (like AcceptEx()), but those don't fix the API, only extend it. Myself, I thought of making the calls less general (e.g. not having a socket() call, but socket_tcp4(), socket_tcp6(), etc., and replacing connect() with connect_tcp4(), or connect_tcp6()). SInce each of those could have their own signatures, it would not be necessary to even have a struct sockaddr, you could just give address and port directly as arguments to connect_tcp4(). A compatibility library with POSIX would be messy, but feasible. But all of this only tackles points 1, 2, and 4, leaving out the 3, and I have no idea what to do there. read() is pretty much required for other things (although I though of making preadv() the system call, and letting the other ones be compatibility functions. But then, not every FD is seekable, and I don't know if I can put the burden of tracking the file position onto the POSIX library, or if something internal would break), but what to do about the multitude of other functions?
Anyway, what are your thoughts on these issues? Do you have any other solutions? Completely ridding the system of BSD sockets is only allowed, though, if BSD sockets can still be implemented on top of your proposal. I simply don't have the time to rewrite all networking code I would like to run on my system in terms of a different API.