Netbounce Project

by: burt rosenberg
at: january 2013


Our first trip on the information highway!

Netbounce

To get started on internet programing, let's try to bounce a packet of a distant machine. True, this is not as hopelessly techno-romantic as moon bouncing, but I would think there would be a thrill of accomplishment as you take your first trip on the information highway.

UDP, Sockets and Ports

And as the picture shows, we will be riding minimal — a protocol called UDP. UDP is the slightest sliver of a protocol about IP, so it will give you a good feel for packet communication on the Internet. The major software abstraction that we use as programmers is the socket, more properly the Berkeley Socket. This abstraction summarizes all of the machinery into a file handle, very similar to reading and writing a simple file, except with a few outside API entry points, to set the destination addresses, or to sense the source address.

The level 4 UDP protocol add to the level 3 IP procotol the concept of ports. A port is a a 16-bit number the identifies in the context of the IP-endpoint which application-endpoint the data comes from or goes to. In the language of client-server communication, the client must know the destination port on the server of where the service is bound, but must choose as a formality a port for it's own socket so that return-trip data can be returned to it. The server must bind to a known port number and listen for incoming packets on that port, and if a response is required, read the source port of the sender and use that as the destination port for a return trip packet.

This divides port numbers into two classes: those that are fixed and known (on the server side), and those that are floating and will be assigned at some point during sending. The first are called well-known and registered and the second are called ephemeral. This distinction is obvious in the sidebar code in that the well-known port is a pound-define, while the ephemeral port is discovered by a call to getsockname in the client and recvfrom in the server.



/*
** netbounce-server.c
** from Beej's Guide to Network Programming: Using Unix Sockets, 
** by Brian "Beej" Hall.
**
** modified by Burt Rosenberg, 
** Created: Feb 8, 2009
** Last modified: Jan 30, 2013
*/

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>


#define MYPORT 3333

#define MAXBUFLEN 100

int main(void) {
	int sockfd;
	struct sockaddr_in my_addr;
	struct sockaddr_in their_addr;
	unsigned int addr_len, numbytes;
	char buf[MAXBUFLEN];
	
	if ((sockfd=socket(AF_INET,SOCK_DGRAM,0))==-1) {
		perror("socket") ;
		exit(1) ;
	}
	
	my_addr.sin_family = AF_INET ;
	my_addr.sin_port = htons(MYPORT) ;
	my_addr.sin_addr.s_addr = INADDR_ANY ;
	memset(&(my_addr.sin_zero),'\0',8) ;

	if (bind(sockfd, (struct sockaddr *)&my_addr, 
		sizeof(struct sockaddr)) == -1 ) {
		perror("bind") ;
		exit(1) ;
	}
	
	addr_len = sizeof(struct sockaddr) ;
	if ((numbytes=recvfrom(sockfd, buf, MAXBUFLEN-1,0,
		(struct sockaddr *)&their_addr, &addr_len)) == -1 ) {
		perror("recvfrom") ;
		exit(1) ;
	}
	
	/* added to get source port number */
	printf("got packet from %s, port %d\n", inet_ntoa(their_addr.sin_addr), 
			ntohs(their_addr.sin_port)) ;
	printf("packet is %d bytes long\n", numbytes ) ;
	buf[numbytes] = '\0' ;
	printf("packet contains \"%s\"\n", buf ) ;
	
	{
	   /* return the packet to sender, 
	    */
		int bytes_sent ;
	    if ((bytes_sent = sendto( sockfd, buf, strlen(buf), 0,
	    		(struct sockaddr *)&their_addr, 
	    		(struct sockaddr)))==-1 ) {
	    	perror("send") ;
	    	exit(1) ;
	    }
	    printf("packet sent, %d bytes\n", bytes_sent) ;
	}
	close(sockfd) ;
	return 0 ;
}


/*
** netbounce-client.c
** from Beej's Guide to Network Programming: Using Unix Sockets, 
** by Brian "Beej" Hall.
**
** modified by Burt Rosenberg, 
** Created: Feb 8, 2009
** Last modified: Jan 30, 2013
*/

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<netdb.h>


#define MYPORT 3333

