Please wait until the page is fully downloaded and then press the "Expand" button or the blue line numbers.

0020001 /*
0020002 arp.c
0020003 
0020004 Copyright 1995 Philip Homburg
0020005 */
ARP OVERVIEW:

In order for two hosts on an ethernet segment to communicate using the IP protocol, the logical IP address must be linked to the physical address of the network adapter card. ARP (Address Resolution Protocol) is the protocol that resolves IP addresses to physical addresses (i.e., it maps a MAC address to an IP address).

Note that ARP data packets do not have IP headers since the ARP protocol is at the same layer in the OSI model as the IP protocol.

Here is an example that shows how ARP works:

Suppose a packet is sent from 138.16.14.190/255.255.255.0 to 138.16.14.1/255.255.255.0. First, 138.16.14.190 verifies that 138.16.14.1 is in the same network. Since they are in the same network, the two systems are connected by ethernet. 138.16.14.190 then looks up 138.16.14.1 in its ARP cache (i.e., ARP table). If an entry exists for 138.16.14.1, an ethernet header is prepended to the packet and the ethernet packet is sent off.

However, if an entry for 138.16.14.1 is not in the ARP table, the corresponding ethernet address is not known and, therefore, there is no way for 138.16.14.190 to send the packet. So 138.16.14.190 sends out an ARP request packet in an attempt to determine this ethernet address. 138.16.14.1 will then see this ARP request packet and send an ARP reply packet indicating its ethernet address. After receiving the ARP reply packet, 138.16.14.190 will use this information and an ethernet header containing this ethernet address is prepended to the packet and the ethernet packet is sent off.

138.16.14.190 creates an entry for this ip address/ethernet address mapping in the ARP table so that it can send ethernet packets to 138.16.14.1 without sending out an ARP request packet first. (Other systems on the ethernet segment that receive this ARP reply will also add an entry to their ARP tables.) Note, however, that entries in the ARP table time out (in the Minix network service, the time out is 20 minutes) and 138.16.14.190 will eventually need to send another ARP request packet to determine 138.16.14.190 associated ethernet address.


0020006 
0020007 #include "inet.h"
0020008 #include "type.h"
0020009 
0020010 #include "arp.h"
0020011 #include "assert.h"
0020012 #include "buf.h"
0020013 #include "clock.h"
0020014 #include "eth.h"
0020015 #include "io.h"
0020016 #include "sr.h"
0020017 
0020018 THIS_FILE
0020019 
0020020 #define ARP_CACHE_NR       64
0020021 
0020022 #define MAX_ARP_RETRIES              5
0020023 #define ARP_TIMEOUT              (HZ/2+1)       /* .5 seconds */
0020024 #ifndef ARP_EXP_TIME
0020025 #define ARP_EXP_TIME              (20L*60L*HZ)       /* 20 minutes */
0020026 #endif
0020027 #define ARP_NOTRCH_EXP_TIME       (5*HZ)              /* 5 seconds */
0020028 #define ARP_INUSE_OFFSET       (60*HZ)       /* an entry in the cache can be deleted
0020029                                               if its not used for 1 minute */
0020030 
0020031 typedef struct arp46
0020032 {
0020033          ether_addr_t a46_dstaddr;
0020034          ether_addr_t a46_srcaddr;
0020035          ether_type_t a46_ethtype;
0020036          union
0020037          {
0020038                   struct
0020039                   {
0020040                            u16_t a_hdr, a_pro;
0020041                            u8_t a_hln, a_pln;
0020042                            u16_t a_op;
0020043                            ether_addr_t a_sha;
0020044                            u8_t a_spa[4];
0020045                            ether_addr_t a_tha;
0020046                            u8_t a_tpa[4];
0020047                   } a46_data;
0020048                   char a46_dummy[ETH_MIN_PACK_SIZE-ETH_HDR_SIZE];
0020049          } a46_data;
0020050 } arp46_t;
arp46_t

The arp46_t struct is the structure of the arp-request and arp-reply packets. The information contained within the arp-reqests and arp-replies is used to build the arp table. The first three fields in the struct (a46_dstaddr, a46_srcaddr, and a46_ethtype) correspond to an ethernet header and the remaining fields are arp-specific.

"46" refers to the number of bytes in the packet - 16 for the ethernet header and 28 for the arp-specific fields.

typedef struct arp46
{
ether_addr_t a46_dstaddr;
ether_addr_t a46_srcaddr;
ether_type_t a46_ethtype;
union
{
struct
{
u16_t a_hdr, a_pro;
u8_t a_hln, a_pln;
u16_t a_op;
ether_addr_t a_sha;
u8_t a_spa[4];
ether_addr_t a_tha;
u8_t a_tpa[4];
} a46_data;
char a46_dummy[ETH_MIN_PACK_SIZE-ETH_HDR_SIZE];
} a46_data;
} arp46_t;




From RFC 2625:

+-------------------------+
| HW Type | 2 bytes
+-------------------------+
| Protocol | 2 bytes
+-------------------------+
| HW Addr Length | 1 byte
+-------------------------+
| Protocol Addr Length | 1 byte
+-------------------------+
| Op Code | 2 bytes
+-------------------------+
| HW Addr of Sender | 6 bytes
+-------------------------+
| Protocol Addr of Sender | 4 bytes
+-------------------------+
| HW Addr of Target | 6 bytes
+-------------------------+
| Protocol Addr of Target | 4 bytes
+-------------------------+
Total 28 bytes
ARP Packet Format



+---------+-------------------------------------------------------+
|Ethernet |For an ARP request, source MAC address is the MAC |
|Header |address of the host sending the ARP request, |
| |destination MAC address is the Ethernet broadcast |
| |address (FF:FF:FF:FF:FF:FF), frame type field is 0x806.|
| |For ARP reply, source MAC address is the MAC address of|
| |the host replying to the ARP request, destination MAC |
| |address is the MAC address of the host that sent the |
| |ARP request, and the frame type field is 0x806. |
+---------+-------------------------------------------------------+
|Hardware |Type of the hardware MAC address which is being mapped.|
|Address |For Ethernet the value of this field is 1. |
|Type | |
+---------+-------------------------------------------------------+
|Protocol |Type of the protocol address to which the MAC address |
|Address |is mapped. For IP address the value of this field is |
|Type |0x800. |
+---------+-------------------------------------------------------+
|Hardware |Size of the hardware MAC address. For Ethernet, the |
|Address |value of this field is 6. |
|Size | |
+---------+-------------------------------------------------------+
|Protocol |Size of the protocol address. For IP, the value of |
|Address |this field is 4. |
|Size | |
+---------+-------------------------------------------------------+
|Operation|Type of operation being performed. The value of this |
| |field can be 1 (ARP request), 2 (ARP reply) |
+---------+-------------------------------------------------------+
|Source |The hardware MAC address of the host sending the ARP |
|MAC |request or reply. This is same as the source MAC |
|address |address present in the Ethernet header. |
+---------+-------------------------------------------------------+
|Source |The IP address of the host sending the ARP request or |
|IP |reply. |
|address | |
+---------+-------------------------------------------------------+
|Target |The hardware MAC address of the host receiving the ARP |
|MAC |request or reply. This is same as the destination MAC |
|address |address present in the Ethernet header. |
+---------+-------------------------------------------------------+
|Target |The IP address of the host receiving the ARP request |
|IP |or reply. |
|address | |
+---------+-------------------------------------------------------+





0020051 
0020052 #define a46_hdr a46_data.a46_data.a_hdr
0020053 #define a46_pro a46_data.a46_data.a_pro
0020054 #define a46_hln a46_data.a46_data.a_hln
0020055 #define a46_pln a46_data.a46_data.a_pln
0020056 #define a46_op a46_data.a46_data.a_op
0020057 #define a46_sha a46_data.a46_data.a_sha
0020058 #define a46_spa a46_data.a46_data.a_spa
0020059 #define a46_tha a46_data.a46_data.a_tha
0020060 #define a46_tpa a46_data.a46_data.a_tpa
0020061 
0020062 typedef struct arp_port
0020063 {
0020064          int ap_flags;
0020065          int ap_state;
0020066          int ap_eth_port;
0020067          int ap_ip_port;
0020068          int ap_eth_fd;
0020069          ether_addr_t ap_ethaddr;
0020070          ipaddr_t ap_ipaddr;
0020071          timer_t ap_timer;
0020072 
0020073          ether_addr_t ap_write_ethaddr;
0020074          ipaddr_t ap_write_ipaddr;
0020075          int ap_write_code;
0020076 
0020077          ipaddr_t ap_req_ipaddr;
0020078          int ap_req_count;
0020079 
0020080          arp_func_t ap_arp_func;
0020081 } arp_port_t;
arp_port

For each ip port that uses an ethernet port (as opposed to a psip port), there will be one arp port.

typedef struct arp_port

{
int ap_flags;
int ap_state;
int ap_eth_port;
int ap_ip_port;
int ap_eth_fd;
ether_addr_t ap_ethaddr;
ipaddr_t ap_ipaddr;
timer_t ap_timer;

ether_addr_t ap_write_ethaddr;
ipaddr_t ap_write_ipaddr;
int ap_write_code;

ipaddr_t ap_req_ipaddr;
int ap_req_count;

arp_func_t ap_arp_func;
} arp_port_t;
int ap_flags:

ap_flags can be one or more of the following:

#define APF_EMPTY 0
#define APF_ARP_RD_IP 0x4
#define APF_ARP_RD_SP 0x8
#define APF_ARP_WR_IP 0x10
#define APF_ARP_WR_SP 0x20
#define APF_INADDR_SET 0x100
#define APF_MORE2WRITE 0x200
#define APF_CLIENTREQ 0x400
#define APF_CLIENTWRITE 0x1000
#define APF_SUSPEND 0x2000

Most of the flags are used to either indicate the state of the arp port or the state of a current read or write and are therefore somewhat uninteresting. An exception is APF_CLIENTREQ; APF_CLIENTREQ is set if an arp-request packet has been sent out but a reply has not yet been received.

