/* arp.c Copyright 1995 Philip Homburg */ #include "inet.h" #include "type.h" #include "arp.h" #include "assert.h" #include "buf.h" #include "clock.h" #include "eth.h" #include "io.h" #include "sr.h" THIS_FILE #define ARP_CACHE_NR 64 #define MAX_ARP_RETRIES 5 #define ARP_TIMEOUT (HZ/2+1) /* .5 seconds */ #ifndef ARP_EXP_TIME #define ARP_EXP_TIME (20L*60L*HZ) /* 20 minutes */ #endif #define ARP_NOTRCH_EXP_TIME (5*HZ) /* 5 seconds */ #define ARP_INUSE_OFFSET (60*HZ) /* an entry in the cache can be deleted if its not used for 1 minute */ 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; #define a46_hdr a46_data.a46_data.a_hdr #define a46_pro a46_data.a46_data.a_pro #define a46_hln a46_data.a46_data.a_hln #define a46_pln a46_data.a46_data.a_pln #define a46_op a46_data.a46_data.a_op #define a46_sha a46_data.a46_data.a_sha #define a46_spa a46_data.a46_data.a_spa #define a46_tha a46_data.a46_data.a_tha #define a46_tpa a46_data.a46_data.a_tpa 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; #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 #define APS_INITIAL 0x00 #define APS_GETADDR 0x01 #define APS_ARPSTART 0x10 #define APS_ARPPROTO 0x20 #define APS_ARPMAIN 0x40 #define APS_ERROR 0x80 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; #define ACF_EMPTY 0 #define ACF_GOTREQ 1 #define ACS_UNUSED 0 #define ACS_INCOMPLETE 1 #define ACS_VALID 2 #define ACS_UNREACHABLE 3 FORWARD acc_t *arp_getdata ARGS(( int fd, size_t offset, size_t count, int for_ioctl )); FORWARD int arp_putdata ARGS(( int fd, size_t offset, acc_t *data, int for_ioctl )); FORWARD void arp_main ARGS(( arp_port_t *arp_port )); FORWARD void arp_timeout ARGS(( int fd, timer_t *timer )); FORWARD void setup_write ARGS(( arp_port_t *arp_port )); FORWARD void setup_read ARGS(( arp_port_t *arp_port )); FORWARD void process_arp_req ARGS(( arp_port_t *arp_port, acc_t *data )); FORWARD void client_reply ARGS(( arp_port_t *arp_port, ipaddr_t ipaddr, ether_addr_t *ethaddr )); FORWARD arp_cache_t *find_cache_ent ARGS(( arp_port_t *arp_port, ipaddr_t ipaddr )); FORWARD arp_cache_t *alloc_cache_ent ARGS(( void )); PRIVATE arp_port_t *arp_port_table; PRIVATE arp_cache_t arp_cache[ARP_CACHE_NR]; PUBLIC void arp_prep() { arp_port_table= alloc(eth_conf_nr * sizeof(arp_port_table[0])); } PUBLIC void arp_init() { arp_port_t *arp_port; int i; assert (BUF_S >= sizeof(struct nwio_ethstat)); assert (BUF_S >= sizeof(struct nwio_ethopt)); assert (BUF_S >= sizeof(arp46_t)); for (i=0, arp_port= arp_port_table; iap_state= APS_ERROR; /* Mark all ports as * unavailable */ } } PRIVATE void arp_main(arp_port) arp_port_t *arp_port; { int result; switch (arp_port->ap_state) { case APS_INITIAL: arp_port->ap_eth_fd= eth_open(arp_port->ap_eth_port, arp_port->ap_eth_port, arp_getdata, arp_putdata, 0); if (arp_port->ap_eth_fd<0) { DBLOCK(1, printf("arp.c: unable to open ethernet\n")); return; } arp_port->ap_state= APS_GETADDR; result= eth_ioctl (arp_port->ap_eth_fd, NWIOGETHSTAT); if ( result == NW_SUSPEND) { arp_port->ap_flags |= APF_SUSPEND; return; } assert(result == NW_OK); /* fall through */ case APS_GETADDR: /* Wait for IP address */ if (!(arp_port->ap_flags & APF_INADDR_SET)) return; /* fall through */ case APS_ARPSTART: arp_port->ap_state= APS_ARPPROTO; { arp_cache_t *cache; int i; cache= arp_cache; for (i=0; iac_state= ACS_UNUSED; cache->ac_flags= ACF_EMPTY; cache->ac_expire= 0; cache->ac_lastuse= 0; } } result= eth_ioctl (arp_port->ap_eth_fd, NWIOSETHOPT); if (result==NW_SUSPEND) { arp_port->ap_flags |= APF_SUSPEND; return; } assert(result == NW_OK); /* fall through */ case APS_ARPPROTO: arp_port->ap_state= APS_ARPMAIN; if (arp_port->ap_flags & APF_MORE2WRITE) setup_write(arp_port); setup_read(arp_port); return; #if !CRAMPED default: ip_panic(( "arp_main(&arp_port_table[%d]) called but ap_state=0x%x\n", arp_port->ap_eth_port, arp_port->ap_state )); #endif } } PRIVATE acc_t *arp_getdata (fd, offset, count, for_ioctl) int fd; size_t offset; size_t count; int for_ioctl; { arp_port_t *arp_port; arp46_t *arp; acc_t *data; int result; arp_port= &arp_port_table[fd]; switch (arp_port->ap_state) { case APS_ARPPROTO: if (!count) { result= (int)offset; if (result<0) { arp_port->ap_state= APS_ERROR; break; } if (arp_port->ap_flags & APF_SUSPEND) { arp_port->ap_flags &= ~APF_SUSPEND; arp_main(arp_port); } return NW_OK; } assert ((!offset) && (count == sizeof(struct nwio_ethopt))); { struct nwio_ethopt *ethopt; acc_t *acc; acc= bf_memreq(sizeof(*ethopt)); ethopt= (struct nwio_ethopt *)ptr2acc_data(acc); ethopt->nweo_flags= NWEO_COPY|NWEO_EN_BROAD| NWEO_TYPESPEC; ethopt->nweo_type= HTONS(ETH_ARP_PROTO); return acc; } case APS_ARPMAIN: assert (arp_port->ap_flags & APF_ARP_WR_IP); if (!count) { result= (int)offset; if (result<0) { DIFBLOCK(1, (result != NW_SUSPEND), printf( "arp.c: write error on port %d: error %d\n", fd, result)); arp_port->ap_state= APS_ERROR; break; } arp_port->ap_flags &= ~APF_ARP_WR_IP; if (arp_port->ap_flags & APF_ARP_WR_SP) setup_write(arp_port); return NW_OK; } assert (offset+count <= sizeof(arp46_t)); data= bf_memreq(sizeof(arp46_t)); arp= (arp46_t *)ptr2acc_data(data); data->acc_offset += offset; data->acc_length= count; if (arp_port->ap_write_code == ARP_REPLY) arp->a46_dstaddr= arp_port->ap_write_ethaddr; else { arp->a46_dstaddr.ea_addr[0]= 0xff; arp->a46_dstaddr.ea_addr[1]= 0xff; arp->a46_dstaddr.ea_addr[2]= 0xff; arp->a46_dstaddr.ea_addr[3]= 0xff; arp->a46_dstaddr.ea_addr[4]= 0xff; arp->a46_dstaddr.ea_addr[5]= 0xff; } arp->a46_hdr= HTONS(ARP_ETHERNET); arp->a46_pro= HTONS(ETH_IP_PROTO); arp->a46_hln= 6; arp->a46_pln= 4; arp->a46_op= htons(arp_port->ap_write_code); arp->a46_sha= arp_port->ap_ethaddr; memcpy (arp->a46_spa, &arp_port->ap_ipaddr, sizeof(ipaddr_t)); arp->a46_tha= arp_port->ap_write_ethaddr; memcpy (arp->a46_tpa, &arp_port->ap_write_ipaddr, sizeof(ipaddr_t)); return data; default: #if !CRAMPED printf("arp_getdata(%d, 0x%d, 0x%d) called but ap_state=0x%x\n", fd, offset, count, arp_port->ap_state); #endif break; } return 0; } PRIVATE int arp_putdata (fd, offset, data, for_ioctl) int fd; size_t offset; acc_t *data; int for_ioctl; { arp_port_t *arp_port; int result; struct nwio_ethstat *ethstat; arp_port= &arp_port_table[fd]; if (arp_port->ap_flags & APF_ARP_RD_IP) { if (!data) { result= (int)offset; if (result<0) { DIFBLOCK(1, (result != NW_SUSPEND), printf( "arp.c: read error on port %d: error %d\n", fd, result)); return NW_OK; } if (arp_port->ap_flags & APF_ARP_RD_SP) { arp_port->ap_flags &= ~(APF_ARP_RD_IP| APF_ARP_RD_SP); setup_read(arp_port); } else arp_port->ap_flags &= ~(APF_ARP_RD_IP| APF_ARP_RD_SP); return NW_OK; } assert (!offset); /* Warning: the above assertion is illegal; puts and gets of data can be brokenup in any piece the server likes. However we assume that the server is eth.c and it transfers only whole packets. */ data= bf_packIffLess(data, sizeof(arp46_t)); if (data->acc_length >= sizeof(arp46_t)) process_arp_req(arp_port,data); bf_afree(data); return NW_OK; } switch (arp_port->ap_state) { case APS_GETADDR: if (!data) { result= (int)offset; if (result<0) { arp_port->ap_state= APS_ERROR; break; } if (arp_port->ap_flags & APF_SUSPEND) { arp_port->ap_flags &= ~APF_SUSPEND; arp_main(arp_port); } return NW_OK; } compare (bf_bufsize(data), ==, sizeof(*ethstat)); data= bf_packIffLess(data, sizeof(*ethstat)); compare (data->acc_length, ==, sizeof(*ethstat)); ethstat= (struct nwio_ethstat *)ptr2acc_data(data); arp_port->ap_ethaddr= ethstat->nwes_addr; bf_afree(data); return NW_OK; default: #if !CRAMPED printf("arp_putdata(%d, 0x%d, 0x%lx) called but ap_state=0x%x\n", fd, offset, (unsigned long)data, arp_port->ap_state); #endif break; } return EGENERIC; } PRIVATE void setup_read(arp_port) arp_port_t *arp_port; { int result; while (!(arp_port->ap_flags & APF_ARP_RD_IP)) { arp_port->ap_flags |= APF_ARP_RD_IP; result= eth_read (arp_port->ap_eth_fd, ETH_MAX_PACK_SIZE); if (result == NW_SUSPEND) { arp_port->ap_flags |= APF_ARP_RD_SP; return; } DIFBLOCK(1, (result != NW_OK), printf("arp.c: eth_read(..,%d)=%d\n", ETH_MAX_PACK_SIZE, result)); } } PRIVATE void setup_write(arp_port) arp_port_t *arp_port; { int i, result; while (arp_port->ap_flags & APF_MORE2WRITE) { if (arp_port->ap_flags & APF_CLIENTWRITE) { arp_port->ap_flags &= ~APF_CLIENTWRITE; arp_port->ap_write_ipaddr= arp_port->ap_req_ipaddr; arp_port->ap_write_code= ARP_REQUEST; clck_timer(&arp_port->ap_timer, get_time() + ARP_TIMEOUT, arp_timeout, arp_port->ap_eth_port); } else { arp_cache_t *cache; cache= arp_cache; for (i=0; iac_flags & ACF_GOTREQ) && cache->ac_port == arp_port) { cache->ac_flags &= ~ACF_GOTREQ; arp_port->ap_write_ethaddr= cache-> ac_ethaddr; arp_port->ap_write_ipaddr= cache-> ac_ipaddr; arp_port->ap_write_code= ARP_REPLY; break; } } if (i>=ARP_CACHE_NR) { arp_port->ap_flags &= ~APF_MORE2WRITE; break; } } arp_port->ap_flags= (arp_port->ap_flags & ~APF_ARP_WR_SP) | APF_ARP_WR_IP; result= eth_write(arp_port->ap_eth_fd, sizeof(arp46_t)); if (result == NW_SUSPEND) arp_port->ap_flags |= APF_ARP_WR_SP; if (result<0) { DIFBLOCK(1, (result != NW_SUSPEND), printf("arp.c: eth_write(..,%d)=%d\n", sizeof(arp46_t), result)); return; } } } PRIVATE void process_arp_req (arp_port, data) arp_port_t *arp_port; acc_t *data; { arp46_t *arp; arp_cache_t *ce; int level; time_t curr_time; ipaddr_t spa, tpa; curr_time= get_time(); arp= (arp46_t *)ptr2acc_data(data); memcpy(&spa, arp->a46_spa, sizeof(ipaddr_t)); memcpy(&tpa, arp->a46_tpa, sizeof(ipaddr_t)); if (arp->a46_hdr != HTONS(ARP_ETHERNET) || arp->a46_hln != 6 || arp->a46_pro != HTONS(ETH_IP_PROTO) || arp->a46_pln != 4) return; ce= find_cache_ent(arp_port, spa); if (ce && ce->ac_expire < curr_time) { DBLOCK(0x10, printf("arp: expiring entry for "); writeIpAddr(ce->ac_ipaddr); printf("\n")); ce->ac_state= ACS_UNUSED; ce= NULL; } if (ce == NULL) { if (tpa != arp_port->ap_ipaddr) return; DBLOCK(0x10, printf("arp: allocating entry for "); writeIpAddr(spa); printf("\n")); ce= alloc_cache_ent(); ce->ac_flags= ACF_EMPTY; ce->ac_state= ACS_VALID; ce->ac_ethaddr= arp->a46_sha; ce->ac_ipaddr= spa; ce->ac_port= arp_port; ce->ac_expire= curr_time+ARP_EXP_TIME; ce->ac_lastuse= curr_time-ARP_INUSE_OFFSET; /* never used */ } if (ce->ac_state == ACS_INCOMPLETE || ce->ac_state == ACS_UNREACHABLE) { ce->ac_ethaddr= arp->a46_sha; if (ce->ac_state == ACS_INCOMPLETE) { ce->ac_state= ACS_VALID; client_reply(arp_port, spa, &arp->a46_sha); } else ce->ac_state= ACS_VALID; } /* Update fields in the arp cache. */ #if !CRAMPED if (memcmp(&ce->ac_ethaddr, &arp->a46_sha, sizeof(ce->ac_ethaddr)) != 0) { printf("arp: ethernet address for IP address "); writeIpAddr(spa); printf(" changed from "); writeEtherAddr(&ce->ac_ethaddr); printf(" to "); writeEtherAddr(&arp->a46_sha); printf("\n"); ce->ac_ethaddr= arp->a46_sha; } #else ce->ac_ethaddr= arp->a46_sha; #endif ce->ac_expire= curr_time+ARP_EXP_TIME; if (arp->a46_op == HTONS(ARP_REQUEST) && (tpa == arp_port->ap_ipaddr)) { ce->ac_flags |= ACF_GOTREQ; arp_port->ap_flags |= APF_MORE2WRITE; if (!(arp_port->ap_flags & APF_ARP_WR_IP)) setup_write(arp_port); } } PRIVATE void client_reply (arp_port, ipaddr, ethaddr) arp_port_t *arp_port; ipaddr_t ipaddr; ether_addr_t *ethaddr; { if ((arp_port->ap_flags & APF_CLIENTREQ) && ipaddr == arp_port->ap_req_ipaddr) { arp_port->ap_flags &= ~(APF_CLIENTREQ|APF_CLIENTWRITE); clck_untimer(&arp_port->ap_timer); } (*arp_port->ap_arp_func)(arp_port->ap_ip_port, ipaddr, ethaddr); } PRIVATE arp_cache_t *find_cache_ent (arp_port, ipaddr) arp_port_t *arp_port; ipaddr_t ipaddr; { arp_cache_t *cache; int i; for (i=0, cache= arp_cache; iac_state != ACS_UNUSED && cache->ac_port == arp_port && cache->ac_ipaddr == ipaddr) { return cache; } } return NULL; } PRIVATE arp_cache_t *alloc_cache_ent() { arp_cache_t *cache, *old; int i; old= NULL; for (i=0, cache= arp_cache; iac_state == ACS_UNUSED) return cache; if (cache->ac_state == ACS_INCOMPLETE) continue; if (!old || cache->ac_lastuse < old->ac_lastuse) old= cache; } assert(old); return old; } PUBLIC void arp_set_ipaddr (eth_port, ipaddr) int eth_port; ipaddr_t ipaddr; { arp_port_t *arp_port; int i; if (eth_port < 0 || eth_port >= eth_conf_nr) return; arp_port= &arp_port_table[eth_port]; arp_port->ap_ipaddr= ipaddr; arp_port->ap_flags |= APF_INADDR_SET; arp_port->ap_flags &= ~APF_SUSPEND; if (arp_port->ap_state == APS_GETADDR) arp_main(arp_port); } PUBLIC int arp_set_cb(eth_port, ip_port, arp_func) int eth_port; int ip_port; arp_func_t arp_func; { arp_port_t *arp_port; int i; assert(eth_port >= 0); if (eth_port >= eth_conf_nr) return ENXIO; arp_port= &arp_port_table[eth_port]; arp_port->ap_eth_port= eth_port; arp_port->ap_ip_port= ip_port; arp_port->ap_state= APS_INITIAL; arp_port->ap_flags= APF_EMPTY; arp_port->ap_arp_func= arp_func; arp_main(arp_port); return NW_OK; } PUBLIC int arp_ip_eth (eth_port, ipaddr, ethaddr) int eth_port; ipaddr_t ipaddr; ether_addr_t *ethaddr; { arp_port_t *arp_port; int i; arp_cache_t *ce; time_t curr_time; assert(eth_port >= 0 && eth_port < eth_conf_nr); arp_port= &arp_port_table[eth_port]; assert(arp_port->ap_state == APS_ARPMAIN || (printf("ap_state= %d\n", arp_port->ap_state), 0)); curr_time= get_time(); ce= find_cache_ent (arp_port, ipaddr); if (ce && ce->ac_expire < curr_time) { ce->ac_state= ACS_UNUSED; ce= NULL; } if (ce) { /* Found an entry. This entry should be valid, unreachable * or incomplete. */ ce->ac_lastuse= curr_time; if (ce->ac_state == ACS_VALID) { *ethaddr= ce->ac_ethaddr; return NW_OK; } if (ce->ac_state == ACS_UNREACHABLE) return EDSTNOTRCH; assert(ce->ac_state == ACS_INCOMPLETE); return NW_SUSPEND; } if (arp_port->ap_flags & APF_CLIENTREQ) { /* We should implement something to be able to do * multiple arp lookups at the same time. At the moment * we just return SUSPEND. */ return NW_SUSPEND; } ce= alloc_cache_ent(); ce->ac_flags= 0; ce->ac_state= ACS_INCOMPLETE; ce->ac_ipaddr= ipaddr; ce->ac_port= arp_port; ce->ac_expire= curr_time+ARP_EXP_TIME; ce->ac_lastuse= curr_time; arp_port->ap_flags |= APF_CLIENTREQ|APF_MORE2WRITE | APF_CLIENTWRITE; arp_port->ap_req_ipaddr= ipaddr; arp_port->ap_req_count= 0; if (!(arp_port->ap_flags & APF_ARP_WR_IP)) setup_write(arp_port); return NW_SUSPEND; } PRIVATE void arp_timeout (fd, timer) int fd; timer_t *timer; { arp_port_t *arp_port; arp_cache_t *ce; int level; time_t curr_time; arp_port= &arp_port_table[fd]; assert (timer == &arp_port->ap_timer); if (++arp_port->ap_req_count < MAX_ARP_RETRIES) { arp_port->ap_flags |= APF_CLIENTWRITE|APF_MORE2WRITE; if (!(arp_port->ap_flags & APF_ARP_WR_IP)) setup_write(arp_port); } else { ce= find_cache_ent(arp_port, arp_port->ap_req_ipaddr); if (ce) { assert(ce->ac_state == ACS_INCOMPLETE || (printf("ce->ac_state= %d\n", ce->ac_state),0)); curr_time= get_time(); ce->ac_state= ACS_UNREACHABLE; ce->ac_expire= curr_time+ ARP_NOTRCH_EXP_TIME; ce->ac_lastuse= curr_time; client_reply(arp_port, ce->ac_ipaddr, NULL); } } } /* * $PchId: arp.c,v 1.6 1995/11/21 06:45:27 philip Exp $ */