Вы находитесь на странице: 1из 9

System

Send an arbitrary Ethernet frame using libpcap


Content

1 Objective

2 Background

3 Scenario

4 Method

4.1 Overview

4.2 Select the required EtherType

4.3 Construct the Ethernet frame

4.4 Obtain a PCAP descriptor by calling pcap_open_live

4.5 Send the Ethernet frame by calling pcap_inject

4.6 Close the PCAP descriptor by calling pcap_close

5 Example program

6 Alternatives

6.1 Using an AF_PACKET socket

6.2 Using a raw socket

7 Further reading

Tested on

Debian (Lenny, Squeeze)


Ubuntu (Lucid)

Objective

To send an arbitrary Ethernet frame using libpcap

Background
Ethernet is a link layer protocol. Most networking programs interact with the network
stack at the transport layer or above, so have no need to deal with Ethernet frames
directly, but there are some circumstances where interaction at a lower level may be
necessary. These include:
implementation of Ethernet-based protocols that are not built in to the network
stack, and
production of malformed or otherwise non-standard frames for testing
purposes.

Scenario
Suppose that you wish to send an ARP request for a given IP address from a given
Ethernet interface. You wish to use libpcap to perform the sending.
(ARP is the Address Resolution Protocol. It is used when a host needs to send a
datagram to a given IP address, but does not know which MAC address corresponds
to that IP address. It is described in RFC 826.)

Method
Overview
The method described here has five steps:
1. Select the required EtherType.
2. Construct the Ethernet frame.
3. Obtain a PCAP descriptor by calling pcap_open_live.
4. Send the Ethernet frame by calling pcap_inject.
5. Close the PCAP descriptor by calling pcap_close.
The following header files are used:

Header
<stdio.h>
<stdlib.h>
<pcap.h>

Used by
fprintf
exit
pcap_open_live, pcap_inject, pcap_close, pcap_perror

Be aware that:
Not all network devices are Ethernet interfaces, or use an Ethernet-compatible
frame format, or support packet injection using libpcap.
Although a link-layer header must be supplied, libpcap does not promise to use
it as-is: both the source address and the EtherType are at risk of being altered.
Programs that send raw packets, using this or any other method, are likely to require
elevated privileges in order to run.

Select the required EtherType


The EtherType of an Ethernet frame specifies the type of payload that it contains.
There are several sources from which EtherTypes can be obtained:
On Linux-based systems the header file <linux/if_ether.h> provides constants
for most commonly-used EtherTypes. Examples include ETH_P_IP for the
Internet Protocol (0x8000), ETH_P_ARP for the Address Resolution Protocol
(0x0806) and ETH_P_8021Q for IEEE 802.1Q VLAN tags (0x8100).
The IEEE maintains the definitive list of registered EtherTypes.
A semi-official list is maintained by IANA.
If you need an EtherType for experimental or private use then the
values 0x88b5 and 0x88b6 have been reserved for that purpose.

Construct the Ethernet frame


Frames sent using libpcap must:
have a link-layer header (there is no option for this to be added automatically),
and
be presented to libpcap as a single, contiguous block of memory (there is no
equivalent of the scatter/gather capability provided by readmsg and sendmsg).

See the example program below for how this might be done in the specific case where
you want to send an ARP request. Be aware that:
Most network protocols require that multi-byte values be converted to network
byte order.
Structures may have padding added by the compiler (although ones provided
by system headers ought to be safe).
C and C++ place restrictions on when pointer casts can be safely used to
convert data from one type to another.
You will probably need to know the MAC address of the interface from which the
packet will be sent. On Linux-based systems this can be obtained using
the ioctl command SIOCGIFHWADDR. See the microHOWTO Get the MAC address of
an Ethernet interface in C using SIOCGIFHWADDR for details.
As noted previously, libpcap does not provide guarantee that the link-layer header that
is sent will be identical to the one that was provided.

Obtain a PCAP descriptor by calling pcap_open_live