int ap_state:

#define APS_INITIAL 0x00
#define APS_GETADDR 0x01
#define APS_ARPSTART 0x10
#define APS_ARPPROTO 0x20
#define APS_ARPMAIN 0x40
#define APS_ERROR 0x80

APS_ARPMAIN is the arp port's normal operational state. All states except for APS_ERROR are initialization states.


int ap_eth_port, ap_ip_port:

ap_eth_port and ap_ip_port are the ip port and the ip port's associated ethernet port and are the ports that receive the arp broadcasts that detail the ip address to ethernet port mappings.


int ap_eth_fd:

The index of the arp port's associated ethernet file descriptor within eth_fd_table[].


ether_addr_t ap_ethaddr:

The ethernet address of the underlying ethernet port.


ipaddr_t ap_ipaddr:

The ip address of the associated ip port.


timer_t ap_timer:

When an arp-request is sent out, a timer is set (to which ap_timer points). If an arp-reply is not received for this arp-request within a half second, arp_timeout() is called. If fewer than four arp-requests have already been sent out, arp_timeout() simply calls setup_write() to send out another arp-request. If four arp-requests have already been sent out, the arp table entry is marked as unreachable and client_reply() is called to alert interested ip file descriptors.


ether_addr_t ap_write_ethaddr:
ipaddr_t ap_write_ipaddr:
int ap_write_code:

When an arp-reply or an arp-request packet is being created, the values of these 3 fields are used to get the target ethernet and ip addresses and the write code (either ARP_REQUEST or ARP_REPLY).


ipaddr_t ap_req_ipaddr:

If an entry for a destination ip address cannot be found in the arp table, this ip address is placed in the ap_req_ipaddr field. When the arp port is able to send out the arp request, the ap_req_ipaddr field is copied to the ap_write_ipaddr field.


int ap_req_count:

Four arp-requests are allowed to resolve the ip address held in the ap_req_ipaddr field.


arp_func_t ap_arp_func:

ap_arp_func is set to ipeth_arp_reply().


0020082 
0020083 #define APF_EMPTY       0
0020084 #define APF_ARP_RD_IP       0x4
0020085 #define APF_ARP_RD_SP       0x8
0020086 #define APF_ARP_WR_IP       0x10
0020087 #define APF_ARP_WR_SP       0x20
0020088 #define APF_INADDR_SET       0x100
0020089 #define APF_MORE2WRITE       0x200
0020090 #define APF_CLIENTREQ       0x400
0020091 #define APF_CLIENTWRITE       0x1000
0020092 #define APF_SUSPEND       0x2000
0020093 
0020094 #define APS_INITIAL       0x00
0020095 #define       APS_GETADDR       0x01
0020096 #define       APS_ARPSTART       0x10
0020097 #define       APS_ARPPROTO       0x20
0020098 #define       APS_ARPMAIN       0x40
0020099 #define       APS_ERROR       0x80
0020100 
0020101 typedef struct arp_cache
0020102 {
0020103          int ac_flags;
0020104          int ac_state;
0020105          ether_addr_t ac_ethaddr;
0020106          ipaddr_t ac_ipaddr;
0020107          arp_port_t *ac_port;
0020108          time_t ac_expire;
0020109          time_t ac_lastuse;
0020110 } arp_cache_t;
arp_cache_t

Each entry in an arp table is of type arp_cache_t:

typedef struct arp_cache

{
int ac_flags;
int ac_state;
ether_addr_t ac_ethaddr;
ipaddr_t ac_ipaddr;
arp_port_t *ac_port;
time_t ac_expire;
time_t ac_lastuse;
} arp_cache_t;
int ac_flags:

ac_flags can be one of the following:

#define ACF_EMPTY 0
#define ACF_GOTREQ 1

