AF_PACKET
socketEthernet 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:
eth0
to the broadcast MAC adddress.(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.)
The method described here has five steps:
AF_PACKET
socket.The following header files are used:
Header | Used by |
---|---|
<errno.h> |
errno |
<string.h> |
memcpy , strerror , strlen |
<arpa/inet.h> |
in_addr_t , htons |
<net/ethernet.h> |
ETHER_ADDR_LEN , ETH_P_* |
<net/if.h> |
struct ifreq |
<netinet/if_ether.h> |
struct ether_arp |
<netpacket/packet.h> |
struct sockaddr_ll |
<sys/ioctl.h> |
SIOCGIFINDEX , ioctl |
<sys/socket.h> |
struct sockaddr , struct iovec , struct msghdr , AF_PACKET , SOCK_DGRAM , socket ,sendto , sendmsg |
AF_PACKET
sockets are specific to Linux. Programs that make use of them need elevated privileges in order to run.
Setting SO_BROADCAST
does not appear to be necessary when sending broadcast frames using an AF_PACKET
socket. Some programs do so anyway, which is unlikely to be harmful, and could be considered a worthwhile hedge against any future change in behaviour.
The EtherType of an Ethernet frame specifies the type of payload that it contains. There are several sources from which EtherTypes can be obtained:
<netinet/if_ether.h>
provides constants for most commonly-used EtherTypes. Examples includeETH_P_IP
for the Internet Protocol (0x8000
), ETH_P_ARP
for the Address Resolution Protocol (0x0806
) andETH_P_8021Q
for IEEE 802.1Q VLAN tags (0x8100
).The wildcard value ETH_P_ALL
allows any EtherType to be received without using multiple sockets. This includes EtherTypes that are handled by the kernel, such as IP and ARP.
If you need an EtherType for experimental or private use then the values 0x88b5
and 0x88b6
have been reserved for that purpose.
The socket that will be used to send the Ethernet frame should be created using the socket
function. This takes three arguments:
AF_PACKET
for a packet socket);SOCK_DGRAM
if you want the Ethernet header to be constructed for you or SOCK_RAW
if you want to construct it yourself); andIn this instance the socket will be used for sending (and presumably also receiving) ARP requests, therefore the third argument should be set to htons(ETH_P_ARP)
(or equivalently, htons(0x0806)
). There is no need to construct a custom Ethernet header so the second argument should be set to SOCK_DGRAM
:
int fd=socket(AF_PACKET,SOCK_DGRAM,htons(ETH_P_ARP)); if (fd==-1) { die("%s",strerror(errno)); }
Network interfaces are usually identified by name in user-facing contexts, but for some low-level APIs like the one used here a number is used instead. You can obtain the index from the name by means of the ioctl
command SIOCGIFINDEX
:
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 { die("interface name is too long"); } if (ioctl(fd,SIOCGIFINDEX,&ifr)==-1) { die("%s",strerror(errno)); } int ifindex=ifr.ifr_ifindex;
For further details of this method see the microHOWTO Get the index number of a Linux network interface in C usingSIOCGIFINDEX
.
To send a frame using an AF_PACKET
socket its destination must be given in the form of a sockaddr_ll
structure. The fields that you need to specify are sll_family
, sll_addr
, sll_halen
, sll_ifindex
and sll_protocol
. The remainder should be zeroed:
const unsigned char ether_broadcast_addr[]= {0xff,0xff,0xff,0xff,0xff,0xff}; struct sockaddr_ll addr={0}; addr.sll_family=AF_PACKET; addr.sll_ifindex=ifindex; addr.sll_halen=ETHER_ADDR_LEN; addr.sll_protocol=htons(ETH_P_ARP); memcpy(addr.sll_addr,ether_broadcast_addr,ETHER_ADDR_LEN);
(At the time of writing, the manpage packet(7) stated that only sll_family
, sll_addr
, sll_halen
and sll_ifindex
need be provided when sending. This is incorrect. The EtherType specified when opening the socket is used for filtering inbound packets but not for constructing outbound ones.)
Frames can in principle be sent using any function that is capable of writing to a file descriptor, however if you have opted for the link-layer header to be constructed automatically then it will be necessary to use either sendto
or sendmsg
so that a destination address can be specified. Of these sendmsg
is the more flexible option, but at the cost of a significantly more complex interface. Details of each function are given below.
Regardless of which function you choose, each function call will result in a separate datagram being sent. For this reason you must either compose each datagram payload as a single, contiguous block of memory, or make use of the scatter/gather capability provided by sendmsg
.
In this particular scenario the payload to be sent is an ARP request. For completeness, here is an example of how such a payload might be constructed:
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));
You will need to set req.arp_tpa
to contain the IP address (in network byte order) for which you want to find the corresponding MAC address. For example, starting from a string in dotted quad format:
const char* target_ip_string="192.168.0.83"; struct in_addr target_ip_addr={0}; if (!inet_aton(target_ip_string,&target_ip_addr)) { die("%s is not a valid IP address",target_ip_string); } memcpy(&req.arp_tpa,&target_ip_addr.s_addr,sizeof(req.arp_tpa));
You will also need to set source_ip_addr
and source_hw_addr
to contain the IP and MAC addresses of the interface from which the request will be sent (in network byte order). See the microHOWTOs Get the IP address of a network interface in C using SIOCGIFADDR and Get the MAC address of an Ethernet interface in C using SIOCGIFHWADDR for details of how to obtain these given the interface name.
To call sendto
you must supply the content of the frame and the remote address to which it should be sent:
if (sendto(fd,&req,sizeof(req),0,(struct sockaddr*)&addr,sizeof(addr))==-1) { die("%s",strerror(errno)); }
The fourth argument is for specifying flags which modify the behaviour of sendto
, none of which are needed in this example.
The value returned by sendto
is the number of bytes sent, or -1 if there was an error. AF_PACKET
frames are sent atomically, so unlike when writing to a TCP socket there is no need to wrap the function call in a loop to handle partially-sent data.
To call sendmsg
, in addition to the datagram content and remote address you must also construct an iovec
array and amsghdr
structure:
struct iovec iov[1]; iov[0].iov_base=&req; iov[0].iov_len=sizeof(req); struct msghdr message; message.msg_name=&addr; message.msg_namelen=sizeof(addr); message.msg_iov=iov; message.msg_iovlen=1; message.msg_control=0; message.msg_controllen=0; if (sendmsg(fd,&message,0)==-1) { die("%s",strerror(errno)); }
The purpose of the iovec
array is to provide a scatter/gather capability so that the datagram payload need not be stored in a contiguous region of memory. In this example the entire payload is stored in a single buffer, therefore only one array element is needed.
The msghdr
structure exists to bring the number of arguments to recvmsg
and sendmsg
down to a managable number. On entry to sendmsg
it specifies where the destination address, the datagram payload and any ancillary data are stored. In this example no ancillary data has been provided.
If you wish to pass any flags into sendmsg
then this cannot be done using msg_flags
, which is ignored on entry. Instead you must pass them using the third argument to sendmsg
(which is zero in this example).
See: | Send an arbitrary Ethernet frame using libpcap |
libpcap is a cross-platform library for capturing traffic from network interfaces. It also has the ability to send, so provides broadly the same functionality as a packet socket (and on Linux, is implemented using a packet socket).
The main advantage of using libpcap is that it abstracts away differences between the operating systems that it supports, thereby allowing relatively portable code to be written. This involves some loss of functionality, and that may make libpcap unsuitable for use in some circumstances, but otherwise it is recommended in preference to AF_PACKET
sockets on the grounds of portability.
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:
AF_PACKET
sockets are specific to Linux.For these reasons, use of a raw socket is recommended unless you specifically need the extra functionality provided by working at the link layer.