To access a network interface via libpcap it is necessary to have an open packet
capture descriptor. This is a pointer of type pcap_t* and can be obtained by
calling pcap_open_live:
char pcap_errbuf[PCAP_ERRBUF_SIZE];
pcap_errbuf[0]='\0';
pcap_t* pcap=pcap_open_live(if_name,96,0,0,pcap_errbuf);
if (pcap_errbuf[0]!='\0') {
fprintf(stderr,"%s",pcap_errbuf);
}
if (!pcap) {
exit(1);
}

The first argument to pcap_open_live is the name of the interface from which the
Ethernet frame is to be sent, for example eth0. (Remember that not all interfaces are
suitable for sending Ethernet frames.)
The second, third and fourth arguments are the snapshot length, promiscuous mode
flag and timeout. These control how packets are captured, and for the task in hand it is
unimportant what values are used, but if you want to capture as well as send then you

will need to ensure that they have been set appropriately (especially the snapshot
length).
The last argument points to a buffer for returning error messages, which must be at
least PCAP_ERRBUF_SIZE bytes long. As suggested on thepcap_open_live manpage, this
has been set to the empty string before the function call then inspected afterwards in
order to detect both warnings and errors.

Send the Ethernet frame by calling pcap_inject


Given a PCAP descriptor, frames can be sent by calling the function pcap_inject:
if (pcap_inject(pcap,&req,sizeof(req))==-1) {
pcap_perror(pcap,0);
pcap_close(pcap);
exit(1);
}

The value returned by pcap_inject is the number of bytes sent, or -1 if there was an
error. In the latter case a human-readable error message can be obtained
using pcap_geterr or (as in this example) printed using pcap_perror.

Close the PCAP descriptor by calling pcap_close


The PCAP descriptor should be closed once it is no longer needed:
pcap_close(pcap)

Example program
The following example program constructs and sends an ARP request using the
method described above:
send_arp.c

See end of article for full source file.

It can be compiled using the command:


gcc -lpcap -o send_arp send_arp.c

When invoked it takes two arguments, the name of the Ethernet interface and the
(numeric) IP address to which the ARP request should be directed:
./send_arp eth0 192.168.0.83

Alternatives
Using an AF_PACKET socket
See:

Send an arbitrary Ethernet frame using an AF_PACKET socket in C

On Linux-based systems an alternative way to send an Ethernet frame is to use


an AF_PACKET socket. This has some advantages over the use of libpcap:
It allows packets to be written directly to a POSIX socket descriptor, making it
possible to use facilities such as scatter/gather and non-blocking output, and
providing compatibility with libraries like libevent that act on file descriptors.
It offers a choice between having the link-layer header supplied by the sender
or constructed by the network stack.
It removes a layer of indirection, and the need for libpcap to be present at
compile time or run time.
The main drawback of AF_PACKET sockets their lack of portability. They are specific to
Linux (version 2.2 and later), and for this reason they are not recommended where the
use of libpcap (or a raw socket) is a viable alternative.

Using a raw socket


See:

Send an arbitrary IPv4 datagram using a raw socket in C

Raw sockets differ from packet sockets in that they operate at the network layer as
opposed to the link layer. For this reason they are limited to network protocols for
which raw socket support has been explicitly built into the network stack, but they
also have a number of advantages which result from operating at a higher level of
abstraction:
You can write code that will work with any suitable type of network interface.

Routing and link-layer address resolution are handled for you.


The network layer header is constructed for you unless you request otherwise.
The raw socket API has been partially standardised by POSIX.
For these reasons, use of a raw socket is recommended unless you specifically need
the extra functionality provided by working at the link layer.

