11 December 2002

    About two weeks ago I gave a talk about my shell based DNS server shdns. At the time, it couldn't reply to traffic because of an unknown error. Tridge suggested that it was because the UDP socket was "disconnected" (i.e. has no specific end point), and therefore my shell script couldn't use write to reply to traffic.

    Well, I finally got some time to look at this, and, as ever, Tridge is right. Here's the sample programs I used to investigate this:
    // Disconnected UDP socket example: this example simply reads from clients (there can be more 
    // than one), and returns what they said straight back to them. You'll note that we can't use 
    // read and write to get to the traffic, as this is not available for disconnected UDP sockets.
    
    #include <stdio.h>
    #include <errno.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    
    int main(int argc, char *argv[]){
      int lfd;
      struct sockaddr_in servaddr;
      struct sockaddr clientaddr;
      char buf[1024];
      size_t len;
      socklen_t clen;
      
      // We will listen with this file descriptor
      if((lfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0){
        fprintf(stderr, "Error whilst starting to listen\n");
        exit(42);
      }
    
      // Define what we are listening for
      bzero(&servaddr, sizeof(servaddr));
      servaddr.sin_family = AF_INET;
      servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
      servaddr.sin_port = htons(1234);
    
      // Bind to the address
      if(bind(lfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0){
        perror("Couldn't bind");
        exit(42);
      }
      
      // Do stuff
      while(1){
        len = 1024;
        printf("Reading...\n");
        clen = sizeof(clientaddr);
        if((len = recvfrom(lfd, buf, len, 0, (struct sockaddr *) &clientaddr, 
    		       &clen)) < 0){
          perror("Socket read error");
          exit(42);
        }
        if(len == 0) break;
    
        // The buffer is not null terminated
        buf[len] = '\0';
        printf("Read: %s\n", buf);
    
        // And send it straight back
        if(sendto(lfd, buf, len, 0, &clientaddr, clen) < 0){
          perror("Socket write error");
          exit(42);
        }
      }
    }
    
    This one above is the standard socket example. It just works, because we can use the socket specific read and write calls for the data.
    // Disconnected UDP socket example: this example simply waits fro traffic, and the starts
    // a process to deal with the results. One process per packet, one packet per process.
    // This version wont work, because the socket is not connected. In fact, cat is smart enough
    // to warn us about this:
    //
    //           cat: write error: Transport endpoint is not connected
    
    #include <stdio.h>
    #include <errno.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/poll.h>
    #include <netinet/in.h>
    
    int main(int argc, char *argv[]){
      int lfd;
      struct sockaddr_in servaddr;
      struct pollfd pfd;
      
      // We will listen with this file descriptor
      if((lfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0){
        fprintf(stderr, "Error whilst starting to listen\n");
        exit(42);
      }
    
      // Define what we are listening for
      bzero(&servaddr, sizeof(servaddr));
      servaddr.sin_family = AF_INET;
      servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
      servaddr.sin_port = htons(1234);
    
      // Bind to the address
      if(bind(lfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0){
        perror("Couldn't bind");
        exit(42);
      }
    
      // Setup the list of file descriptors we want to wait for events on
      pfd.fd = lfd;
      pfd.events = POLLIN | POLLPRI;
      
      // Do stuff
      while(1){
        if(poll(&pfd, 1, -1) < 0){
          perror("Waiting for new data failled");
          exit(42);
        }
    
        printf("Data arrived\n");
    
        // Spawn a child to handle this packet
        switch(fork()){
        case -1:
          perror("Couldn't spawn child to handle connection");
          exit(42);
          
        case 0:
          // Child process -- setup the file descriptors, and the run the helper application
          dup2(lfd, 0);
          dup2(lfd, 1);
    
          execl("/bin/cat", "cat", NULL);
          perror("Exec failled");
          exit(42);
          break;
    
        default:
          // Parent process
          break;
        }
      }
    }
    
    This one is the exec version of the first example. This is basically exactly what inetd and xinetd do. It doesn't work, because cat doesn't know that it has to use the socket specific read and write, instead of the normal ones.
    // Connected UDP socket example: this example simply reads from clients (there can be more 
    // than one), and returns what they said straight back to them. You'll note that we can now use 
    // read and write to get to the traffic...
    
    #include <stdio.h>
    #include <errno.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    
    int main(int argc, char *argv[]){
      int lfd;
      struct sockaddr_in servaddr;
      struct sockaddr clientaddr;
      char buf[1024];
      size_t len;
      socklen_t clen;
      
      // We will listen with this file descriptor
      if((lfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0){
        fprintf(stderr, "Error whilst starting to listen\n");
        exit(42);
      }
    
      // Define what we are listening for
      bzero(&servaddr, sizeof(servaddr));
      servaddr.sin_family = AF_INET;
      servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
      servaddr.sin_port = htons(1234);
    
      // Bind to the address
      if(bind(lfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0){
        perror("Couldn't bind");
        exit(42);
      }
      
      // Do stuff
      while(1){
        // We need to peek at the first part of the packet to determine who to connect to
        len = 1;
        printf("Reading...\n");
        clen = sizeof(clientaddr);
        if((len = recvfrom(lfd, buf, len, MSG_PEEK, (struct sockaddr *) &clientaddr, 
    		       &clen)) < 0){
          perror("Socket peek error");
          exit(42);
        }
        if(len == 0) break;
    
        // Connect
        if(connect(lfd, &clientaddr, clen) < 0){
          perror("Could not connect");
          exit(42);
        }
    
        // And now we can just use the normal read and write
        len = 1024;
        if((len = read(lfd, buf, len)) < 0){
          perror("Socket read error");
          exit(42);
        }
        if(write(lfd, buf, len) < 0){
          perror("Socket write error");
          exit(42);
        }
      }
    }
    
    Here's an example of just the socket code, but connected, so that we can just use the read and write functions on the file descriptor (as if it was a file).
    // Connected UDP socket example: this example this example simply waits fro traffic, and the 
    // starts a process to deal with the results. One process per packet, one packet per process. 
    // You'll note that we can now use read and write to get to the traffic, and that this all
    // works...
    
    #include <stdio.h>
    #include <errno.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    
    int main(int argc, char *argv[]){
      int lfd;
      struct sockaddr_in servaddr;
      struct sockaddr clientaddr;
      char buf[1024];
      size_t len;
      socklen_t clen;
      
      // We will listen with this file descriptor
      if((lfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0){
        fprintf(stderr, "Error whilst starting to listen\n");
        exit(42);
      }
    
      // Define what we are listening for
      bzero(&servaddr, sizeof(servaddr));
      servaddr.sin_family = AF_INET;
      servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
      servaddr.sin_port = htons(1234);
    
      // Bind to the address
      if(bind(lfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0){
        perror("Couldn't bind");
        exit(42);
      }
      
      // Do stuff
      while(1){
        // We need to peek at the first part of the packet to determine who to connect to
        len = 1;
        printf("Reading...\n");
        clen = sizeof(clientaddr);
        if((len = recvfrom(lfd, buf, len, MSG_PEEK, (struct sockaddr *) &clientaddr, 
    		       &clen)) < 0){
          perror("Socket peek error");
          exit(42);
        }
        if(len == 0) break;
    
        // Connect
        if(connect(lfd, &clientaddr, clen) < 0){
          perror("Could not connect");
          exit(42);
        }
        
        printf("Data arrived\n");
    
        // Spawn a child to handle this packet
        switch(fork()){
        case -1:
          perror("Couldn't spawn child to handle connection");
          exit(42);
          
        case 0:
          // Child process -- setup the file descriptors, and the run the helper application
          dup2(lfd, 0);
          dup2(lfd, 1);
    
          execl("/bin/cat", "cat", NULL);
          perror("Exec failled");
          exit(42);
          break;
    
        default:
          // Parent process
          break;
        }
      }
    }
    
    And finally, this is what inetd and xinetd should do. And now cat works as a UDP echo server...

    The next step is to write up the patches to inetd and xinetd. It strikes me as being singularly useless otherwise...

    Peter Morgan also seems to think I am on the right track with my honours thesis, which is a good thing.

    Company social golf in the evening. Pictures up soon!

posted at: 22:00 | path: /diary | permanent link to this entry
There are no comments on this post which have survived moderation. 174 posts have been culled and 171 blocked. Be the first to make a non-spam comment here, please!