17.11. MAC Address Resolution
An
interesting issue with Ethernet communication is how to associate the
MAC addresses (the interface's unique hardware ID)
with the IP number. Most protocols have a similar problem, but we
concentrate on the Ethernet-like case here. We try to offer a
complete description of the issue, so we show three situations: ARP,
Ethernet headers without ARP (such as plip), and
non-Ethernet headers.
17.11.1. Using ARP with Ethernet
The usual way to deal with address
resolution is by using the Address Resolution Protocol (ARP).
Fortunately, ARP is managed by the kernel, and an Ethernet interface
doesn't need to do anything special to support ARP.
As long as dev->addr and
dev->addr_len are correctly assigned at open
time, the driver doesn't need to worry about
resolving IP numbers to MAC addresses;
ether_setup assigns the correct device methods
to dev->hard_header and
dev->rebuild_header.
Although the kernel normally handles the details of address
resolution (and caching of the results), it calls upon the interface
driver to help in the building of the packet. After all, the driver
knows about the details of the physical layer header, while the
authors of the networking code have tried to insulate the rest of the
kernel from that knowledge. To this end, the kernel calls the
driver's
hard_header
method to lay out the packet with the results of the ARP query.
Normally, Ethernet driver writers need not know about this
process—the common Ethernet code takes care of everything.
17.11.2. Overriding ARP
Simple point-to-point network
interfaces, such as plip, might benefit from
using Ethernet headers, while avoiding the overhead of sending ARP
packets back and forth. The sample code in snull
also falls into this class of network devices.
snull cannot use ARP because the driver changes
IP addresses in packets being transmitted, and ARP packets exchange
IP addresses as well. Although we could have implemented a simple ARP
reply generator with little trouble, it is more illustrative to show
how to handle physical-layer headers directly.
If
your device wants to use the usual hardware header without running
ARP, you need to override the default
dev->hard_header method. This is how
snull implements it, as a very short function:
int snull_header(struct sk_buff *skb, struct net_device *dev,
unsigned short type, void *daddr, void *saddr,
unsigned int len)
{
struct ethhdr *eth = (struct ethhdr *)skb_push(skb,ETH_HLEN);
eth->h_proto = htons(type);
memcpy(eth->h_source, saddr ? saddr : dev->dev_addr, dev->addr_len);
memcpy(eth->h_dest, daddr ? daddr : dev->dev_addr, dev->addr_len);
eth->h_dest[ETH_ALEN-1] ^= 0x01; /* dest is us xor 1 */
return (dev->hard_header_len);
}
The function simply takes the information provided by the kernel and
formats it into a standard Ethernet header. It also toggles a bit in
the destination Ethernet address, for reasons described later.
When a packet is received by the interface, the hardware header is
used in a couple of ways by eth_type_trans. We
have already seen this call in snull_rx:
skb->protocol = eth_type_trans(skb, dev);
The function extracts the protocol identifier
(ETH_P_IP, in this case) from the Ethernet header;
it also assigns skb->mac.raw, removes the
hardware header from packet data (with
skb_pull), and sets
skb->pkt_type. This last item defaults to
PACKET_HOST at skb allocation
(which indicates that the packet is directed to this host), and
eth_type_trans changes it to reflect the
Ethernet destination address: if that address does not match the
address of the interface that received it, the
pkt_type field is set to
PACKET_OTHERHOST. Subsequently, unless the
interface is in promiscuous mode or packet forwarding is enabled in
the kernel, netif_rx drops any packet of type
PACKET_OTHERHOST. For this reason,
snull_header is careful to make the destination
hardware address match that of the
"receiving" interface.
If your interface is a point-to-point link, you
won't want to receive unexpected multicast packets.
To avoid this problem, remember that a destination address whose
first octet has 0 as the least significant bit
(LSB) is directed to a single host (i.e., it is either
PACKET_HOST or
PACKET_OTHERHOST). The plip
driver uses 0xfc as the first octet of its
hardware address, while snull uses
0x00. Both addresses result in a working
Ethernet-like point-to-point link.
17.11.3. Non-Ethernet Headers
We have just seen that the hardware
header contains some information in addition to the destination
address, the most important being the communication protocol. We now
describe how hardware headers can be used to encapsulate relevant
information. If you need to know the details, you can extract them
from the kernel sources or the technical documentation for the
particular transmission medium. Most driver writers are able to
ignore this discussion and just use the Ethernet implementation.
It's worth noting that not all information has to be
provided by every protocol. A point-to-point link such as
plip or snull could avoid
transferring the whole Ethernet header without losing generality. The
hard_header device method, shown earlier as
implemented by snull_header, receives the
delivery information—both protocol-level and hardware
addresses—from the kernel. It also receives the 16-bit protocol
number in the type argument; IP, for example, is
identified by ETH_P_IP. The driver is expected to
correctly deliver both the packet data and the protocol number to the
receiving host. A point-to-point link could omit addresses from its
hardware header, transferring only the protocol number, because
delivery is guaranteed independent of the source and destination
addresses. An IP-only link could even avoid transmitting any hardware
header whatsoever.
When the packet is picked up at the other end of the link, the
receiving function in the driver should correctly set the fields
skb->protocol,
skb->pkt_type, and
skb->mac.raw.
skb->mac.raw is a char pointer used by the
address-resolution mechanism implemented in higher layers of the
networking code (for instance, net/ipv4/arp.c).
It must point to a machine address that matches
dev->type. The possible values for the device
type are defined in <linux/if_arp.h>;
Ethernet interfaces use ARPHRD_ETHER. For example,
here is how eth_type_trans deals with the
Ethernet header for received packets:
skb->mac.raw = skb->data;
skb_pull(skb, dev->hard_header_len);
In the simplest case (a point-to-point link with no headers),
skb->mac.raw can point to a static buffer
containing the hardware address of this interface,
protocol can be set to
ETH_P_IP, and packet_type can
be left with its default value of PACKET_HOST.
Because every hardware type is unique, it is hard to give more
specific advice than already discussed. The kernel is full of
examples, however. See, for example, the AppleTalk driver
(drivers/net/appletalk/cops.c), the infrared
drivers (such as drivers/net/irda/smc_ircc.c),
or the PPP driver (drivers/net/ppp_generic.c).
|