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!