//-------------------------------------------------------------------
//	arpdemo.cpp
//
//	This application will transmit an ARP Request message via
//	our character-mode Linux device-driver for Intel's 82573L 
//	Ethernet network interface controller, modified to permit
//	writing and reading a 'raw' Ethernet packet ('nicraw.c'); 
//	then afterward, it will try to read an ARP Reply message.
//
//		compile using:  $ g++ arpdemo.cpp -o arpdemo
//		execute using:  $ ./arpdemo <ip-address>
//
//	NOTE: In this revision of 'arpdemo.cpp' the device-driver
//	is expected to implement an 'ioctl()' method that returns
//	the hardware MAC-address of the network interface device. 
//
//	programmer: ALLAN CRUSE
//	written on: 10 MAY 2009
//	revised on: 13 MAY 2009 -- remove reliance on 'ethers' file
//-------------------------------------------------------------------

#include <netdb.h>	// for gethostbyname()
#include <stdio.h>	// for printf(), perror(), fopen(), fgets() 
#include <fcntl.h>	// for open() 
#include <stdlib.h>	// for strtol(), exit() 
#include <unistd.h>	// for write(), read()  
#include <string.h>	// for strlen(), strtok()
#include <arpa/inet.h>	// for inet_aton()
#include <sys/ioctl.h>	// for ioctl()


typedef struct	{
		unsigned short	hardware_type;
		unsigned short	protocol_type;
		unsigned char	hardware_size;
		unsigned char	protocol_size;
		unsigned short	arp_operation;
		unsigned char	src_hw[ 6 ];
		unsigned char	src_ip[ 4 ];
		unsigned char	dst_hw[ 6 ];
		unsigned char	dst_ip[ 4 ];
		} ARP_MESSAGE;


char devname[] = "/dev/nic";
unsigned char dst[ 6 ] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
unsigned char src[ 6 ] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
unsigned char typ[ 2 ] = { 0x08, 0x06 };
char arp[ 60 ], buf[ 60 ];


int main( int argc, char **argv )
{
	// check for presence of the command-line argument
	if ( argc == 1 ) { fprintf( stderr, "nodename missing\n" ); exit(1); }
	
	// get the IP-address for the destination nodename
	struct hostent	*pp = gethostbyname( argv[ 1 ] );
	if ( !pp ) { herror( "gethostbyname" ); exit(1); } 	
	char	peeraddr[ 16 ] = { 0 };
	strcpy( peeraddr, inet_ntoa( *(in_addr*)pp->h_addr ) );
	
	// get the IP-address for the source nodename
	char	hostname[ 64 ] = { 0 }, hostaddr[ 16 ] = { 0 };
	gethostname( hostname, 63 );
	struct hostent	*hp = gethostbyname( hostname );
	if ( !hp ) { herror( "gethostbyname" ); exit(1); }
	strcpy( hostaddr, inet_ntoa( *(in_addr*)hp->h_addr ) ); 	

	// convert dotted-decimal strings to 32-bit integers 
	unsigned int	peer_ip = inet_addr( peeraddr );
	unsigned int	host_ip = inet_addr( hostaddr );

	printf( "\nHost is \'%s\' (%s) \n", hostname, hostaddr );

	// open the network interface device-file
	int	fp = open( devname, O_RDWR );
	if ( fp < 0 ) { perror( devname ); exit(1); }

	// get this station's hardware MAC-address
	if ( ioctl( fp, 0, src ) < 0 ) { perror( "ioctl"); exit(1); }

	// initialize the ARP protocol header
	ARP_MESSAGE	arp_request = { 0 };
	arp_request.hardware_type = htons( 0x0001 );
	arp_request.protocol_type = htons( 0x0800 );
	arp_request.hardware_size = 6;
	arp_request.protocol_size = 4;
	arp_request.arp_operation = htons( 1 );
	memcpy( &arp_request.src_hw, src, 6 );
	memcpy( &arp_request.src_ip, &host_ip, 4 );
	memcpy( &arp_request.dst_ip, &peer_ip, 4 );
	
	// initialize the 'raw' Ethernet packet
	memcpy( arp+0, dst, 6 );
	memcpy( arp+6, src, 6 );
	*(unsigned short*)( arp+12 ) = htons( 0x0806 );
	memcpy( arp+14, &arp_request, 28 );
	
	// Display the 'raw' ARP Request frame
	unsigned char	*cp = (unsigned char*)arp;
	for (int i = 0; i < 42; i+=16)
		{
		printf( "\n %04X: ", i );
		for (int j = 0; j < 16; j++)
			{
			if ( i+j < 42 ) printf( "%02X ", cp[ i+j ] );
			else printf( "   " );
			}
		for (int j = 0; j < 16; j++) 
			{
			unsigned char	c = (i+j<42) ? cp[ i+j ] : 0x20;
			if (( c < 0x20 )||( c > 0x7E )) c = '.';
			printf( "%c", c );
			}
		}
	printf( "\n\n" ); 

	// write our ARP REQUEST to the device-file
	int	txbytes = write( fp, arp, sizeof( arp ) );
	if ( txbytes < 0 ) { perror( "write" ); exit(1); }
	printf( "Wrote %d bytes to \'%s\' \n\n", txbytes, argv[1] );

	// try to read an ARP REPLY 
	do	{
		int	rxbytes = read( fp, buf, sizeof( buf ) );
		if ( rxbytes < 0 ) { perror( "read" ); exit(1); }
		printf( "Read %d bytes from \'%s\' \n", rxbytes, argv[1] );
		}
	while ( *(unsigned short*)(buf+12) != htons( 0x0806 ) );

	// Display the 'raw' ARP Reply frame 
	cp = (unsigned char*)buf;
	for (int i = 0; i < 42; i+=16)
		{
		printf( "\n %04X: ", i );
		for (int j = 0; j < 16; j++)
			{
			if ( i+j < 42 ) printf( "%02X ", cp[ i+j ] );
			else printf( "   " );
			}
		for (int j = 0; j < 16; j++) 
			{
			unsigned char	c = (i+j<42) ? cp[ i+j ] : 0x20;
			if (( c < 0x20 )||( c > 0x7E )) c = '.';
			printf( "%c", c );
			}
		}
	printf( "\n\n" ); 

	// display the reported Hardware Address of the target station 
	ARP_MESSAGE	*ap = (ARP_MESSAGE*)( buf + 14 );
	printf( "Station \'%s\' (%s) ", argv[ 1 ], peeraddr );
	printf( "has hardware-address " );
	for (int i = 0; i < 6; i++)
		printf( "%02X%c", ap->src_hw[i], (i<5)?':':' ' );
	printf( "\n\n" );
}