simple C program using bind() worked on 9.3 doesn't work on 11.4

I have distilled my porting issue down to the following C program, which used to work on 9.3 (32-bit) and doesn't work on 11.4 (32-bit) (code below).

The result is bind() giving an error Address family not supported by protocol family, or errno 47.

Here's the run of the code on 11.4:
Code:
@tape$ gcc -o s s.c
@tape$ rm -f /tmp/socketfoo; ./s
failed after bind(): 47: Address family not supported by protocol family
@tape$

and here's the output on 9.3:
Code:
@kiko$ cc -o s s.c
@kiko$ rm -f /tmp/socketfoo; ./s
s=3
@kiko$

C:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>

main()
{
    char *local_filename = "/tmp/socketfoo";
    int backlog=5;
    int s;
    int socksize;
    char *saddr;

    if ((s = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) {
    fprintf(stderr, "failed after socket()\n");
    exit(-1);
    }

    socksize = sizeof (short) + strlen(local_filename) + 1;

    saddr = (char *)malloc(socksize);

    *(short *)saddr = AF_UNIX;
    strcpy(saddr+sizeof(short), local_filename);

    if (bind(s, (struct sockaddr *)saddr, socksize) == -1) {
    fprintf(stderr, "failed after bind(): %d: %s\n",
        errno, strerror(errno));
    exit(-1);
    }

    if (listen(s,backlog) != 0) {
      close(s);
      fprintf(stderr, "failed after listen()\n");
      exit(-1);
    }

    fprintf(stderr, "s=%x\n", s);

    exit(0);
}
 
Do you have a reason to not use the correct structs for the sockaddr? Like, real real reasons?
 
Please read the unix(4) manual page to learn how to use UNIX-domain sockets correctly. You must use a struct sockaddr_un. Alternatively, refer to Stevens’ APUE chapter 15.5.2 (advanced interprocess communication, 4.3+BSD).

Also, your main() function misses a type declaration (must be int), and for the close(2) system call you need to #include <unistd.h>.
 
You should also compile with -Wall -Werror so the compiler can bash you over the head about your programming issues.
:)
 
  • Like
Reactions: a6h
Btw, we're using GCC 4.8 on the 11.4 system.
Could you let us know why you still use this outdated compiler? What's the problem with the system default compiler, clang 10?
 
It's worse than that. In a sockaddr (..._in) structure, the call expects: (a) the address family, which you did put in there, but perhaps the wrong number of bytes, I don't know whether it the length of a short or an int, (b) the port number, (c) the IP address. Instead, you are giving it an ASCII string, which will be interpreted as binary gibberish. That makes very little sense.

EDIT: Sorry, I'm wrong: For this, you actually want a pathname; for connect, you want port and IP address in a sockaddr_in. My bad.
 
It's worse than that. In a sockaddr (..._in) structure, the call expects: (a) the address family, which you did put in there, but perhaps the wrong number of bytes, I don't know whether it the length of a short or an int, (b) the port number, (c) the IP address. Instead, you are giving it an ASCII string, which will be interpreted as binary gibberish. That makes very little sense.
Because that is how UNIX domain sockets work. The actual socket is created as a special file within the filesystem:
Code:
/*                                                                                                                  
 * Definitions for UNIX IPC domain.                                                                                 
 */                                                                                                                 
struct sockaddr_un {                                                                                                
        unsigned char   sun_len;        /* sockaddr len including null */                                           
        sa_family_t     sun_family;     /* AF_UNIX */                                                               
        char    sun_path[104];          /* path name (gag) */                                                       
};
Although I admit, that the way the OP initializes the data deviates quite a bit from all known standards :p
 
It's worse than that. In a sockaddr (..._in) structure, the call expects: (a) the address family, which you did put in there, but perhaps the wrong number of bytes, I don't know whether it the length of a short or an int, (b) the port number, (c) the IP address. Instead, you are giving it an ASCII string, which will be interpreted as binary gibberish. That makes very little sense.

No, this is wrong. What he's done is sort of ok, but his use of just char instead of the structure has lead him to not see the use of sa_len.

eg:
In BSDs it's

Code:
struct sockaddr {
        unsigned char   sa_len;
        sa_family_t     sa_family;
        char            sa_data[14];
};
In Linux (and Solaris):
Code:
struct sockaddr {
               sa_family_t sa_family;
               char        sa_data[14];
};

He might also want to include <sys/un.h> and use the structure as mickey mentions.

Also note the relevant part of https://www.freebsd.org/doc/en_US.I...ers-handbook/sockets-essential-functions.html

If this is the OP's work, he's being too cute. If it isn't, then whomever he's copying it from is a naughty boy. It looks like old-skool coding.
 