When an arp-request is received, an entry in the arp table is created for the system that sent the arp-request (if an entry doesn't already exist) and, if the system was requesting the ethernet address that corresponds to the local system's ip address, the ACF_GOTREQ flag for the entry is set.


int ac_state:

The possible states for an arp table entry are:

#define ACS_UNUSED 0
#define ACS_INCOMPLETE 1
#define ACS_VALID 2
#define ACS_UNREACHABLE 3

ACS_INCOMPLETE indicates that the arp code has sent out an arp broadcast to determine the ethernet address that matches an ip address but a valid response has not yet been received. The other states are self-explanatory.


ether_addr_t ac_ethaddr: The ethernet address.


ipaddr_t ac_ipaddr: The ip address.


arp_port_t *ac_port: The arp port associated with the entry. If you have more than one ethernet port, there will be more than one arp port.


time_t ac_expire: The expiration time for the entry. If the system is found to be unreachable, the expiration time for the entry's unreachable status is 5 seconds.


time_t ac_lastuse: The time of the entry's last access. If the arp table is full and an entry is needed for a new ip address to ethernet mapping, the entry with the earliest ac_lastuse is claimed.



0020111 
0020112 #define ACF_EMPTY       0
0020113 #define ACF_GOTREQ       1
0020114 
0020115 #define ACS_UNUSED       0
0020116 #define ACS_INCOMPLETE       1
0020117 #define ACS_VALID       2
0020118 #define ACS_UNREACHABLE       3
0020119 
0020120 FORWARD acc_t *arp_getdata ARGS(( int fd, size_t offset,
0020121          size_t count, int for_ioctl ));
0020122 FORWARD int arp_putdata ARGS(( int fd, size_t offset,
0020123          acc_t *data, int for_ioctl ));
0020124 FORWARD void arp_main ARGS(( arp_port_t *arp_port ));
0020125 FORWARD void arp_timeout ARGS(( int fd, timer_t *timer ));
0020126 FORWARD void setup_write ARGS(( arp_port_t *arp_port ));
0020127 FORWARD void setup_read ARGS(( arp_port_t *arp_port ));
0020128 FORWARD void process_arp_req ARGS(( arp_port_t *arp_port, acc_t *data ));
0020129 FORWARD void client_reply ARGS(( arp_port_t *arp_port,
0020130          ipaddr_t ipaddr, ether_addr_t *ethaddr ));
0020131 FORWARD arp_cache_t *find_cache_ent ARGS(( arp_port_t *arp_port,
0020132          ipaddr_t ipaddr ));
0020133 FORWARD arp_cache_t *alloc_cache_ent ARGS(( void ));
0020134 
0020135 PRIVATE arp_port_t *arp_port_table;
0020136 PRIVATE       arp_cache_t arp_cache[ARP_CACHE_NR];
0020137 
0020138 PUBLIC void arp_prep()
arp_prep()

arp_prep() simply allocates memory for the arp port table. The arp ports are later initialized by arp_init().


0020139 {
0020140          arp_port_table= alloc(eth_conf_nr * sizeof(arp_port_table[0]));
eth_conf_nr was set in read_conf().

For the following inet.conf file:

eth0 DP8390 0 { default; };
psip1;

eth_conf_nr will be 1 since there is only one ethernet interface. eth_port_table[] will therefore consist of a single element which corresponds to the single ethernet interface.


0020141 }
0020142 
0020143 PUBLIC void arp_init()
arp_init()

arp_init() sets the ap_state field of all the arp ports to APS_ERROR. Later, when the ip code acquires an arp port, the ap_state field of the acquired arp port is set to APS_INITIAL.


0020144 {
0020145          arp_port_t *arp_port;
0020146          int i;
0020147 
0020148          assert (BUF_S >= sizeof(struct nwio_ethstat));
0020149          assert (BUF_S >= sizeof(struct nwio_ethopt));
0020150          assert (BUF_S >= sizeof(arp46_t));
0020151 
0020152          for (i=0, arp_port= arp_port_table; i<eth_conf_nr; i++, arp_port++)
0020153          {
0020154                   arp_port->ap_state= APS_ERROR;       /* Mark all ports as
0020155                                                * unavailable */
During the initialization of the ip code, ipeth_main() calls arp_set_cb() to acquire an arp port. arp_set_cb() sets the ap_state field of this acquired arp port to APS_INITIAL.


0020156          }
0020157 
0020158 }
0020159 
0020160 PRIVATE void arp_main(arp_port)
0020161 arp_port_t *arp_port;
arp_main()

arp_main(arp_port) is called in two places during the initialization of the network service. The first time arp_main() is called is by arp_set_cb(), which is in the ip code. During this call, arp_main() opens an ethernet file descriptor for the arp port arp_port, arp_main()'s only parameter and retrieves the ethernet address of the ethernet file descriptor's underlying ethernet port, with which it sets the arp port's ap_ethaddr field.

arp_set_ipaddr() calls arp_main() the second time. During this call, the arp table (which is also referred to as the arp cache) is initialized and the ethernet file descriptor previously opened is configured with values appropriate for an ethernet file descriptor supporting an arp port. After configuring the file descriptor, setup_read() is called.


0020162 {
0020163          int result;
0020164 
0020165          switch (arp_port->ap_state)
0020166          {
0020167          case APS_INITIAL:
0020168                   arp_port->ap_eth_fd= eth_open(arp_port->ap_eth_port,
0020169                            arp_port->ap_eth_port, arp_getdata, arp_putdata, 0);
eth_open()

eth_open(port, srfd, get_userdata, put_userdata, put_pkt) finds an ethernet file descriptor that is free and associates the file descriptor with an ethernet port whose index within eth_port_table[] is port, eth_open()'s first parameter.

eth_open() is called by the ip code, the arp code and is called if an ethernet device file (e.g., /dev/eth) is opened directly.

Here are the relationships between various file descriptors and ports:






0020170 
0020171                   if (arp_port->ap_eth_fd<0)
0020172                   {
0020173                            DBLOCK(1, printf("arp.c: unable to open ethernet\n"));
0020174                            return;
0020175                   }
0020176 
0020177                   arp_port->ap_state= APS_GETADDR;
0020178 
0020179                   result= eth_ioctl (arp_port->ap_eth_fd, NWIOGETHSTAT);
In this case, eth_ioctl() sets the ap_ethaddr field of the arp port to the ethernet address of the ethernet file descriptor's underlying ethernet port.


eth_ioctl()


The actions of eth_ioctl(fd, req) depend on req, eth_ioctl()'s second parameter:

NWIOSETHOPT (NetWork IO Set ETHernet OPTions):

If req is NWIOSETHOPT, eth_ioctl() configures the ethernet file descriptor fd (eth_ioctl()'s first parameter), which can then be used by a higher layer (e.g., ip, arp).

NWIOGETHSTAT (NetWork IO Get ETHernet STATs):

Only the arp code calls eth_ioctl() with the second parameter set to NWIOGETHSTAT. In this case, the ap_ethaddr field of the arp port is set to the ethernet address of the ethernet file descriptor's underlying ethernet port.


0020180 
0020181                   if ( result == NW_SUSPEND)
0020182                   {
0020183                            arp_port->ap_flags |= APF_SUSPEND;
0020184                            return;
0020185                   }
0020186                   assert(result == NW_OK);
0020187 
0020188                   /* fall through */
0020189          case APS_GETADDR:
0020190                   /* Wait for IP address */
0020191                   if (!(arp_port->ap_flags & APF_INADDR_SET))
0020192                            return;
The first time that arp_main() is called, this conditional will be true and the function will return. When arp_main() is called the second time (by arp_set_ipaddr(), arp_set_ipaddr has just set the ap_ipaddr field of the arp port to the ip address of the associated ip port (i.e., APF_INADDR_SET is set). Therefore, the second time that arp_main() is called, this conditional will be false and the code will fall through.


0020193 
0020194                   /* fall through */
0020195          case APS_ARPSTART:
0020196                   arp_port->ap_state= APS_ARPPROTO;
0020197 
Initialize the arp table.


0020198                   {
0020199                            arp_cache_t *cache;
0020200                            int i;
0020201 
0020202                            cache= arp_cache;
0020203                            for (i=0; i<ARP_CACHE_NR; i++, cache++)
0020204                            {
0020205                                     cache->ac_state= ACS_UNUSED;
0020206                                     cache->ac_flags= ACF_EMPTY;
0020207                                     cache->ac_expire= 0;
0020208                                     cache->ac_lastuse= 0;
0020209                            }
0020210                   }
0020211                   result= eth_ioctl (arp_port->ap_eth_fd, NWIOSETHOPT);
The ethernet file descriptor's flags and types are set to the following:

nweo_flags= NWEO_COPY|NWEO_EN_BROAD|NWEO_TYPESPEC;
nweo_type= HTONS(ETH_ARP_PROTO);


eth_ioctl()


The actions of eth_ioctl(fd, req) depend on req, eth_ioctl()'s second parameter:

NWIOSETHOPT (NetWork IO Set ETHernet OPTions):

If req is NWIOSETHOPT, eth_ioctl() configures the ethernet file descriptor fd (eth_ioctl()'s first parameter), which can then be used by a higher layer (e.g., ip, arp).

NWIOGETHSTAT (NetWork IO Get ETHernet STATs):

Only the arp code calls eth_ioctl() with the second parameter set to NWIOGETHSTAT. In this case, the ap_ethaddr field of the arp port is set to the ethernet address of the ethernet file descriptor's underlying ethernet port.


0020212 
0020213                   if (result==NW_SUSPEND)
0020214                   {
0020215                            arp_port->ap_flags |= APF_SUSPEND;
0020216                            return;
0020217                   }
0020218                   assert(result == NW_OK);
0020219 
0020220                   /* fall through */
0020221          case APS_ARPPROTO:
0020222                   arp_port->ap_state= APS_ARPMAIN;
0020223                   if (arp_port->ap_flags & APF_MORE2WRITE)
0020224                            setup_write(arp_port);
0020225                   setup_read(arp_port);
setup_read() / arp

setup_read() is called in two places, by arp_main() during the initialization of the network service and by arp_putdata(). setup_read() calls eth_read() to deliver any ethernet packets that are waiting to be delivered to the arp port.


0020226                   return;
0020227 
0020228 #if !CRAMPED
0020229          default:
0020230                   ip_panic((
0020231                    "arp_main(&arp_port_table[%d]) called but ap_state=0x%x\n",
0020232                            arp_port->ap_eth_port, arp_port->ap_state ));
0020233 #endif
0020234          }
0020235 }
0020236 
0020237 PRIVATE acc_t *arp_getdata (fd, offset, count, for_ioctl)
0020238 int fd;
0020239 size_t offset;
0020240 size_t count;
0020241 int for_ioctl;
arp_getdata()

arp_getdata(fd, offset, count, for_ioctl) accomplishes several different tasks, depending on the state of the arp port:

APS_ARPPROTO (configuration state):

If count, arp_getdata()'s third parameter, is non-zero, arp_getdata() creates a nwio_eth_opt struct and sets the appropriate flags and type for an ethernet file descriptor that services an arp port:

nweo_flags= NWEO_COPY|NWEO_EN_BROAD|NWEO_TYPESPEC;
nweo_type= HTONS(ETH_ARP_PROTO);

and then returns an accessor that contains the struct.

ARS_ARPMAIN (operational state):

If count, arp_getdata()'s third parameter, is non-zero, arp_getdata() creates an arp packet (an arp46_t struct) and returns the struct. eth_write() calls arp_getdata() to create an arp-request or arp_reply packet (the packets that build the arp table. The ip and ethernet address of the arp-reply or request is gotten from the ap_write_ipaddr and ap_write_ethaddr fields, respectively, of the arp port.


0020242 {
0020243          arp_port_t *arp_port;
0020244          arp46_t *arp;
0020245          acc_t *data;
0020246          int result;
0020247 
0020248          arp_port= &arp_port_table[fd];
0020249 
0020250          switch (arp_port->ap_state)
0020251          {
0020252          case APS_ARPPROTO:
0020253                   if (!count)
0020254                   {
0020255                            result= (int)offset;
0020256                            if (result<0)
0020257                            {
0020258                                     arp_port->ap_state= APS_ERROR;
0020259                                     break;
0020260                            }
0020261                            if (arp_port->ap_flags & APF_SUSPEND)
0020262                            {
0020263                                     arp_port->ap_flags &= ~APF_SUSPEND;
0020264                                     arp_main(arp_port);
0020265                            }
0020266                            return NW_OK;
0020267                   }
0020268                   assert ((!offset) && (count == sizeof(struct nwio_ethopt)));
Allocate an accessor big enough for a nwio_eth_opt struct, set the appropriate flags and type for an ethernet file descriptor that services an arp port:

nweo_flags= NWEO_COPY|NWEO_EN_BROAD|NWEO_TYPESPEC;
nweo_type= HTONS(ETH_ARP_PROTO);

and then return the accessor that contains the struct.


0020269                   {
0020270                            struct nwio_ethopt *ethopt;
0020271                            acc_t *acc;
0020272 
0020273                            acc= bf_memreq(sizeof(*ethopt));
bf_memreq()

After the buffers have been initialized, accessors[] looks like the following:



bf_memreq() allocates accessors to the caller. For example, if 1514 bytes of buffer space are requested immediately after the network process starts and each buffer is 512 bytes (the default), then accessors[] will look like the following:



Note that three elements of accessors[] have been removed from buf512_freelist and that the head of the chain of the 3 accessors is returned by bf_memreq(). Also note that the acc_linkC and buf_linkC fields have been set to one and acc_length and acc_offset have been set to their appropriate values.

So what happens if there are not enough buffers on the buf512_freelist to satisfy a request? On lines 2280-2290 of buf.c, functions that free buffers for the specific clients (e.g., eth_buffree()) are called until there are enough buffers on buf512_freelist.

For a complete description of the network service's buffer management, click here.


0020274                            ethopt= (struct nwio_ethopt *)ptr2acc_data(acc);
ptr2acc_data()

The macro ptr2acc_data is #define'd in inet/generic/buf.h as:

#define ptr2acc_data(/* acc_t * */ a) (bf_temporary_acc=(a), \
(&bf_temporary_acc->acc_buffer->buf_data_p[bf_temporary_acc-> \
acc_offset]))

ptr2acc_data() simply returns a pointer to the actual data within an accessor.

ptr2acc_data() is usually called so that the fields of a header (e.g., ip header) can be analyzed.


0020275                            ethopt->nweo_flags= NWEO_COPY|NWEO_EN_BROAD|
0020276                                     NWEO_TYPESPEC;
0020277                            ethopt->nweo_type= HTONS(ETH_ARP_PROTO);
0020278                            return acc;
0020279                   }
0020280          case APS_ARPMAIN:
0020281                   assert (arp_port->ap_flags & APF_ARP_WR_IP);
0020282                   if (!count)
0020283                   {
0020284                            result= (int)offset;
0020285                            if (result<0)
0020286                            {
0020287                                     DIFBLOCK(1, (result != NW_SUSPEND),
0020288                                              printf(
0020289                                     "arp.c: write error on port %d: error %d\n",
0020290                                              fd, result));
0020291 
0020292                                     arp_port->ap_state= APS_ERROR;
0020293                                     break;
0020294                            }
0020295                            arp_port->ap_flags &= ~APF_ARP_WR_IP;
0020296                            if (arp_port->ap_flags & APF_ARP_WR_SP)
0020297                                     setup_write(arp_port);
0020298                            return NW_OK;
0020299                   }
If arp_getdata() is called from eth_write(), an arp-request or arp-reply packet (an arp46_t struct) is created to be sent out by eth_send().

In order to follow the code below, it is important to understand the arp46_t struct.



REMOVE LATER (I think this diagram is a little confusing since eth_write() calls both arp_getdata() and eth_send():



( prepare data ) +--------------+
( for eth_send() ) | setup_write()|
| +--------------+
| OR |
+---------------+
| arp_getdata() |
+---------------+
||
+-------------+
| eth_write() |
+-------------+



arp46_t


The arp46_t struct is the structure of the arp-request and arp-reply packets. The information contained within the arp-reqests and arp-replies is used to build the arp table. The first three fields in the struct (a46_dstaddr, a46_srcaddr, and a46_ethtype) correspond to an ethernet header and the remaining fields are arp-specific.

"46" refers to the number of bytes in the packet - 16 for the ethernet header and 28 for the arp-specific fields.

typedef struct arp46
{
ether_addr_t a46_dstaddr;
ether_addr_t a46_srcaddr;
ether_type_t a46_ethtype;
union
{
struct
{
u16_t a_hdr, a_pro;
u8_t a_hln, a_pln;
u16_t a_op;
ether_addr_t a_sha;
u8_t a_spa[4];
ether_addr_t a_tha;
u8_t a_tpa[4];
} a46_data;
char a46_dummy[ETH_MIN_PACK_SIZE-ETH_HDR_SIZE];
} a46_data;
} arp46_t;




From RFC 2625:

+-------------------------+
| HW Type | 2 bytes
+-------------------------+
| Protocol | 2 bytes
+-------------------------+
| HW Addr Length | 1 byte
+-------------------------+
| Protocol Addr Length | 1 byte
+-------------------------+
| Op Code | 2 bytes
+-------------------------+
| HW Addr of Sender | 6 bytes
+-------------------------+
| Protocol Addr of Sender | 4 bytes
+-------------------------+
| HW Addr of Target | 6 bytes
+-------------------------+
| Protocol Addr of Target | 4 bytes
+-------------------------+
Total 28 bytes
ARP Packet Format



+---------+-------------------------------------------------------+
|Ethernet |For an ARP request, source MAC address is the MAC |
|Header |address of the host sending the ARP request, |
| |destination MAC address is the Ethernet broadcast |
| |address (FF:FF:FF:FF:FF:FF), frame type field is 0x806.|
| |For ARP reply, source MAC address is the MAC address of|
| |the host replying to the ARP request, destination MAC |
| |address is the MAC address of the host that sent the |
| |ARP request, and the frame type field is 0x806. |
+---------+-------------------------------------------------------+
|Hardware |Type of the hardware MAC address which is being mapped.|
|Address |For Ethernet the value of this field is 1. |
|Type | |
+---------+-------------------------------------------------------+
|Protocol |Type of the protocol address to which the MAC address |
|Address |is mapped. For IP address the value of this field is |
|Type |0x800. |
+---------+-------------------------------------------------------+
|Hardware |Size of the hardware MAC address. For Ethernet, the |
|Address |value of this field is 6. |
|Size | |
+---------+-------------------------------------------------------+
|Protocol |Size of the protocol address. For IP, the value of |
|Address |this field is 4. |
|Size | |
+---------+-------------------------------------------------------+
|Operation|Type of operation being performed. The value of this |
| |field can be 1 (ARP request), 2 (ARP reply) |
+---------+-------------------------------------------------------+
|Source |The hardware MAC address of the host sending the ARP |
|MAC |request or reply. This is same as the source MAC |
|address |address present in the Ethernet header. |
+---------+-------------------------------------------------------+
|Source |The IP address of the host sending the ARP request or |
|IP |reply. |
|address | |
+---------+-------------------------------------------------------+
|Target |The hardware MAC address of the host receiving the ARP |
|MAC |request or reply. This is same as the destination MAC |
|address |address present in the Ethernet header. |
+---------+-------------------------------------------------------+
|Target |The IP address of the host receiving the ARP request |
|IP |or reply. |
|address | |
+---------+-------------------------------------------------------+





0020300                   assert (offset+count <= sizeof(arp46_t));
0020301                   data= bf_memreq(sizeof(arp46_t));
bf_memreq()

After the buffers have been initialized, accessors[] looks like the following:



bf_memreq() allocates accessors to the caller. For example, if 1514 bytes of buffer space are requested immediately after the network process starts and each buffer is 512 bytes (the default), then accessors[] will look like the following:



Note that three elements of accessors[] have been removed from buf512_freelist and that the head of the chain of the 3 accessors is returned by bf_memreq(). Also note that the acc_linkC and buf_linkC fields have been set to one and acc_length and acc_offset have been set to their appropriate values.

So what happens if there are not enough buffers on the buf512_freelist to satisfy a request? On lines 2280-2290 of buf.c, functions that free buffers for the specific clients (e.g., eth_buffree()) are called until there are enough buffers on buf512_freelist.

For a complete description of the network service's buffer management, click here.


0020302                   arp= (arp46_t *)ptr2acc_data(data);
ptr2acc_data()

The macro ptr2acc_data is #define'd in inet/generic/buf.h as:

#define ptr2acc_data(/* acc_t * */ a) (bf_temporary_acc=(a), \
(&bf_temporary_acc->acc_buffer->buf_data_p[bf_temporary_acc-> \
acc_offset]))

ptr2acc_data() simply returns a pointer to the actual data within an accessor.

ptr2acc_data() is usually called so that the fields of a header (e.g., ip header) can be analyzed.


0020303                   data->acc_offset += offset;
0020304                   data->acc_length= count;
Both offset and count are parameters of arp_getdata().

arp_getdata() sends out both arp-reply and arp-request packets. If the packet is an arp-reply, a system has already sent out an arp-request and arp_getdata() sends out a response packet to that specific machine. If the packet is an arp-request, arp_getdata() sends out a broadcast packet.



0020305                   if (arp_port->ap_write_code == ARP_REPLY)
0020306                            arp->a46_dstaddr= arp_port->ap_write_ethaddr;
0020307                   else
0xffffffffffff is the ethernet broadcast address.


0020308                   {
0020309                            arp->a46_dstaddr.ea_addr[0]= 0xff;
0020310                            arp->a46_dstaddr.ea_addr[1]= 0xff;
0020311                            arp->a46_dstaddr.ea_addr[2]= 0xff;
0020312                            arp->a46_dstaddr.ea_addr[3]= 0xff;
0020313                            arp->a46_dstaddr.ea_addr[4]= 0xff;
0020314                            arp->a46_dstaddr.ea_addr[5]= 0xff;
0020315                   }
The four fields below for both arp-request and arp-reply packets always have the same values. (The size of an ethernet address is 6 bytes and the size of an ip address is 4 bytes.)


0020316                   arp->a46_hdr= HTONS(ARP_ETHERNET);
0020317                   arp->a46_pro= HTONS(ETH_IP_PROTO);
0020318                   arp->a46_hln= 6;
0020319                   arp->a46_pln= 4;
0020320                   arp->a46_op= htons(arp_port->ap_write_code);
The operation will either be ARP_REQUEST or ARP_REPLY.

a46_sha (Source Hardware Address) and a46_spa (Source Protocol Address) take the values of the arp port. setup_write() sets ap_write_ethaddr and ap_write_ethaddr before calling eth_write(). (Note that "tha" and "tpa" stand for "Target Hardware Address" and "Target Protocol Address", respectively.)


0020321                   arp->a46_sha= arp_port->ap_ethaddr;
0020322                   memcpy (arp->a46_spa, &arp_port->ap_ipaddr, sizeof(ipaddr_t));
0020323                   arp->a46_tha= arp_port->ap_write_ethaddr;
0020324                   memcpy (arp->a46_tpa, &arp_port->ap_write_ipaddr,
0020325                            sizeof(ipaddr_t));
0020326                   return data;
0020327          default:
0020328 #if !CRAMPED
0020329                   printf("arp_getdata(%d, 0x%d, 0x%d) called but ap_state=0x%x\n",
0020330                            fd, offset, count, arp_port->ap_state);
0020331 #endif
0020332                   break;
0020333          }
0020334          return 0;
0020335 }
0020336 
0020337 PRIVATE int arp_putdata (fd, offset, data, for_ioctl)
0020338 int fd;
0020339 size_t offset;
0020340 acc_t *data;
0020341 int for_ioctl;
arp_putdata()

During the initialization of the network service, arp_main() calls eth_ioctl(). eth_ioctl(), in turn, (indirectly) calls arp_putdata() to get the underlying ethernet port's ethernet address.

After the initialization of the network service is complete, arp_putdata() is (indirectly) called by the ethernet code's packet2user() to deliver an arp-request or arp-reply packet to its destination arp port. arp_putdata() is also called by the ethernet code's reply_thr_put(), in which case arp_putdata() will call setup_read() to deliver any arp packets waiting to be delivered to the arp port.


0020342 {
0020343          arp_port_t *arp_port;
0020344          int result;
0020345          struct nwio_ethstat *ethstat;
0020346 
0020347          arp_port= &arp_port_table[fd];
0020348 
0020349          if (arp_port->ap_flags & APF_ARP_RD_IP)
After the initialization of the network service is complete, arp_putdata() is (indirectly) called by the ethernet code's packet2user() (with a non-null value for data) to deliver an arp-request or arp-reply packet to its destination arp port. This packet will then be processed by process_arp_req(). arp_putdata() is also called by the ethernet code's reply_thr_put() (with a null value for data), in which case arp_putdata() will call setup_read() to deliver any arp packets waiting to be delivered to the arp port.


0020350          {
0020351                   if (!data)
0020352                   {
0020353                            result= (int)offset;
0020354                            if (result<0)
0020355                            {
0020356                                     DIFBLOCK(1, (result != NW_SUSPEND), printf(
0020357                                     "arp.c: read error on port %d: error %d\n",
0020358                                              fd, result));
0020359 
0020360                                     return NW_OK;
0020361                            }
0020362                            if (arp_port->ap_flags & APF_ARP_RD_SP)
If the code arrives here after an arp packet has been processed by an earlier call to arp_putdata(), the APF_ARP_RD_SP flag will not be set. However, if the ethernet code's reply_thr_put() is called in response to an error, it is possible that the APF_ARP_RD_SP flag has been set. In that case, call setup_read() to deliver any arp packets waiting to be delivered to the arp port.


0020363                            {
0020364                                     arp_port->ap_flags &= ~(APF_ARP_RD_IP|
0020365                                              APF_ARP_RD_SP);
0020366                                     setup_read(arp_port);
setup_read() / arp

setup_read() is called in two places, by arp_main() during the initialization of the network service and by arp_putdata(). setup_read() calls eth_read() to deliver any ethernet packets that are waiting to be delivered to the arp port.


0020367                            }
0020368                            else
The read operation is complete so clear the read and suspend flags.


0020369                                     arp_port->ap_flags &= ~(APF_ARP_RD_IP|
0020370                                              APF_ARP_RD_SP);
0020371                            return NW_OK;
0020372                   }
0020373                   assert (!offset);
0020374                   /* Warning: the above assertion is illegal; puts and gets of
0020375                    data can be brokenup in any piece the server likes. However
0020376                    we assume that the server is eth.c and it transfers only
0020377                    whole packets. */
0020378                   data= bf_packIffLess(data, sizeof(arp46_t));
bf_packIffLess()

If the data in a linked list of accessors is less than min_len (the second parameter), bf_packIffLess(pack, min_len) packs the data by calling bf_pack().

bf_packIffLess() is often called to ensure that a packet's header is in a single contiguous buffer so that the individual fields of the header can be easily accessed.

For a detailed description of the network service's buffer management, click here.


0020379                   if (data->acc_length >= sizeof(arp46_t))
0020380                            process_arp_req(arp_port,data);
process_arp_req()

The name process_arp_req() is a misnomer since the function not only processes arp-requests but also processes arp-replies. process_arp_req() is called by arp_putdata(), which is (indirectly) called by packet2user() upon receipt of either an arp-request or an arp-reply. If an entry in the arp table for the source ip address of the arp-request or arp-reply doesn't exist, the arp-request or arp-reply is discarded. Otherwise, the entry is updated with the ethernet address contained in the arp-request/arp-reply. If the updated entry was previously incomplete (in other words, the system was waiting for an arp-reply from an earlier arp-request), client_reply() is called to send out ethernet packets that were waiting for arp resolution. If an arp-request was received for the system's ip address, setup_write() is called to send out an arp-reply answering the arp-request.


0020381                   bf_afree(data);
0020382                   return NW_OK;
0020383          }
0020384          switch (arp_port->ap_state)
0020385          {
0020386          case APS_GETADDR:
During the initialization of the network service (when the state of an arp port is APS_GETADDR), arp_main() calls eth_ioctl(). eth_ioctl(), in turn, (indirectly) calls arp_putdata() to get the underlying ethernet port's ethernet address.


0020387                   if (!data)
If the initialization of the network service and the arp port was not successfully finished, the code will arrive here.


0020388                   {
0020389                            result= (int)offset;
0020390                            if (result<0)
0020391                            {
0020392                                     arp_port->ap_state= APS_ERROR;
0020393                                     break;
0020394                            }
0020395                            if (arp_port->ap_flags & APF_SUSPEND)
0020396                            {
0020397                                     arp_port->ap_flags &= ~APF_SUSPEND;
0020398                                     arp_main(arp_port);
arp_main()

arp_main(arp_port) is called in two places during the initialization of the network service. The first time arp_main() is called is by arp_set_cb(), which is in the ip code. During this call, arp_main() opens an ethernet file descriptor for the arp port arp_port, arp_main()'s only parameter and retrieves the ethernet address of the ethernet file descriptor's underlying ethernet port, with which it sets the arp port's ap_ethaddr field.

arp_set_ipaddr() calls arp_main() the second time. During this call, the arp table (which is also referred to as the arp cache) is initialized and the ethernet file descriptor previously opened is configured with values appropriate for an ethernet file descriptor supporting an arp port. After configuring the file descriptor, setup_read() is called.


0020399                            }
0020400                            return NW_OK;
0020401                   }
0020402                   compare (bf_bufsize(data), ==, sizeof(*ethstat));
compare()

compare is #define'd in inet/generic/assert.h:

#define compare(a,t,b) (!((a) t (b)) ? bad_compare(this_file, __LINE__, \
(a), #a " " #t " " #b, (b)) : (void) 0)

and bad_compare() is defined in inet/inet.c.

If the relationship between the 3 arguments in compare() does not hold, some debugging output is emitted and then Minix is terminated.

For example, if compare(result, >=, 0) is called and result (the first argument) is -1, Minix will be terminated.


0020403                   data= bf_packIffLess(data, sizeof(*ethstat));
bf_packIffLess()

If the data in a linked list of accessors is less than min_len (the second parameter), bf_packIffLess(pack, min_len) packs the data by calling bf_pack().

bf_packIffLess() is often called to ensure that a packet's header is in a single contiguous buffer so that the individual fields of the header can be easily accessed.

For a detailed description of the network service's buffer management, click here.


0020404                   compare (data->acc_length, ==, sizeof(*ethstat));
compare()

compare is #define'd in inet/generic/assert.h:

#define compare(a,t,b) (!((a) t (b)) ? bad_compare(this_file, __LINE__, \
(a), #a " " #t " " #b, (b)) : (void) 0)

and bad_compare() is defined in inet/inet.c.

If the relationship between the 3 arguments in compare() does not hold, some debugging output is emitted and then Minix is terminated.

For example, if compare(result, >=, 0) is called and result (the first argument) is -1, Minix will be terminated.


0020405                   ethstat= (struct nwio_ethstat *)ptr2acc_data(data);
The nwio_ethstat struct is described here.


ptr2acc_data()


The macro ptr2acc_data is #define'd in inet/generic/buf.h as:

#define ptr2acc_data(/* acc_t * */ a) (bf_temporary_acc=(a), \
(&bf_temporary_acc->acc_buffer->buf_data_p[bf_temporary_acc-> \
acc_offset]))

ptr2acc_data() simply returns a pointer to the actual data within an accessor.

ptr2acc_data() is usually called so that the fields of a header (e.g., ip header) can be analyzed.


0020406                   arp_port->ap_ethaddr= ethstat->nwes_addr;
0020407                   bf_afree(data);
0020408                   return NW_OK;
0020409          default:
0020410 #if !CRAMPED
0020411                   printf("arp_putdata(%d, 0x%d, 0x%lx) called but ap_state=0x%x\n",
0020412                            fd, offset, (unsigned long)data, arp_port->ap_state);
0020413 #endif
0020414                   break;
0020415          }
0020416          return EGENERIC;
0020417 }
0020418 
0020419 PRIVATE void setup_read(arp_port)
0020420 arp_port_t *arp_port;
setup_read() / arp

setup_read() is called in two places, by arp_main() during the initialization of the network service and by arp_putdata(). setup_read() calls eth_read() to deliver any ethernet packets that are waiting to be delivered to the arp port.


0020421 {
0020422          int result;
0020423 
0020424          while (!(arp_port->ap_flags & APF_ARP_RD_IP))
eth_read() calls packet2user(), which (indirectly) calls arp_putdata() twice. The first time that arp_putdata() is called, it attempts to deliver the packet to the arp port; if arp_putdata() can deliver the packet, arp_putdata() clears the APF_ARP_RD_IP flag the second time it is called.


0020425          {
0020426                   arp_port->ap_flags |= APF_ARP_RD_IP;
0020427                   result= eth_read (arp_port->ap_eth_fd, ETH_MAX_PACK_SIZE);
eth_read()

eth_read() attempts to deliver all of the ethernet packets in an ethernet file descriptor's read queue to its associated ip port or arp port or sr file descriptor and returns NW_SUSPEND when there are no more ethernet packets to deliver.


0020428                   if (result == NW_SUSPEND)
eth_read() returns NW_SUSPEND after delivering all of the ethernet file descriptor's ethernet packets.


0020429                   {
0020430                            arp_port->ap_flags |= APF_ARP_RD_SP;
0020431                            return;
0020432                   }
0020433                   DIFBLOCK(1, (result != NW_OK),
0020434                            printf("arp.c: eth_read(..,%d)=%d\n",
0020435                            ETH_MAX_PACK_SIZE, result));
0020436          }
0020437 }
0020438 
0020439 PRIVATE void setup_write(arp_port)
0020440 arp_port_t *arp_port;
setup_write()

setup_write() initiates the sending of an arp-request or an arp-reply. Specifically, several fields of the arp port (e.g., ap_write_ipaddr - the ip address whose ethernet address is being sought) are set before calling eth_write() to send out the arp-request or arp-reply.


0020441 {
0020442          int i, result;
0020443 
0020444          while (arp_port->ap_flags & APF_MORE2WRITE)
0020445          {
0020446                   if (arp_port->ap_flags & APF_CLIENTWRITE)
An arp-request will be sent out in an attempt to determine the ethernet address for a given ip address. APF_CLIENTWRITE is set by arp_ip_eth() before setup_write() is called.


0020447                   {
0020448                            arp_port->ap_flags &= ~APF_CLIENTWRITE;
0020449                            arp_port->ap_write_ipaddr= arp_port->ap_req_ipaddr;
0020450                            arp_port->ap_write_code= ARP_REQUEST;
0020451                            clck_timer(&arp_port->ap_timer,
0020452                                     get_time() + ARP_TIMEOUT,
0020453                                     arp_timeout, arp_port->ap_eth_port);
Set a timer for the arp-request for a half second. If the arp-request times out, arp_timeout() is called.

ARP_TIMEOUT is #define'd on line 20023:

#define ARP_TIMEOUT (HZ/2+1) /* .5 seconds */


arp_timeout()


When an arp-request is sent out, a timer is set. If an arp-reply is not received for this arp-request within a half second, arp_timeout() is called. If fewer than four arp-requests have already been sent out, arp_timeout() simply calls setup_write() to send out another arp-request. If four arp-requests have already been sent out, the arp table entry is marked as unreachable and client_reply() is called to alert interested ip file descriptors.


clck_timer()


clck_timer(timer, timeout, func, fd) configures timer, clck_timer()'s first parameter, based on timeout, func, and fd (clck_timer()'s second, third, and fourth parameters) and places the timer in its appropriate position in timer_chain. In other words, if the new timer is scheduled to expire before any other timer, it is inserted at the head of the linked list and if the new timer is scheduled to expire after all the other timers, it is inserted at the tail of the linked list. If the new timer is scheduled to expire before any other timers, clck_timer() calls set_timer(), which sends a message to the clock task with a revised expiration time for the synchronous alarm.

An example of a client calling clck_timer() to insert a timer in timer_chain is the arp client.


get_time()


get_time() returns the number of clock ticks since reboot.

Several of the clients (eth, arp, ip, tcp, and udp) use get_time() to determine an appropriate timeout value for a given operation. For example, the arp code calls get_time() to determine an appropriate amount of time to wait for a response from an arp request before giving up.


0020454                   }
0020455                   else
An arp-reply will be sent out in response to an arp-request that was previously received.


0020456                   {
0020457                            arp_cache_t *cache;
0020458 
When an arp-request is received, an entry in the arp table is created for the system that sent the arp-request (if an entry doesn't already exist) and the ACF_GOTREQ flag for the entry is set. The code below searches through the arp table for the entry and sets the ap_write_ethaddr and ap_write_ipaddr to the system that sent the arp-request and sets ap_write_code to ARP_REPLY. These fields are later used by eth_write() to construct the arp-reply.


0020459                            cache= arp_cache;
0020460                            for (i=0; i<ARP_CACHE_NR; i++, cache++)
0020461                            {
0020462                                     if ((cache->ac_flags & ACF_GOTREQ) &&
0020463                                              cache->ac_port == arp_port)
0020464                                     {
0020465                                              cache->ac_flags &= ~ACF_GOTREQ;
0020466                                              arp_port->ap_write_ethaddr= cache->
0020467                                                     ac_ethaddr;
0020468                                              arp_port->ap_write_ipaddr= cache->
0020469                                                     ac_ipaddr;
0020470                                              arp_port->ap_write_code= ARP_REPLY;
0020471                                              break;
0020472                                     }
0020473                            }
0020474                            if (i>=ARP_CACHE_NR)
0020475                            {
0020476                                     arp_port->ap_flags &= ~APF_MORE2WRITE;
0020477                                     break;
A matching entry wasn't found.


0020478                            }
0020479                   }
0020480                   arp_port->ap_flags= (arp_port->ap_flags & ~APF_ARP_WR_SP) |
0020481                            APF_ARP_WR_IP;
0020482                   result= eth_write(arp_port->ap_eth_fd, sizeof(arp46_t));
eth_write()

If a few tests (e.g., a test to determine if the ethernet packet is either too large or too small) have positive results and the ethernet task is not attempting to send an ethernet packet (i.e., etp_wr_pack is null) and the packet is coming from the ip code, eth_write(fd, count) passes the ethernet packet stored in the dl_eth.de_frame field of the ip port associated with the ethernet file descriptor fd, eth_write()'s first parameter, to eth_send().

If the packet is coming from the arp code (i.e., an arp-request or an arp-reply is being sent out), eth_write() calls arp_getdata() to create the ethernet packet before passing the newly created packet off to eth_send().

If the ethernet task is attempting to send an ethernet packet, eth_write() sets the ethernet port's EPF_MORE2WRITE flag and returns NW_SUSPEND.


0020483                   if (result == NW_SUSPEND)
0020484                            arp_port->ap_flags |= APF_ARP_WR_SP;
0020485                   if (result<0)
0020486                   {
0020487                            DIFBLOCK(1, (result != NW_SUSPEND),
0020488                                     printf("arp.c: eth_write(..,%d)=%d\n",
0020489                                     sizeof(arp46_t), result));
0020490                            return;
0020491                   }
0020492          }
0020493 }
0020494 
0020495 PRIVATE void process_arp_req (arp_port, data)
0020496 arp_port_t *arp_port;
0020497 acc_t *data;
process_arp_req()

The name process_arp_req() is a misnomer since the function not only processes arp-requests but also processes arp-replies. process_arp_req() is called by arp_putdata(), which is (indirectly) called by packet2user() upon receipt of either an arp-request or an arp-reply. If an entry in the arp table for the source ip address of the arp-request or arp-reply doesn't exist, the arp-request or arp-reply is discarded. Otherwise, the entry is updated with the ethernet address contained in the arp-request/arp-reply. If the updated entry was previously incomplete (in other words, the system was waiting for an arp-reply from an earlier arp-request), client_reply() is called to send out ethernet packets that were waiting for arp resolution. If an arp-request was received for the system's ip address, setup_write() is called to send out an arp-reply answering the arp-request.


0020498 {
0020499          arp46_t *arp;
0020500          arp_cache_t *ce;
0020501          int level;
0020502          time_t curr_time;
0020503          ipaddr_t spa, tpa;
0020504 
0020505          curr_time= get_time();
get_time()

get_time() returns the number of clock ticks since reboot.

Several of the clients (eth, arp, ip, tcp, and udp) use get_time() to determine an appropriate timeout value for a given operation. For example, the arp code calls get_time() to determine an appropriate amount of time to wait for a response from an arp request before giving up.


0020506 
0020507          arp= (arp46_t *)ptr2acc_data(data);
ptr2acc_data()

The macro ptr2acc_data is #define'd in inet/generic/buf.h as:

#define ptr2acc_data(/* acc_t * */ a) (bf_temporary_acc=(a), \
(&bf_temporary_acc->acc_buffer->buf_data_p[bf_temporary_acc-> \
acc_offset]))

ptr2acc_data() simply returns a pointer to the actual data within an accessor.

ptr2acc_data() is usually called so that the fields of a header (e.g., ip header) can be analyzed.


0020508          memcpy(&spa, arp->a46_spa, sizeof(ipaddr_t));
0020509          memcpy(&tpa, arp->a46_tpa, sizeof(ipaddr_t));
Copy the source and target protocol (i.e., ip) addresses found in the incoming arp-request to the spa and tpa variables.


0020510 
The following four fields are the same for every arp-request and arp-reply packet. 6 bytes is the length of an ethernet address and 4 bytes is the length of an ip address.


0020511          if (arp->a46_hdr != HTONS(ARP_ETHERNET) ||
0020512                   arp->a46_hln != 6 ||
0020513                   arp->a46_pro != HTONS(ETH_IP_PROTO) ||
0020514                   arp->a46_pln != 4)
0020515                   return;
0020516          ce= find_cache_ent(arp_port, spa);
find_cache_ent()

find_cache_ent(arp_port, ipaddr) looks in the arp table for an ip address, returning a reference to the corresponding arp table entry (of type arp_cache_t) if it finds the ip address and NULL if it does not find the ip address.

Note that find_cache_ent() will return the entry even if the entry has expired or is incomplete.


0020517          if (ce && ce->ac_expire < curr_time)
If find_cache_ent() returned an expired entry, nullify the entry (i.e., set the entry's state to ACS_UNUSED).

curr_time was set on line 20505.


0020518          {
0020519                   DBLOCK(0x10, printf("arp: expiring entry for ");
0020520                            writeIpAddr(ce->ac_ipaddr); printf("\n"));
0020521                   ce->ac_state= ACS_UNUSED;
0020522                   ce= NULL;
0020523          }
0020524          if (ce == NULL)
If an entry for the source wasn't found or the entry had expired, initialize a new entry that corresponds to the source of the arp-request packet.


0020525          {
0020526                   if (tpa != arp_port->ap_ipaddr)
0020527                            return;
Ignore all arp-request packets except those packets that are looking for the ethernet address that corresponds to the ip address of the local machine.

It does seem curious that the system wouldn't go ahead and fill out a cache entry anyway since the information in the arp-request packet is valid nonetheless.


0020528 
0020529                   DBLOCK(0x10, printf("arp: allocating entry for ");
0020530                            writeIpAddr(spa); printf("\n"));
0020531 
0020532                   ce= alloc_cache_ent();
alloc_cache_ent()

alloc_cache_ent() finds an unused entry within the arp table and returns the entry's index within the table. If there are no unused entries within the arp table, the entry that was last used is returned instead.


0020533                   ce->ac_flags= ACF_EMPTY;
0020534                   ce->ac_state= ACS_VALID;
0020535                   ce->ac_ethaddr= arp->a46_sha;
0020536                   ce->ac_ipaddr= spa;
0020537                   ce->ac_port= arp_port;
0020538                   ce->ac_expire= curr_time+ARP_EXP_TIME;
The entry expires after 20 minutes.

On line 20024:

#define ARP_EXP_TIME (20L*60L*HZ) /* 20 minutes */



0020539                   ce->ac_lastuse= curr_time-ARP_INUSE_OFFSET; /* never used */
Regardless what the author's comment above indicates, ac_lastuse is used (see lines 20627-20628). If there are no unused arp table entries in the arp table and a new entry must be made, the entry whose ac_lastuse field is the oldest is selected by alloc_cache_ent().

Since the cache entry is not really being used here (it is simply being set), a 1 minute handicap is placed on the entry. Therefore, if an entry was accessed and then an entry was added in this code location shortly thereafter and a new entry is later needed, the latter will be chosen for removal before the former.

ARP_INUSE_OFFSET is #define'd on line 20028 as 1 minute:

#define ARP_INUSE_OFFSET (60*HZ)


0020540          }
0020541 
0020542          if (ce->ac_state == ACS_INCOMPLETE || ce->ac_state == ACS_UNREACHABLE)
The state of an entry in the arp table is ACS_INCOMPLETE if an arp-request for the system's ip address has been sent out but a response from the system has not been received. In this case, we've gotten lucky - we have received an unrelated arp-request from the system itself.

If the state of an entry is ACS_UNREACHABLE, the system was unreachable. By receiving an arp-request from this system, it appears that this is no longer the case.

Since we know the entry is now valid, mark it as such (i.e., set the state of the entry to ACS_VALID). If the entry was incomplete, call client_reply().


0020543          {
0020544                   ce->ac_ethaddr= arp->a46_sha;
0020545                   if (ce->ac_state == ACS_INCOMPLETE)
0020546                   {
0020547                            ce->ac_state= ACS_VALID;
0020548                            client_reply(arp_port, spa, &arp->a46_sha);
client_reply()

client_reply() is called either upon arp resolution (generally through an arp-reply) or upon the timeout of an arp-request. client_reply() simply unsets the timer associated with the arp-request and calls ipeth_arp_reply() so that ethernet packets that were waiting in the queue waiting for arp resolution can either be sent (if the arp-request got an answer) or discarded (if the arp-request timed out).


0020549                   }
0020550                   else
0020551                            ce->ac_state= ACS_VALID;
0020552          }
0020553 
0020554          /* Update fields in the arp cache. */
0020555 #if !CRAMPED
0020556          if (memcmp(&ce->ac_ethaddr, &arp->a46_sha,
0020557                   sizeof(ce->ac_ethaddr)) != 0)
0020558          {
0020559                   printf("arp: ethernet address for IP address ");
0020560                   writeIpAddr(spa);
0020561                   printf(" changed from ");
0020562                   writeEtherAddr(&ce->ac_ethaddr);
0020563                   printf(" to ");
0020564                   writeEtherAddr(&arp->a46_sha);
0020565                   printf("\n");
0020566                   ce->ac_ethaddr= arp->a46_sha;
Place the ethernet address of the sender in the appropriate entry in the arp table and log some arp infomation (writeIpAddr() and writeEtherAddr() log information).


0020567          }
0020568 #else
0020569          ce->ac_ethaddr= arp->a46_sha;
0020570 #endif
0020571          ce->ac_expire= curr_time+ARP_EXP_TIME;
The entry in the arp table expires after 20 minutes (ARP_EXP_TIME = 20 minutes).


0020572 
0020573          if (arp->a46_op == HTONS(ARP_REQUEST) && (tpa == arp_port->ap_ipaddr))
If the arp-request was for the ip address of the arp port's associated ip port, prepare to send an arp-reply in response.


htons() / ntohs() / htonl() / ntohl()


From htons(3):

"htons() converts a 16-bit quantity from host byte order to network byte order."

Different CPU architectures group multiple bytes differently. For example, on a "little-endian" machine (an example of which is the Intel CPU), the value 0x1234 is stored in memory as 0x3412. However, on a "big-endian" machine, the value 0x1234 is stored in memory as 0x1234.

It is important that values in a header are sent across a network in a consistent manner independent of the architecture of the sending or receiving system. For this reason, a standard was chosen. The standard chosen was big-endian although it could have just as well been little-endian.

htons() is defined in /include/net/hton.h, as:
#define htons(x) (_tmp=(x), ((_tmp>>8) & 0xff) | ((_tmp<<8) & 0xff00))

ntohs() converts a 16-bit quantity from network byte order to host byte order, the reverse of htons().

htonl() and ntohl() are identical to htons() and ntohs() except that they convert 32-bit quantities instead of 16-bit quantities.

Processes generally supply header information when sending packets. The data in these fields is converted to the network format (i.e., big-endian) by the process before the process copies the data to the network service.


0020574          {
0020575                   ce->ac_flags |= ACF_GOTREQ;
The ACF_GOTREQ flag indicates that the system that corresponds to the entry in the arp table is waiting for an arp-reply from the local system.


0020576                   arp_port->ap_flags |= APF_MORE2WRITE;
0020577                   if (!(arp_port->ap_flags & APF_ARP_WR_IP))
0020578                            setup_write(arp_port);
setup_write()

setup_write() initiates the sending of an arp-request or an arp-reply. Specifically, several fields of the arp port (e.g., ap_write_ipaddr - the ip address whose ethernet address is being sought) are set before calling eth_write() to send out the arp-request or arp-reply.


0020579          }
0020580 }
0020581 
0020582 PRIVATE void client_reply (arp_port, ipaddr, ethaddr)
0020583 arp_port_t *arp_port;
0020584 ipaddr_t ipaddr;
0020585 ether_addr_t *ethaddr;
client_reply()

client_reply() is called either upon arp resolution (generally through an arp-reply) or upon the timeout of an arp-request. client_reply() simply unsets the timer associated with the arp-request and calls ipeth_arp_reply() so that ethernet packets that were waiting in the queue waiting for arp resolution can either be sent (if the arp-request got an answer) or discarded (if the arp-request timed out).


0020586 {
0020587          if ((arp_port->ap_flags & APF_CLIENTREQ) &&
0020588                   ipaddr == arp_port->ap_req_ipaddr)
Determine if an arp-request has been sent out for this ip address. If so, since either the arp-request has timed out or another arp-request has been received that contains the information requested, the timer for the arp-request can be turned off and the appropriate flags can be cleared.


0020589          {
0020590                   arp_port->ap_flags &= ~(APF_CLIENTREQ|APF_CLIENTWRITE);
0020591                   clck_untimer(&arp_port->ap_timer);
clck_untimer()

clck_untimer(timer) releases timer, clck_untimer()'s only parameter. If necessary, clck_untimer() sends a message to the clock task requesting a synchronous alarm with a revised expiration time (actually, clck_untimer() calls set_timer(), which would send the message). Remember that the synchronous alarm task is aware of only a single alarm at any given time.


0020592          }
0020593          (*arp_port->ap_arp_func)(arp_port->ap_eth_port, ipaddr, ethaddr);
ipeth_arp_reply()

ipeth_arp_reply() is called (indirectly) by client_reply() under one of the following circumstances:

1) An arp-reply packet has been received in response to a previous arp-request packet that this system sent out.

2) An arp-request packet has timed out. In this case, eth_addr, ipeth_arp_reply()'s third parameter, will be NULL.

3) An arp-request packet has been received that contains the information requested by a previous arp-request packet that this system sent out.