Further reading
PCAP(3) (libpcap manpage)
Full Source below for sending via libpcap:
// Purpose: to construct an ARP request and write it to an Ethernet interface
// using libpcap.
//
// See: "Send an arbitrary Ethernet frame using libpcap"
//
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pcap.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <net/ethernet.h>
#include <netinet/if_ether.h>
#include <sys/ioctl.h>
int main(int argc,const char* argv[]) {
// Get interface name and target IP address from command line.
if (argc<2) {
fprintf(stderr,"usage: send_arp <interface> <ipv4-address>\n");
exit(1);
}
const char* if_name=argv[1];
const char* target_ip_string=argv[2];
// Construct Ethernet header (except for source MAC address).
// (Destination set to broadcast address, FF:FF:FF:FF:FF:FF.)
struct ether_header header;
header.ether_type=htons(ETH_P_ARP);
memset(header.ether_dhost,0xff,sizeof(header.ether_dhost));
// Construct ARP request (except for MAC and IP addresses).
struct ether_arp req;
req.arp_hrd=htons(ARPHRD_ETHER);
req.arp_pro=htons(ETH_P_IP);
req.arp_hln=ETHER_ADDR_LEN;
req.arp_pln=sizeof(in_addr_t);

req.arp_op=htons(ARPOP_REQUEST);
memset(&req.arp_tha,0,sizeof(req.arp_tha));
// Convert target IP address from string, copy into ARP request.
struct in_addr target_ip_addr={0};
if (!inet_aton(target_ip_string,&target_ip_addr)) {
fprintf(stderr,"%s is not a valid IP address",target_ip_string);
exit(1);
}
memcpy(&req.arp_tpa,&target_ip_addr.s_addr,sizeof(req.arp_tpa));
// Write the interface name to an ifreq structure,
// for obtaining the source MAC and IP addresses.
struct ifreq ifr;
size_t if_name_len=strlen(if_name);
if (if_name_len<sizeof(ifr.ifr_name)) {
memcpy(ifr.ifr_name,if_name,if_name_len);
ifr.ifr_name[if_name_len]=0;
} else {
fprintf(stderr,"interface name is too long");
exit(1);
}
// Open an IPv4-family socket for use when calling ioctl.
int fd=socket(AF_INET,SOCK_DGRAM,0);
if (fd==-1) {
perror(0);
exit(1);
}
// Obtain the source IP address, copy into ARP request
if (ioctl(fd,SIOCGIFADDR,&ifr)==-1) {
perror(0);
close(fd);
exit(1);
}
struct sockaddr_in* source_ip_addr = (struct sockaddr_in*)&ifr.ifr_addr;
memcpy(&req.arp_spa,&source_ip_addr->sin_addr.s_addr,sizeof(req.arp_spa));
// Obtain the source MAC address, copy into Ethernet header and ARP
request.
if (ioctl(fd,SIOCGIFHWADDR,&ifr)==-1) {
perror(0);
close(fd);
exit(1);
}
if (ifr.ifr_hwaddr.sa_family!=ARPHRD_ETHER) {
fprintf(stderr,"not an Ethernet interface");
close(fd);
exit(1);
}
const unsigned char* source_mac_addr=(unsigned
char*)ifr.ifr_hwaddr.sa_data;
memcpy(header.ether_shost,source_mac_addr,sizeof(header.ether_shost));
memcpy(&req.arp_sha,source_mac_addr,sizeof(req.arp_sha));
close(fd);

// Combine the Ethernet header and ARP request into a contiguous block.
unsigned char frame[sizeof(struct ether_header)+sizeof(struct ether_arp)];
memcpy(frame,&header,sizeof(struct ether_header));
memcpy(frame+sizeof(struct ether_header),&req,sizeof(struct ether_arp));
// Open a PCAP packet capture descriptor for the specified interface.
char pcap_errbuf[PCAP_ERRBUF_SIZE];
pcap_errbuf[0]='\0';
pcap_t* pcap=pcap_open_live(if_name,96,0,0,pcap_errbuf);
if (pcap_errbuf[0]!='\0') {
fprintf(stderr,"%s\n",pcap_errbuf);
}
if (!pcap) {
exit(1);
}
// Write the Ethernet frame to the interface.
if (pcap_inject(pcap,frame,sizeof(frame))==-1) {
pcap_perror(pcap,0);
pcap_close(pcap);
exit(1);
}
// Close the PCAP descriptor.
pcap_close(pcap);
return 0;
}

Вам также может понравиться