/* * * File: tcpEchoClient.c * * Author: George V. Neville-Neil * * Purpose: This file implements a combined IPv4 and IPv6 TCP echo * client. It is meant for use on any BSD sockets compliant platform. * */ /* * sys/types.h includes the file endian.h which contains * all of the byte ordering macros such as ntohl() etc. */ #include /* * sys/socket.h defines all the data structures, defines and * function prototypes for the IPv4 socket calls. */ #include /* * Individual protocol structures are included from their respective * files but both IPv4 and IPv6 are provided by including netinet/in.h. * To find the definitions of IPv6 data structures (such as sockaddr_in6) * you must dig deeper and read netinet6/in6.h. */ #include /* * For host name resolution to work you must include * which contains the definitions used by the gethostname(3) and * gethostname2(3) routines. */ #include /* * unistd.h contains the definitions necessary for using the getopt(3) * routine which processes command line arguments. */ #include char program[32]; /* Magic number, program name is not more than 32 */ void usage(char* message) { if (message != NULL) printf("Error: %s\n", message); printf("Usage: %s -h hostname -m message -v 4|6\n", program); exit (-1); } int main(int argc, char **argv) { int sock; /* The socket file descriptor used in all calls. */ int retVal; /* Generic return value holder. */ int ipv; /* Version of IP we're going to use, either 4 or 6. */ char *host; /* Host name we're going to contact. */ char *message; /* Text we're sending to the echo server. */ char *retMessage; /* Text we're receiving from the echo server. */ /* * The following are necessary for command line argument processing * by getopt(3). */ extern char *optarg; extern int optind; extern int optopt; extern int opterr; extern int optreset; int optChar; /* Character returned by getopt(3) */ bcopy(argv[0], program, strlen(argv[0]) + 1); while ((optChar = getopt(argc, argv, "v:h:m:")) != -1) { switch(optChar) { case 'v': ipv = atoi(optarg); break; case 'h': host = optarg; break; case 'm': message = optarg; break; case '?': default: usage(NULL); break; } } argc -= optind; argv += optind; if (host == NULL) usage("You must specify a host name with -h."); if ((ipv != 4) && (ipv != 6)) usage("Version must be 4 or 6."); /* * The first real work we do is get a socket descriptor which is * really file descriptor as far as the operating system is * considered. * * We are opening an IPv4 socket. */ if (ipv == 4) { /* * Define data we only use within this block of code. */ struct sockaddr_in haddr; /* IPv4 specific socket address. */ struct hostent *hp; /* Host Entry information is generic. */ struct servent *sp; /* Service Entry information is generic. */ /* * Here we open a socket with the address family AF_INET (IPv4) * and the type set to SOCK_STREAM (reliable connection). * We don't specify the protocol (the last argument is 0) * because we don't need to. Upon successful completion * sock contains a number that is 0 or greater although * the values 0, 1, and 2 are extremely unlikely as they * are usually reserved to the process's standard in, * standard out, and standard error file descriptors. */ if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror(program); usage("Could not open socket."); } /* ` * In order to talk to a host on the network we have to look * up its information based on its name. We could work directly * with Internet Addresses but humans are far better at remembering * names. * * gethostbyname(3) is an IPv4 specific routine to get host * information, returned in the variable hp, from the network * itself. The O/S handles all the work of looking up the * name for us. */ if ((hp = gethostbyname(host)) == NULL) { perror(program); usage("gethostbyname failed"); } /* * To use the information we must place the the relevant parts into * a sockaddr_in structure which will be passed to connect(3). * Before we use the structure we have to clear it out. */ bzero(&haddr, sizeof(haddr)); /* * Set the address family in the structure to AF_INET which is * IPv4. */ haddr.sin_family = AF_INET; /* * Copy the address we received from gethostbyname(3) into * the sockaddr_in structure. This is the IPv4 address that * connect(3) will contact. */ bcopy(hp->h_addr, &haddr.sin_addr, hp->h_length); /* * Next we must map the well known service (echo) to a * specific port number. This is done by getservbyname(3). */ sp = getservbyname("echo","tcp"); if (sp == NULL) usage("getservbyname failed on echo service"); /* * Place the returned port into the sockaddr_in structure. * Service names can be found on most Unix's in /etc/services. * They are also defined in the Assigned Numbers * RFC (see rfc-index.txt) The ports returned by getservbyname * are already in network byte order. */ haddr.sin_port = sp->s_port; /* * Finally we get to the point of connecting to the remote system. * As arguments we pass the socket file descriptor (sock) returned * from the socket(2) call, the sockaddr_in (which MUST be cast * to a sockaddr*) and the size of the sockaddr_in. */ if ((retVal = connect(sock, (struct sockaddr *)&haddr, sizeof(haddr))) < 0) { perror(program); close(sock); usage("Could not connect"); } } /* * The following routines achieve the same effect as those * above except that they work for IPv6. The advent of the * IPv6 protocol required a reworking of the sockets interfaces * to handle different address families and address lengths. * Much of this is discussed in the current Internet Draft * draft-ietf-ipngwg-rfc2553bis-04.txt and RFC 2553. Start * with the RFC. */ if (ipv == 6) { /* * Block specific data, so we can attempt to hide v4 vs. v6 * differences. */ struct sockaddr_in6 haddr; /* IPv6 specific socket address. */ struct hostent *hp; /* Host Entry information is generic. */ struct servent *sp; /* Service Entry information is generic. */ /* * We open the socket with the address family of AF_INET6 (IPv6) * the type of SOCK_STREAM, and protocol of 0. Leaving out * the protocol has no effect. The system will choose TCP * as the transport layer and IPv6 as the network layer. */ if ((sock = socket(AF_INET6, SOCK_STREAM, 0)) < 0) { perror(program); usage("Could not open socket."); } /* * For IPv6 we must use the new call gethostbyname2(3) which * works both for IPv6 and IPv4 because it has two arguments * the name of the host to contact, and the address family. * In this case we supply AF_INET6 as the address family just as * in the call to socket(2). */ if ((hp = gethostbyname2(host, AF_INET6)) == NULL) { perror(program); usage("Could not contact host."); } /* * To use the information we must place the the relevant parts into * a sockaddr_in structure which will be passed to connect(3). * Before we use the structure we have to clear it out. */ bzero(&haddr, sizeof(haddr)); /* * Set the address family in the structure to AF_INET6. */ haddr.sin6_family = AF_INET6; /* * Copy the address we received from gethostbyname(3) into * the sockaddr_in structure. This is the IPv4 address that * connect(3) will contact. */ bcopy(hp->h_addr, &haddr.sin6_addr, hp->h_length); /* * Next we must map the well known service (echo) to a * specific port number. This is done by getservbyname(3). */ sp = getservbyname("echo","tcp"); if (sp == NULL) usage("getservbyname failed on echo service"); /* * Place the returned port into the sockaddr_in6 structure. * Service names can be found on most Unix's in /etc/services. * They are also defined in the Assigned Numbers * RFC (see rfc-index.txt) The size of a port is the same * in both IPv4 and IPv6. The ports returned by getservbyname * are already in network byte order. */ haddr.sin6_port = sp->s_port; /* * Finally we get to the point of connecting to the remote system. * As arguments we pass the socket file descriptor (sock) returned * from the socket(2) call, the sockaddr_in (which MUST be cast * to a sockaddr*) and the size of the sockaddr structure. */ if ((retVal = connect(sock, (struct sockaddr *)&haddr, sizeof(haddr))) < 0) { perror(program); close(sock); usage("Could not connect"); } } /* * NOTE: We are no longer worried about the differences between * IPv4 and IPv6. */ /* * Once the connect succeeds we can use the socket * write a message to the server. The echo service * simply echo's back whatever we write. We write our * message, read it back, verify that the message is the * same and exit. */ /* * write(2) takes three arguments, the file descriptor * (in this case our open, connected, socket), the data * to write, and the length of the data we're writing. * A return value of -1 indicates and error. */ if ((retVal = write(sock, message, strlen(message) + 1)) < 0) { perror(program); close(sock); usage("Could not write to socket."); } /* * We allocate a buffer to read the returned string so that * we can compare what the server sent us with what we * sent to it. If the malloc(2) fails (very unlikely) * we exit. */ if ((retMessage = (char *)malloc(strlen(message) + 1)) == NULL) { close(sock); usage("Could not malloc space for return value."); } /* * Here we read back the response, on the same socket, from * the echo server. */ retVal = read(sock, retMessage, strlen(message) + 1); if ((retVal < 0) || (retVal < (strlen(message) + 1))) { free(retMessage); close(sock); perror(program); usage("Read failed to get correct amount of data."); } /* * We're done talking to the server now so we close the * socket. */ close(sock); /* * Compare the two buffers, and print them both out. */ printf("Sent: %s.\n", message); printf("Recieved: %s.\n", retMessage); if (strcmp(message, retMessage) !=0) { free(retMessage); usage("Messages are not equal!"); } /* * Free the allocated data. */ free(retMessage); /* * Successful completion exit with a code of 0. */ exit(0); }