ipeth_arp_reply() searches the queue of ethernet packets waiting for arp resolution for the ip address of the arp-request/arp-reply. If the arp resolution timed out, the packet is discarded. If the arp resolution was successful, the ethernet packet is moved to the queue of packets waiting to be sent out and ipeth_restart_send() is called to send out the packets.


0020594 }
0020595 
0020596 PRIVATE arp_cache_t *find_cache_ent (arp_port, ipaddr)
0020597 arp_port_t *arp_port;
0020598 ipaddr_t ipaddr;
find_cache_ent()

find_cache_ent(arp_port, ipaddr) looks in the arp table for an ip address, returning a reference to the corresponding arp table entry (of type arp_cache_t) if it finds the ip address and NULL if it does not find the ip address.

Note that find_cache_ent() will return the entry even if the entry has expired or is incomplete.


0020599 {
0020600          arp_cache_t *cache;
0020601          int i;
0020602 
0020603          for (i=0, cache= arp_cache; i<ARP_CACHE_NR; i++, cache++)
Look through the arp table for an entry that is in use (i.e., ac_state is not ACS_UNUSED) and whose ip address is ipaddr, the second parameter to find_cache_ent().


0020604          {
0020605                   if (cache->ac_state != ACS_UNUSED &&
0020606                            cache->ac_port == arp_port &&
0020607                            cache->ac_ipaddr == ipaddr)
0020608                   {
0020609                            return cache;
0020610                   }
0020611          }
0020612          return NULL;
0020613 }
0020614 
0020615 PRIVATE arp_cache_t *alloc_cache_ent()
0020616 {
alloc_cache_ent()

alloc_cache_ent() finds an unused entry within the arp table and returns the entry's index within the table. If there are no unused entries within the arp table, the entry that was last used is returned instead.


0020617          arp_cache_t *cache, *old;
0020618          int i;
0020619 
0020620          old= NULL;
0020621          for (i=0, cache= arp_cache; i<ARP_CACHE_NR; i++, cache++)
0020622          {
0020623                   if (cache->ac_state == ACS_UNUSED)
0020624                            return cache;
0020625                   if (cache->ac_state == ACS_INCOMPLETE)
0020626                            continue;
ACS_INCOMPLETE indicates that the arp code has sent out an arp broadcast to determine the ethernet address that matches an ip address but a valid response has not yet been received.


0020627                   if (!old || cache->ac_lastuse < old->ac_lastuse)
0020628                            old= cache;
If there are no unused entries within the arp table, the entry that was last used is returned instead.


0020629          }
0020630          assert(old);
0020631          return old;
0020632 }
0020633 
0020634 PUBLIC void arp_set_ipaddr (eth_port, ipaddr)
0020635 int eth_port;
0020636 ipaddr_t ipaddr;
arp_set_ipaddr()