int main( int argc, char * argv[] ) {

	int sockfd ;
	struct sockaddr_in their_addr ;
	struct hostent *he ;
	int numbytes ;
	
	if ( argc!=3 ) {
		fprintf(stderr,"usage: netbounce-client hostname message\n") ;
		exit(1) ;
	}
	if ((he=gethostbyname(argv[1]))==NULL) {
		perror("gethostbyname") ;
		exit(1) ;
	}
	if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1 ) {
		perror("socket") ;
		exit(1) ;
	}
	their_addr.sin_family = AF_INET ;
	their_addr.sin_port = htons(MYPORT) ;
	their_addr.sin_addr = *((struct in_addr *)he->h_addr) ;
	memset(&(their_addr.sin_zero), '\0', 8 ) ;
	
	if ((numbytes=sendto(sockfd, argv[2], strlen(argv[2]),0,
			(struct sockaddr *)&their_addr, 
			sizeof(struct sockaddr)) ) == -1 ) {
		perror("sendto") ;
		exit(1) ;
	}
	printf("send %d bytes to %s\n", numbytes, 
		inet_ntoa(their_addr.sin_addr)) ;
	
	/* added by burt;
	   sendto bound socket, let's get the port number that was selected
	*/
	{
	
#define MAXBUFLEN 100

		struct sockaddr_in my_addr ;
		unsigned int addr_len ;
		char buf[MAXBUFLEN];
		
		addr_len = sizeof(struct sockaddr_in) ;
		getsockname( sockfd, (struct sockaddr *)&my_addr, &addr_len ) ;
		printf("sent from port %d\n", ntohs(my_addr.sin_port)) ;
		/* on OSX 10.5.4, this prints out ascending port numbers 
		   each time run, starting around 49486 
		 */
		
		/* now receive the response .. 
		 */
		 
		/*** put a wait in here to show how buffering worls ***/
		printf("sleeping for 5 seconds\n") ;
		sleep(5) ;
		
		printf("calling for return packet\n") ;
		addr_len = sizeof(struct sockaddr_in) ;
		if ((numbytes=recvfrom(sockfd, buf, MAXBUFLEN-1,0,
			(struct sockaddr *)&their_addr, &addr_len)) == -1 ) {
			perror("recvfrom") ;
			exit(1) ;
		}
		/* added to get source port number */
		printf("got packet from %s, port %d\n", 
			inet_ntoa(their_addr.sin_addr), ntohs(their_addr.sin_port)) ;
		printf("packet is %d bytes long\n", numbytes ) ;
		buf[numbytes] = '\0' ;
		printf("packet contains \"%s\"\n", buf ) ;
	
	}
	
	close(sockfd) ;
	return 0 ;
}



#
# Feb 8, 2009
#

CC = gcc
LOCALHOST = localhost
REMOTEHOST = ec2-50-19-171-60.compute-1.amazonaws.com

all:
	${CC} -o netbounce-server netbounce-server.c ${LIBS}
	${CC} -o netbounce-client netbounce-client.c ${LIBS}

test:
	@echo For a local test, for run test-local-s in one window, 
	@echo then test-local-c in another.
	@echo For a netbounce, run nb-s in the far computer, 
	@echo then nb-c in the near computer.

test-local-c:
	./netbounce-server

test-local-s:
	./netbounce-client ${LOCALHOST} hello_world


nb-s:
	@echo Ready to bounce!
	./netbounce-server

nb-c:
	./netbounce-client ${REMOTEHOST} "The amazing net bounce!"

submit:
	svn commit -m "submitted for grade"

clean:
	-rm netbounce-client netbounce-server

 