Feels like ages since I last had to use stuff like that, but this compiles and runs on FreeBSD 12.1:
C:
#include <stdio.h>                                                                                                  
#include <string.h>                                                                                                 
#include <errno.h>                                                                                                  
#include <unistd.h>                                                                                                 
#include <sys/types.h>                                                                                              
#include <sys/socket.h>                                                                                             
#include <sys/un.h>                                                                                                 
                                                                                                                    
#define MYSOCKET        "/tmp/socketfoo"                                                                            
#define BACKLOG         5                                                                                           
                                                                                                                    
int main(int argc, char *argv[])                                                                                    
{                                                                                                                   
        int s;                                                                                                      
        int rv = -1;                                                                                                
                                                                                                                    
        if((s = socket(PF_UNIX, SOCK_STREAM, 0)) != -1)                                                             
        {                                                                                                           
                struct sockaddr_un sun;                                                                             
                                                                                                                    
                sun.sun_family = AF_UNIX;                                                                           
                strlcpy(sun.sun_path, MYSOCKET, sizeof(sun.sun_path));                                              
                sun.sun_len = SUN_LEN(&sun);                                                                        
                                                                                                                    
                if(!bind(s, (struct sockaddr *)&sun, sun.sun_len))                                                  
                {                                                                                                   
                        if(!(rv = listen(s, BACKLOG)))                                                              
                        {                                                                                           
                                fprintf(stderr, "SUCCESS socket=%x\n", s);                                          
                        }                                                                                           
                        else                                                                                        
                        {                                                                                           
                                fprintf(stderr, "failed to listen (%s)\n",                                          
                                        strerror(errno));                                                           
                        }                                                                                           
                }                                                                                                   
                else                                                                                                
                {                                                                                                   
                        fprintf(stderr, "failed to bind socket (%s)\n",                                             
                                strerror(errno));                                                                   
                }                                                                                                   
                                                                                                                    
                close(s);                                                                                           
                unlink(MYSOCKET);                                                                                   
        }                                                                                                           
        else                                                                                                        
        {                                                                                                           
                fprintf(stderr, "failed to create socket (%s)\n",                                                   
                        strerror(errno));                                                                           
        }                                                                                                           
                                                                                                                    
        return rv;                                                                                                  
}
 
It looks like old-skool coding.

Agreed. If there was any function with parameters i wouldn't be surprised in the slightest to see the funky old style type definitions but the implicit int return type on main (which technically isn't even used afterwards) is suspicious enough in my opinion. Trying to make assumptions about the actual data types of some system structure while also ignoring any alignment there might be is just the icing on the cake.