arp_set_ipaddr(eth_port, ipaddr) is called only from ipeth_set_ipaddr(), which is (indirectly) called by ip_ioctl().

arp_set_ipaddr() simply sets the ap_ipaddr field of an arp port whose index within the arp_port_table[] is eth_port, arp_set_ipaddr()'s first parameter, to the ip address ipaddr, arp_set_ipaddr()'s second parameter.


0020637 {
0020638          arp_port_t *arp_port;
0020639          int i;
0020640 
0020641          if (eth_port < 0 || eth_port >= eth_conf_nr)
0020642                   return;
Verify that the ethernet port number (and therefore the corresponding arp port number) exists.


0020643          arp_port= &arp_port_table[eth_port];
Find the arp port whose index within arp_port_table[] is eth_port, the corresponding ethernet port.


0020644 
0020645          arp_port->ap_ipaddr= ipaddr;
0020646          arp_port->ap_flags |= APF_INADDR_SET;
0020647          arp_port->ap_flags &= ~APF_SUSPEND;
0020648          if (arp_port->ap_state == APS_GETADDR)
0020649                   arp_main(arp_port);
arp_main()

arp_main(arp_port) is called in two places during the initialization of the network service. The first time arp_main() is called is by arp_set_cb(), which is in the ip code. During this call, arp_main() opens an ethernet file descriptor for the arp port arp_port, arp_main()'s only parameter and retrieves the ethernet address of the ethernet file descriptor's underlying ethernet port, with which it sets the arp port's ap_ethaddr field.

arp_set_ipaddr() calls arp_main() the second time. During this call, the arp table (which is also referred to as the arp cache) is initialized and the ethernet file descriptor previously opened is configured with values appropriate for an ethernet file descriptor supporting an arp port. After configuring the file descriptor, setup_read() is called.


0020650 }
0020651 
0020652 PUBLIC int arp_set_cb(eth_port, ip_port, arp_func)
0020653 int eth_port;
0020654 int ip_port;
0020655 arp_func_t arp_func;
arp_set_cb()