Server code

  1. Sockets, server or client, are created with a call to socket (note the includes: sys/types.h, sys/socket.h, netinet/in.h and apra/inet.h tend to be standard requirements).
  2. Since we are a server we need to bind to our well known port, using a call to bind. This requires that a struct sockaddr_in structure be filled out.
    * Note that the structure is filled out with INADDR_ANY as the source address. This mean the socket will listen on all interfaces. A single machine might have several interfaces, and you can specify a single interface, if that is what is needed.
    * Note the use of the "host-to-network-short" macro htons. The Internet is big-endian. The computer on which the code runs might be big-endian or little-endian. This macro is defined to make the correction (or do nothing) for short data-types.
    * Note the use of sizeof. The structure is variable sized, depending on the protocol, so the called function needs to be told how many bytes are in the passed structure.
  3. The server then listens on the port by a call to recvfrom. This is a blocking call. It will return with the message copied into the buffer, and the number of bytes copied as a return value.
    * Note that there is no way to know how many bytes of the data has been returned is just by looking at the data. Many students are confused between byte data and string data. String data is byte data with a stop signal at the end of the data — a null byte. No such thing for byte data.
    * Note the cast in the call to a struct sockaddr *. This cast takes the subclass sockaddr_in and widens it to a generic sockaddr for the purposes of matching the signature of the called function.
  4. The packet is sent back using a call to sendto. Note that the destination address is passed into the function using the struct sockaddr_in that was filled out by recvfrom. All you need do is pass this struct to the sender and it is properly filled out with return trip data, including the ephemeral port of the client.

Client code

  1. The level 3 endpoint is an IP address. However, what most people have for the endpont is a name. The DNS system is a world wide network of computers that form a dynamic and distributed database to resolve names to IP addresses. For the client to invoke this database, it calles gethostbyname given the (ASCII) name, and returns a struct hostent structure.
    * Note that the memory used by this structure is "property of" the callee. You did not send a pointer, your received a pointer. You have been warned!
  2. A socket is created with a call to socket, just as for the server.
  3. The data is sent with a call to sendto.
    * Note that the client socket is bound with this call to a freshly chosen port number.
  4. To get the source port number, the program makes a call to getsockname.
    * Note that in this case the call fills out a caller allocated struct sockaddr_in, and that it is down-cast to a sockaddr for the purposes of the call.
  5. The bounced packet is received by the client using a call to recvfrom, which also fills out struct sockaddr_in according the the packet information of the returned packet.
    * Note that the small wait I added to the code demonstrates that the receive queue is active immediately after bind, and recvfrom will take a packet from this queue, if the queue is not empty, or block waiting for a packet, if the queue is empty.

Bound and connected UDP sockets

UDP sockets can be bound or unbound, and connected or unconnected. First they get bound, after which packets to the port from any host are put on the socket's receive queue. If the socket becomes connected, it is associated with a specific source host and port and the queue will be restricted to packets from that port on that host. Bind binds a socket, and connect connects it.

Recvfrom must be called on a bound socket, and sendto, if called on an unbound socket will bind it to an default interface at an ephemeral port. Neither ever connects the socket. Binding a socket sets the port for both sento and recvfrom. A socket sends and receives on the same port.

Once connected using the connect call, the usage of sendto and recvfrom is restricted — at best the address structure will be ignored. I'm a bit confused about the use of connect, as there seems to be a critical race between arriving packets during the interval between bind and connect — unless connect auto-flushes the receive queue.

Firewall issues

As a final note about firewall issues: to bounce a packet, the server code must be on a server, i.e. a machine whose firewall ruleset lets pass unsolicited UDP packets destined for the bounce port. Generally this will not be true of your home machine which is behind a ADSL router, or a typical end-user machine at a school or business. While we can bounce off the moon, martians are not allowed to bounce off of us. We are concerned that the bounce packet will contain a virus that will take over the world.

Seriously. Although not martians, but hackers, are the concern.

You can arrange to have the server run behind a home firewall, but you will have to enable port forwarding, so that the incoming packet is let pass. You can also arrange for this at your university. You'd have to ask IT. And fill out a form. And go before a committee. And take an on-line ethics exam.

However, it will work if the client is behind a firewall, because most firewalls allow outgoing UDP packets and are generally configured to allow an incoming UDP packet if it somehow matches a recent outgoing packet. The firewall, noting a packet crossing its protection barrier from port A of machine M to port B of machine N, will often drop its guard for packets coming across its barrier from port B of machine N if it is addressed to port A of machine M. Unsolicited packets are dropped as potential attacks.

Copyright 2013 burton rosenberg
Last modified: 30 jan 2013