Академический Документы
Профессиональный Документы
Культура Документы
1 Objective
2 Background
3 Scenario
4 Method
4.1 Overview
5 Example program
6 Alternatives
7 Further reading
Tested on
Objective
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.
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.
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.
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.
Example program
The following example program constructs and sends an ARP request using the
method described above:
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:
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.
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;
}