During the initialization of the network service (and, more specifically, the initialization of the ip layer), arp_set_cb(eth_port, ip_port, arp_func) is called to initialize the arp port associated with the ethernet port eth_port, arp_set_cb()'s first parameter. After initializing the arp port, arp_set_cb() calls arp_main().

It is unclear what the "cb" in the function name stands for.


0020656 {
0020657          arp_port_t *arp_port;
0020658          int i;
0020659 
0020660          assert(eth_port >= 0);
0020661          if (eth_port >= eth_conf_nr)
0020662                   return ENXIO;
0020663 
0020664          arp_port= &arp_port_table[eth_port];
0020665          arp_port->ap_eth_port= eth_port;
0020666          arp_port->ap_ip_port= ip_port;
0020667          arp_port->ap_state= APS_INITIAL;
0020668          arp_port->ap_flags= APF_EMPTY;
0020669          arp_port->ap_arp_func= arp_func;
arp_func is set to ipeth_arp_reply() by ipeth_main().


0020670 
0020671          arp_main(arp_port);
0020672 
0020673          return NW_OK;
0020674 }
0020675 
0020676 PUBLIC int arp_ip_eth (eth_port, ipaddr, ethaddr)
0020677 int eth_port;
0020678 ipaddr_t ipaddr;
0020679 ether_addr_t *ethaddr;
arp_ip_eth()

