/*----------------------------------------------------------------------*/ /* arp6, Copyright (c) 2002, Peter Bozarov, peter bozz.demon.nl */ /* */ /* Note: requires libpnet6.0.1.0 or higher. */ /* */ /* Link with libpnet6 and libpthread. */ /* */ /* A simple ARP-like address resolution/neighbor discovery client */ /* This program resolves IP addresses to hardware addresses. It */ /* supports IPv4 style ARP, and the new IPv6 style Neighbor Discovery */ /* mechanism to achieve this. */ /* */ /* This program is a good example of how to use some of libpnet6's most */ /* advanced features, such as reading and writing raw network packets */ /* directly to and from the ``wire'', as well as using raw sockets and */ /* ICMPv6 style messages and filters. */ /* In addition, it demonstrates the ease with with interface information*/ /* such as IP address, name, and type can be retrieved by libpnet6. */ /* */ /* $Id: arp6.c,v 1.11 2002/10/22 12:40:32 kingofgib Exp $ */ /*----------------------------------------------------------------------*/ # include # include # include # include # include /*----------------------------------------------------------------------*/ /* Forward declarations */ /*----------------------------------------------------------------------*/ static const char * progname = "arp6"; static int reverse = 0; static int debug = 0; static int Debug = 0; static PNETADDR arpHost; /* InetAddr of resolved host name */ static PNETIF arpIf; /* Interface we need to use */ static PNETIFADDR arpIfAddr; /* InetAddr of interface */ static int arp6_parse(pnet_byte *,int); static int arp6_usage(void); static PNETIF arp6_get_interface(const char *,int,PNETIFADDR*); static int arp6_do_arp(void); static int arp6_do_nd(void); static int arp6_usage( void ) { printf("%s: a simple libpnet6-based arp client\n",progname); printf(" -a force the use of ARP\n"); printf(" -h hostname/ip address to look up\n"); printf(" -i interface to use to send/receive requests\n"); printf(" -d turn on debug info\n"); printf(" -D turn on libpnet6 debug info\n"); printf(" -r do a reverse arp (not implemented yet)\n"); return 0; } int main( int argc, const char ** argv ) { const char * hname = NULL; /* Host name to look up */ const char * ifname= NULL; /* Interface name to use */ int use_arp = 0; int force_arp = 0; int c; /* Used to parse options */ progname = argv[0]; while ( (c = pnetGetopt( argc, argv, ":Dani:h:rd" )) != -1 ) { switch( c ) { case 'D': Debug = 1; break; case 'a': force_arp = 1; break; case 'h': hname = strdup( pnetOptarg ); /* Get host to look up */ break; case 'i': ifname = strdup( pnetOptarg ); /* Get interface to use */ break; case 'r': reverse = 1; break; case 'd': debug = 1; break; case '!': fprintf(stderr,"Option '%c' requires an argument\n", pnetOptopt ); arp6_usage(); return 1; case '?': fprintf( stderr, "Unknown argument '%c'\n", pnetOptopt ); arp6_usage(); return 1; } } if ( !hname ) { arp6_usage(); return 1; } /* * Look up the host and store it in the variable arpHost. * We first look up a IPv6 enabled address. If that can be resolved, * then we use Neighbor Discovery instead of ARP. Else, we fall * back to arp. If force_arp is on, we do ARP anyway. */ if ( !force_arp && (arpHost = pnetAddrResolve( PNET_IPv6, hname )) ) use_arp = 0; else if ( (arpHost = pnetAddrResolve( PNET_IPv4, hname )) ) use_arp = 1; else { fprintf(stderr,"%s: Cannot resolve %s\n", progname , hname ); return 1; } /* * Now, look up the interface we should use. ARP requires we use IPv4, * while neighbor discovery requires we use IPv6. Either way, if a * suitable interface exists, it is returned here, along with the first * of its addresses that supports the requested family (IPv4 or IPv6). * The IP address is returned through and into arpIfAddr. */ if ( !( arpIf = arp6_get_interface( ifname, use_arp ? PNET_IPv4 : PNET_IPv6, &arpIfAddr )) ) { /* arp6_get_interface() prints an error message if something's wrong */ return 1; } if ( use_arp ) arp6_do_arp(); else arp6_do_nd(); pnetAddrFree( arpHost ); return 0; } /*----------------------------------------------------------------------*/ /* Having obtained the hardware address of the host in question, output */ /* it here, using Berkeley-esque style. */ /*----------------------------------------------------------------------*/ static void arp6_print_result( pnet_byte * hw_addr ) { char hostname[ PNET_ADDR_BUFSIZ ]; char abuf[ PNET_ADDR_BUFSIZ ]; pnetAddrToString( arpHost, abuf, sizeof( abuf )); if ( pnetAddrToHostname( arpHost, hostname, sizeof( hostname ) ) ) { if ( debug ) printf("No hostname associated with %s\n", abuf ); strcpy( hostname, abuf ); } printf("%s (%s) is at %x:%x:%x:%x:%x:%x on %s [%s]\n", hostname, abuf, hw_addr[0],hw_addr[1],hw_addr[2], hw_addr[3],hw_addr[4],hw_addr[5], pnetIfName( arpIf ), pnetIfTypeString( arpIf )); } /*----------------------------------------------------------------------*/ /* Get a pointer to an interface we can use for our requests. */ /* If the family specifed is PNET_IPv4, we need to locate a broadcast */ /* enabled interface. If family is PNET_IPv6, we don't need a broadcast */ /* interface. */ /* In all cases, we skip interfaces that are not up, or aren't running */ /* or have no physical (hardware) address (such as PPP or SLIP). */ /*----------------------------------------------------------------------*/ static PNETIF arp6_get_interface( const char * ifname, int fam, PNETIFADDR * ppia ) { const char* iptype = fam == PNET_IPv6 ? "IPv6" : "IPv4"; char abuf[ PNET_ADDR_BUFSIZ ]; PNETIF pif = NULL; const pnet_byte * pbin = 0; pnet_int len = 0; /* * If no interface was specified, we look up all interfaces, and * return a pointer to the first suitable interface. */ if ( ! ifname ) { /* Loop through all interfaces here */ for ( pif = pnetGetIfInfo( fam ); pif; pif = pnetIfNext(pif) ) { /* Skip non-interesting interfaces */ if ( ! pnetIfIsUp( pif ) || ! pnetIfIsRunning( pif ) ) continue; /* For IPv4 we need a broadcast enabled interface */ if ( fam == PNET_IPv4 && ! pnetIfIsBroadcast( pif ) ) continue; /* * This is the interface we need. Use it only if * it has a physical address (i.e. no loopback) */ if ( (pbin = pnetIfGetLinkAddress( pif, &len ) ) ) { ifname = pnetIfName( pif ); break; } } if ( ! ifname ) { fprintf( stderr,"%s: could not find a suitable %s network " "inteface\n", progname, iptype ); return NULL; } } /* * Fetch details on the requested interface: we need its IP address. * Note: pnetIfGetByName() takes the family as argument. This way, it * only reads those interface addresses that match the given address * family. Calling pnetIfGetNextAddr() afterwards will guarantee that * the first address we get a handle to is of the correct address * family (i.e. not IPv6 when we need an IPv4 address). */ if ( !( pif || ( pif = pnetIfGetByName( fam, ifname )) ) || !( *ppia = pnetIfGetNextAddr( pif, NULL ) ) ) { fprintf( stderr, "%s: no such interface '%s', or interface " "not %s enabled\n", progname , ifname, iptype ); return NULL; } if ( debug ) printf("Using interface %s [%s, %x:%x:%x:%x:%x:%x]\n", pnetIfName( pif ),pnetIfAddrToString(*ppia,abuf,sizeof(abuf)), pbin[0],pbin[1],pbin[2],pbin[3],pbin[4],pbin[5]); return pif; } /*----------------------------------------------------------------------*/ /* Do a standard IPv4 ARP request */ /* Consult the appropriate RFC */ /*----------------------------------------------------------------------*/ # define ETH_ARPCODE_0 0x08 # define ETH_ARPCODE_1 0x06 # define ETH_RARPCODE_0 0x80 # define ETH_RARPCODE_1 0x35 # define ARP_REQUEST 1 # define ARP_REPLY 2 # define RARP_REQUEST 3 # define RARP_REPLY 4 struct arp_hdr { pnet_ushort h_type; pnet_ushort p_type; pnet_byte h_alen; pnet_byte p_alen; pnet_ushort opcode; pnet_byte s_haddr[6]; pnet_byte s_paddr[4]; pnet_byte t_haddr[6]; pnet_byte t_paddr[4]; }; /* * We can only send ARP requests and receive ARP replies if we access * the network interface directly. We need to open a ``live'' link layer * device (i.e. the ethernet card) and send and receive raw ARP packets * directly on the network ``wire''. */ static int arp6_do_arp( void ) { PNET_PKTACCESS pa; /* Link Layer access device */ PNetPacket pkt; /* To receive link layer packets */ pnet_byte buf[ 128 ]; /* Buffer space to format requests */ struct arp_hdr* arp; /* Arp header format pointer */ const pnet_byte * pbin = 0; /* Work pointer for binary addresses */ int len; int req_size = 14 + sizeof( struct arp_hdr ); /* * Open our datalink interface. Set mode to non-promiscuous, and timeout * to 0, in order to get our packets immediately. We only need 28 bytes of * arp header, but we need to account for the 14 byte ethernet header. So * we tell the packet capture driver to return 42 bytes of each packet. */ if ( !(pa = pnetPktOpenInterface( pnetIfName( arpIf ), 0, 0, 42 )) ) { fprintf( stderr, "%s: Cannot open interface %s\n", progname , pnetIfName( arpIf )); return -1; } /* * Get a pointer to the interface's hardware address. */ if ( !(pbin = pnetIfGetLinkAddress( arpIf, &len )) ) { fprintf( stderr,"%s: could not read hardware address for %s.\n", progname , pnetIfName( arpIf )); pnetPktCloseInterface( pa ); return -1; } /* * Set up an listen filter for ARP replies. Kernel will only pass * to us packets that are ARP replies. 7 and 8th bytes of the ARP * header contain the arp type: 2 means an ARP reply. Note that * the filter works by indexing bytes starting at 0. */ pnetPktAddFilter( pa, "arp[6:2]=2" );/* ARP replies */ memset( buf, 0, sizeof( buf ) ); /* * Fill in the ethernet header: * target address [6 bytes]: all 1's since we're sending a broadcast * source address [6 bytes]: our hardware address (read off the interface) * frame type [2 bytes]: identifying this frame as an ARP frame * * Remember, len is 6 bytes. This was set above by pnetIfGetLinkAddress(). */ memset( buf, 0xFF, len ); /* Destination */ memcpy( buf + len, pbin, len ); /* Source */ buf[12] = ETH_ARPCODE_0; buf[13] = ETH_ARPCODE_1; /* * Fill in ARP header. Read an RFC to understand the format. */ arp = (struct arp_hdr*) (buf + 14); /* Skip Ethernet frame header */ arp->h_type = pnet_htons( 0x001 ); /* Hardware is Ethernet */ arp->p_type = pnet_htons( 0x800 ); /* IP addresses are IPv4 */ arp->h_alen = 6; /* Length hw address */ arp->p_alen = 4; /* Length IP address */ arp->opcode = pnet_htons( reverse ? RARP_REQUEST : ARP_REQUEST ); /* * Fill in the given interface's hardware address (this is 'our' * hardware address). Still set from pnetIfGetLinkAddress(). */ memcpy( arp->s_haddr, pbin, len ); /* * Fill in our IP address (we read it off the interface). * Note that we fetch the actual binary (network) representation of * the address, that is, as it should go into the arp buffer. */ pbin = pnetIfAddrGetBinary( arpIfAddr, &len ); memcpy( arp->s_paddr, pbin, len ); /* * Fill in targer IP address. * Get the binary representation of the resolved host address arpIfAddr. * Note: this can't (or rather, should never) fail. */ pbin = pnetAddrGetBinary( arpHost , &len ); memcpy( arp->t_paddr, pbin, len ); if ( Debug ) { /* Dump the buffer so we can check its contents out */ pnetHexdumpX( stdout,(byte*)buf, req_size, 4, 0 ); } /* Pump the request into the network */ if ( pnetPktWrite( pa, buf, req_size ) != req_size ) { fprintf( stderr, "%s: error writing request\n", progname ); pnetPktCloseInterface( pa ); return -1; } /* * Put interface into cooked mode, so that the link layer frame header is * stripped off after a packet is read in (won't have to do it ourselves). */ pnetPktSetReadMode( pa, PNET_READ_COOKED ); memset( &pkt, 0, sizeof( pkt ) ); /* Wait for replies */ while ( 1 ) { if ( pnetPktNextPacket( pa, &pkt ) ) if ( arp6_parse( (byte*)pkt.pkt_buf, pkt.pkt_grablen ) == 0 ) break; } pnetPktCloseInterface( pa ); return 0; } /*----------------------------------------------------------------------*/ /* Parse an ARP reply */ /* 1. Check that packet is an ARP reply. */ /* 2. Check that target address of packet matches our own address. */ /* 3. Fetch sender's hardware address from the packet. */ /* 4. Return -1 on any error, 0 on success. */ /*----------------------------------------------------------------------*/ static int arp6_parse( pnet_byte * buf, int blen ) { struct arp_hdr *arp = (struct arp_hdr*) buf; const pnet_byte* pbin; int len; char abuf[ PNET_ADDR_BUFSIZ ]; if ( (unsigned)blen < sizeof( struct arp_hdr ) ) { printf("ARP packet corrupt\n"); return -1; } if ( arp->opcode != pnet_htons( ARP_REPLY ) ) return -1; /* Skip other arp messages */ /* * First, compare the target address of the message to see if * it is intended for us. */ pbin = pnetIfGetLinkAddress( arpIf, &len ); if ( ! memcmp( arp->s_haddr, pbin , len ) ) { if ( debug ) { if ( !pnet_inet_ntop( PNET_IPv4,arp->t_paddr,abuf,sizeof(abuf) ) ) { fprintf(stderr,"Illegible target address in ARP message\n"); return -1; } printf( "Got an ARP reply for %s\n", abuf ); } return -1; } arp6_print_result( arp->s_haddr ); return 0; } /*----------------------------------------------------------------------*/ /* Do Neighbor Discovery */ /* See RFC 1970 */ /*----------------------------------------------------------------------*/ # define ND_SOLICIT 135 # define ND_ADVERT 136 struct nd_opt { pnet_byte type; pnet_byte len ; /* In lengths of 8 octets */ pnet_byte data[6]; }; static int arp6_nd_recv( PNETSOCK ps, void * data ) { int ret; char buf[128]; char abuf[128]; pnet_icmp6 * icmp6; struct nd_opt* llopt; pnet_byte* tgt_addr; const pnet_byte* pbin; int len,hops; ret = pnetRead( ps, buf, sizeof( buf ) ); if ( ret == PNET_READ_TIMED_OUT ) /* Should not happen */ { printf("pnetRead() timed out\n"); return -1; } else if ( ret == PNET_READ_ERROR ) { printf("pnetRead(): input error\n"); return -1; } if ( ret < (int)sizeof( struct pnet_icmp6) ) { printf("pnetRead(): packet truncated\n"); return -1; } icmp6 = (struct pnet_icmp6*) buf; /* * We still check the message type, in case we weren't able to place * a suitable filter on this socket. */ if ( icmp6->icmp6_type != ND_ADVERT ) return 0; hops = 0; if ( pnetSockGetHops( ps, &hops ) || hops != 255 ) /* MUST be 255 */ { printf( "Message hops != 255, ignoring\n" ); return 0; } /* * First, compare the target address of the message to see if * it is intended for us. Target address is located just past the * ICMPv6 header. */ tgt_addr = (pnet_byte*) ( buf + sizeof( pnet_icmp6 ) ); pbin = pnetIfGetLinkAddress( arpIf, &len ); if ( !memcmp( tgt_addr, pbin , len ) ) { if ( debug ) { if ( pnet_inet_ntop( PNET_IPv6, tgt_addr, abuf, sizeof( abuf ) ) ) { fprintf(stderr,"Illegible target address in ICMPv6 message\n"); return 0; } printf( "Got a neighbor solicit message for %s\n", abuf ); } return 0; } if ( ret <= (int)sizeof( pnet_icmp6 ) + 16 ) { printf("pnetRead(): packet truncated\n"); return -1; } llopt = (struct nd_opt*) ( buf + sizeof( pnet_icmp6 ) + 16 ); arp6_print_result( llopt->data ); pnetStopListen( ); return 0; } /*----------------------------------------------------------------------*/ /* We create a raw socket, and send a Neighbor Solicit message to the */ /* arpHost. We put our hardware address as an option (as defined in */ /* RFC 1970). Then we set up an ICMPv6 filter on the raw socket, to only*/ /* accept ICMPv6 Neighbor Advertisements messages. */ /*----------------------------------------------------------------------*/ static int arp6_do_nd( void ) { PNETSOCK ps; /* Raw socket needed for ICMPv6 */ PNET_ICMPv6_Filter filter; char buf[128]; pnet_icmp6 * icmp6; const pnet_byte * pbin; struct nd_opt * llopt; int len; int total_len; /* * Get a pointer to the interface's hardware address. */ if ( !(pbin = pnetIfGetLinkAddress( arpIf, &len )) ) { fprintf( stderr,"%s: could not read hardware address for %s.\n", progname , pnetIfName( arpIf )); return -1; } /* * Open an IPv6 raw socket */ if ( !( ps = pnetRAWSocket2( PNET_IPv6, PNET_IPPROTO_ICMPV6 ) ) ) { fprintf( stderr, "Cannot open raw IPv6 socket.\n" ); return -1; } /* * Place an ICMPv6 filter on the socket. We only need to receive * Neighbor Advertisement messages. */ filter = pnetICMP_FilterOpen(); pnetICMP_FilterSetPass( filter, ND_ADVERT ); if ( pnetICMP_FilterInstall( ps, filter ) ) { fprintf( stderr, "Cannot install ICMPv6 filter for " "Neighbor Advert messages\n"); fprintf( stderr, "Continuing anyway....\n"); } /* * We don't need the PNET filter anymore, its already on the socket. * Release memory. */ pnetICMP_FilterClose( filter ); /* * Build a Neighbor Solicit ICMPv6 message */ total_len = sizeof( pnet_icmp6 ); /* * ICMPv6 header */ memset( buf, 0, sizeof( buf ) ); icmp6 = (pnet_icmp6*)buf; icmp6->icmp6_type = ND_SOLICIT; /* Neighbor Solicit type */ icmp6->icmp6_code = 0; icmp6->icmp6_cksum= 0; /* Copy target IP address into ICMP header */ pbin = pnetAddrGetBinary( arpHost , &len ); memcpy( buf + sizeof( pnet_icmp6 ), pbin, len ); total_len += len; llopt = (struct nd_opt*) ( buf + sizeof( pnet_icmp6 ) + len ); llopt->type = 1; llopt->len = 1; /* * Add as an option our own link layer address. */ pbin = pnetIfGetLinkAddress( arpIf, &len ); memcpy( llopt->data, pbin, len ); total_len += sizeof( struct nd_opt ); /* * Now, set IPv6 header parameters, in accordance with RFC 1970 * We set hop limit to 255, and let the kernel compute and set the * checksum. The ICMPv6 checksum field is located at offset 2 from * the start of the ICMPv6 header (i.e. the third and forth bytes). */ pnetSockChecksum( ps, 1 /* Request computation */, 2 /* Store here */ ); pnetSockSetHops( ps, 255 ); /* MUST be 255 */ if ( Debug ) { /* Dump the buffer so we can check its contents out */ pnetHexdumpX( stdout,(byte*)buf, total_len, 8, 0 ); } if ( pnetWriteTo( ps, arpHost , buf, total_len ) != total_len ) { fprintf( stderr, "Error sending ND request.\n" ); pnetClose( ps ); return -1; } pnetSockAddReadcallback( ps, arp6_nd_recv, NULL ); if ( pnetListen( ps, NULL ) || pnetStartListen( 0 ) ) printf("Can't establish listen socket \n"); pnetClose( ps ); return 0; }