I think the code should be rewritten (at this size it's really not to much work) in a more clean way if one wants it to survive the next couple decades. Kinda like what mickey did above.
 
You are all right, not using the official structures is bad. This code is literally decades old and hasn't been touched since it was written.

The real issue is that even if I used the real structures in FreeBSD 9.3, that binary wouldn't work on 11.3, because the "sun_len" slot was added to the beginning of sockaddr_un. That was unfortunate, as it breaks binary compatibility, no?

As to why we use GCC 4.8... we haven't groomed our C code for clang 10. Originally I did build with it, but had too many problems.

Also, the C code in our system is minimal. The rest is common lisp. The C bits are just for glue between system libraries and CL code.

I appreciate everyone's help on this. Thanks.
 
You are all right, not using the official structures is bad. This code is literally decades old and hasn't been touched since it was written.

The real issue is that even if I used the real structures in FreeBSD 9.3, that binary wouldn't work on 11.3, because the "sun_len" slot was added to the beginning of sockaddr_un. That was unfortunate, as it breaks binary compatibility, no?
No, the binary would still work on 11.3 (if it was programmed correctly, of course), because the COMPAT_FREEBSD* options in the kernel provide compatibility syscalls for old binaries. These syscalls translate the older structs to newer ones and then call the newer syscalls.
 
  • Like
Reactions: dkl
As to why we use GCC 4.8... we haven't groomed our C code for clang 10. Originally I did build with it, but had too many problems.
I once was in a project where the way to do things (TWTDT) was like that. Once we went -Wall -Werror -Wextra ... and used clangs build-scan plus valgrind, we had 3 months of work to do. After that (and one subcontrcting company fired for incompetence) we suddenly had a pretty stable software. It is well worth the extra mile, believe me.
 
  • Like
Reactions: dkl
I once was in a project where the way to do things (TWTDT) was like that. Once we went -Wall -Werror -Wextra ... and used clangs build-scan plus valgrind, we had 3 months of work to do. After that (and one subcontrcting company fired for incompetence) we suddenly had a pretty stable software. It is well worth the extra mile, believe me.

Absolutely. Teaching people to write clean code is something C is very good at. It simply doesn't forgive laziness and makes you suffer for not paying attention to detail. Everyone gets the drill sooner or later.
 
You are all right, not using the official structures is bad. This code is literally decades old and hasn't been touched since it was written...
As to why we use GCC 4.8... we haven't groomed our C code for clang 10. Originally I did build with it, but had too many problems...
Technical debt incurred.

I once was in a project where the way to do things (TWTDT) was like that. Once we went -Wall -Werror -Wextra ... and used clangs build-scan plus valgrind, we had 3 months of work to do. After that (and one subcontrcting company fired for incompetence) we suddenly had a pretty stable software. It is well worth the extra mile, believe me.
Technical debt paid.

Technical debt foreclosed is left as an exercise for the reader.
 
This meme needs to die. C is not a very principled language at all, starting with the "undefined behavior" concept.

I don't see a contradiction there. Quite the opposite. Undefined behavior is just another concept you have to be aware of. Either that or be ignorant and pay the price and that price is what results in writing more and more clean code over time. It reinforces the concept of taking responsibility for your code because the compiler will happily let you do really stupid stuff. It's you who has to think about it and how to not do it.

The only situation i can think of where undefined behavior is semi problematic is strict aliasing which to some extend can be avoided using unions but i know that's often annoying or not really practical. Still when you are aware of the implications it's not much of a problem. It's just when you think you could ignore it that it might bite you.
 
Undefined behavior is just another concept you have to be aware of. Either that or be ignorant and pay the price and that price is what results in writing more and more clean code over time. It reinforces the concept of taking responsibility for your code because the compiler will happily let you do really stupid stuff. It's you who has to think about it and how to not do it.

You are implicitly assuming that:
  1. the person originally writing the code is the person dealing with the consequences (this isn't true for OP at least);
  2. the code is broken to an extent that programmers are forced to acknowledge and address the issues.
Undefined behavior can be quite benign until it suddenly isn't. For example, see this case of use after deinitialization: https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=248225. Compare this with Rust where destroying/freeing something twice wouldn't be possible (without jumping through the hoops).
 
the person originally writing code is the person dealing with the consequences (this isn't true for OP at least).

You have a point there. It only works when the person actually has to maintain the code they wrote but unless they manage to have it work fine for some decades it will come back to haunt them one way or the other. At the size the example consequences would be minor anyways as it's so tiny and complexity is so low the error almost spots itself. Try that with an off by one/overflow/uninitialized variable in a messy 100k loc source of some hard to predict application. Very different outcome.

the code is broken to an extent that programmers are forced to acknowledge and address the issues.

Well, it's either broken or it isn't. If the error doesn't have a noticeable effect i'd say there is no problem to begin with.

Undefined behavior can be quite benign until it suddenly isn't. For example, see this case of use after deinitialization: https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=248225. Compare this with Rust where destroying/freeing something twice wouldn't be possible at all (without jumping through the hoops).

How is some function having a certain set of requirements undefined behavior? At best one could complain that it's only documented as not being thread safe while also calling it multiple times from the same thread is not going to work. The fact that it might work if some obscure condition is met doesn't change that. Besides the way some library is written has nothing to do with the language itself. It either comes down to the function being badly documented or the implementation being faulty.

Edit: I should have read the notes section on the pthread_join documentation. Well, that changes things a bit. It's actually also documented that calling it multiple times from the same thread is not guaranteed to work. It's all on the person who wrote the crashing application then. This is no different from doing a double free and deciding it's fine just because it works in the current environment. I wonder what their logic looks like anyways when it's possible for it to join the threads multiple times. The real problem is probably way bigger.

Regarding to possibility to shoot yourself in the foot with a double free: Well, sure it would be nice if that wasn't possible but it is and therefore has to be taken into account. Also if it wasn't there would have to be some internal magic to prevent it. Now this magic isn't free of charge but there will be cases where you know it isn't needed but the compiler can't deduct it so your final binary contains pointless checks or some other kind of limitation that makes things like that impossible. If you write C you are the one writing the checks, well, or you don't. It's up to you. If you don't like that then some other language (maybe Rust as you suggest?) might be better suited for you.
 
How is some function having a certain set of requirements undefined behavior?

Strictly speaking, this an API contract violation. However, since use-after-free as often referred to in that manner*, I don't think this is much of a stretch either. In other words, I'm just being slightly lazy. This doesn't make the issue any less real.

* From the OpenBSD man pages, https://man.openbsd.org/free.3:
If ptr was previously freed by free() or a reallocation function, the behavior is undefined and the double free is a security concern.


If you don't like that then some other language (maybe Rust as you suggest?) might be better suited for you.

Who cares about me? I don't even have source code access for that shit. (Which, wait for it, technically means that all behavior is well defined, since everything is already compiled.)
 
Back
Top