arp_ip_eth(eth_port, ipaddr, ethaddr) looks for an entry in the arp table that matches ipaddr, the second parameter, and if it finds it, returns the corresponding ethernet address in ethaddr, the third parameter. If arp_ip_eth() does not find a valid entry in the arp table for the ip address, it sends out an arp broadcast in an attempt to find the ethernet address for the ip address and returns NW_SUSPEND.


0020680 {
0020681          arp_port_t *arp_port;
0020682          int i;
0020683          arp_cache_t *ce;
0020684          time_t curr_time;
0020685 
0020686          assert(eth_port >= 0 && eth_port < eth_conf_nr);
0020687          arp_port= &arp_port_table[eth_port];
0020688          assert(arp_port->ap_state == APS_ARPMAIN ||
0020689                   (printf("ap_state= %d\n", arp_port->ap_state), 0));
0020690 
0020691          curr_time= get_time();
get_time()

get_time() returns the number of clock ticks since reboot.

Several of the clients (eth, arp, ip, tcp, and udp) use get_time() to determine an appropriate timeout value for a given operation. For example, the arp code calls get_time() to determine an appropriate amount of time to wait for a response from an arp request before giving up.


0020692 
0020693          ce= find_cache_ent (arp_port, ipaddr);
find_cache_ent()

find_cache_ent(arp_port, ipaddr) looks in the arp table for an ip address, returning a reference to the corresponding arp table entry (of type arp_cache_t) if it finds the ip address and NULL if it does not find the ip address.

Note that find_cache_ent() will return the entry even if the entry has expired or is incomplete.


0020694          if (ce && ce->ac_expire < curr_time)
Mark the entry as unused (i.e., ac_state = ACS_UNUSED) if the entry has expired.


0020695          {
0020696                   ce->ac_state= ACS_UNUSED;
0020697                   ce= NULL;
0020698          }
0020699          if (ce)
0020700          {
0020701                   /* Found an entry. This entry should be valid, unreachable
0020702                    * or incomplete.
0020703                    */
0020704                   ce->ac_lastuse= curr_time;
0020705                   if (ce->ac_state == ACS_VALID)
The arp table entry is valid. Return a reference to this ethernet address to the user.


0020706                   {
0020707                            *ethaddr= ce->ac_ethaddr;
0020708                            return NW_OK;
0020709                   }
0020710                   if (ce->ac_state == ACS_UNREACHABLE)
0020711                            return EDSTNOTRCH;
0020712                   assert(ce->ac_state == ACS_INCOMPLETE);
0020713                   return NW_SUSPEND;
0020714          }
0020715 
A valid arp table entry was not found.


0020716          if (arp_port->ap_flags & APF_CLIENTREQ)
0020717          {
0020718                   /* We should implement something to be able to do
0020719                    * multiple arp lookups at the same time. At the moment
0020720                    * we just return SUSPEND.
0020721                    */
0020722                   return NW_SUSPEND;
0020723          }
Send out an arp broadcast to determine the ethernet address of the system with ip address ipaddr (the second parameter to arp_ip_eth()).


0020724          ce= alloc_cache_ent();
0020725          ce->ac_flags= 0;
0020726          ce->ac_state= ACS_INCOMPLETE;
0020727          ce->ac_ipaddr= ipaddr;
0020728          ce->ac_port= arp_port;
0020729          ce->ac_expire= curr_time+ARP_EXP_TIME;
ARP_EXP_TIME is #define'd on line 20025 as 20 minutes, the default expiration time of the arp table entry.


0020730          ce->ac_lastuse= curr_time;
0020731          arp_port->ap_flags |= APF_CLIENTREQ|APF_MORE2WRITE | APF_CLIENTWRITE;
0020732          arp_port->ap_req_ipaddr= ipaddr;
0020733          arp_port->ap_req_count= 0;
0020734          if (!(arp_port->ap_flags & APF_ARP_WR_IP))
0020735                   setup_write(arp_port);
0020736          return NW_SUSPEND;
0020737 }
0020738 
0020739 PRIVATE void arp_timeout (fd, timer)
0020740 int fd;
0020741 timer_t *timer;
arp_timeout()

