0020001 /*
0020002 arp.c
0020003
0020004 Copyright 1995 Philip Homburg
0020005 */
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
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
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
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
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;
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;
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;
0020643 arp_port= &arp_port_table[eth_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;
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)
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)
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
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];
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) {
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 */
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.