When an arp-request is sent out, a timer is set. If an arp-reply is not received for this arp-request within a half second, arp_timeout() is called. If fewer than four arp-requests have already been sent out, arp_timeout() simply calls setup_write() to send out another arp-request. If four arp-requests have already been sent out, the arp table entry is marked as unreachable and client_reply() is called to alert interested ip file descriptors.


0020742 {
0020743          arp_port_t *arp_port;
0020744          arp_cache_t *ce;
0020745          int level;
0020746          time_t curr_time;
0020747 
0020748          arp_port= &arp_port_table[fd];
Find the arp port whose index within arp_port_table is fd, arp_timeout()'s first parameter.


0020749 
0020750          assert (timer == &arp_port->ap_timer);
0020751 
0020752          if (++arp_port->ap_req_count < MAX_ARP_RETRIES)
Four attempts can be made to determine the ethernet address that corresponds to an ip address.

MAX_ARP_RETRIES is #define'd on line 20022:

#define MAX_ARP_RETRIES 5


0020753          {
0020754                   arp_port->ap_flags |= APF_CLIENTWRITE|APF_MORE2WRITE;
0020755                   if (!(arp_port->ap_flags & APF_ARP_WR_IP))
0020756                            setup_write(arp_port);
setup_write()

setup_write() initiates the sending of an arp-request or an arp-reply. Specifically, several fields of the arp port (e.g., ap_write_ipaddr - the ip address whose ethernet address is being sought) are set before calling eth_write() to send out the arp-request or arp-reply.


0020757          }
0020758          else
0020759          {
0020760                   ce= find_cache_ent(arp_port, arp_port->ap_req_ipaddr);
find_cache_ent()

find_cache_ent(arp_port, ipaddr) looks in the arp table for an ip address, returning a reference to the corresponding arp table entry (of type arp_cache_t) if it finds the ip address and NULL if it does not find the ip address.

Note that find_cache_ent() will return the entry even if the entry has expired or is incomplete.


0020761                   if (ce) {
Mark the cache entry as unreachable.


0020762                            assert(ce->ac_state == ACS_INCOMPLETE ||
0020763                                     (printf("ce->ac_state= %d\n", ce->ac_state),0));
0020764                            curr_time= get_time();
get_time()

get_time() returns the number of clock ticks since reboot.

Several of the clients (eth, arp, ip, tcp, and udp) use get_time() to determine an appropriate timeout value for a given operation. For example, the arp code calls get_time() to determine an appropriate amount of time to wait for a response from an arp request before giving up.


0020765                            ce->ac_state= ACS_UNREACHABLE;
0020766                            ce->ac_expire= curr_time+ ARP_NOTRCH_EXP_TIME;
Set the expiration time for this cache entry to 5 sec in the future.

ARP_NOTRCH_EXP_TIME is #define'd on line 20027:

#define ARP_NOTRCH_EXP_TIME (5*HZ) /* 5 seconds */


0020767                            ce->ac_lastuse= curr_time;
0020768 
0020769                            client_reply(arp_port, ce->ac_ipaddr, NULL);
client_reply()

client_reply() is called either upon arp resolution (generally through an arp-reply) or upon the timeout of an arp-request. client_reply() simply unsets the timer associated with the arp-request and calls ipeth_arp_reply() so that ethernet packets that were waiting in the queue waiting for arp resolution can either be sent (if the arp-request got an answer) or discarded (if the arp-request timed out).


0020770                   }
0020771          }
0020772 }
0020773 
0020774 /*
0020775  * $PchId: arp.c,v 1.6 1995/11/21 06:45:27 philip Exp $
0020776  */