/* arp.c - ARP state machine */ /* Written 1995-2000 by Werner Almesberger, EPFL-LRC/ICA */ #if HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include /* for linux/if_arp.h */ #include /* for ntohs, etc. */ #include #include #include #include #include #include #include "atmd.h" #include "atmarp.h" #include "atmarpd.h" #include "table.h" #include "itf.h" #include "io.h" #include "arp.h" #define COMPONENT "ARP" #define SEC 1000000 #define T_REPLY (1*SEC) /* 1 sec */ #define R_REPLY 5 /* 5 retries */ #ifdef FREQUENT_REVAL #define T_CREVAL (30*SEC) /* 30 sec, client */ #define T_SREVAL (35*SEC) /* 35 sec, server */ #else #define T_CREVAL (600*SEC) /* 10 min, client (RFC1577: <= 15 min) */ #define T_SREVAL (1500*SEC) /* 25 min, server (RFC1577: >= 20 min) */ #endif #define R_CREVAL 0 /* no retries */ #define R_SREVAL 0 /* no retries */ #define T_RETRY (1*SEC) /* 1 sec */ #define R_RETRY 25000 /* enough try keep trying during a long weekend */ #define T_REGISTER (5*SEC) /* 5 sec, registration with ATMARP server */ #define R_REGISTER 0 /* no explicit retries */ #define MAX_DELAY (300*SEC) /* never wait more than 5 min for anything */ #define ENTER_TIMER_HANDLER(entry) entry->timer = NULL #define START_TIMER(entry,tmr) ({ \ if (entry->timer) stop_timer(entry->timer); \ entry->timeout = T_##tmr; \ entry->timer = start_timer(entry->timeout,timer_expiration,entry); \ entry->retries = R_##tmr; }) #define STOP_TIMER(entry) ({ \ if (entry->timer) { \ stop_timer(entry->timer); \ entry->timer = NULL; \ } }) static void discard_vcc(VCC *vcc) { if (do_close(vcc->fd)) diag(COMPONENT,DIAG_ERROR,"close: %s",strerror(errno)); if (vcc->entry) Q_REMOVE(vcc->entry->vccs,vcc); else Q_REMOVE(unidirectional_vccs,vcc); free(vcc); } void discard_vccs(ENTRY *entry) { VCC *vcc,*next; for (vcc = entry->vccs; vcc; vcc = next) { next = vcc->next; discard_vcc(vcc); } } void discard_entry(ENTRY *entry) { if (entry->notify) diag(COMPONENT,DIAG_FATAL,"discard_entry: entry %p had notifications"); STOP_TIMER(entry); discard_vccs(entry); if (!entry->itf) Q_REMOVE(unknown_incoming,entry); else { if (entry == entry->itf->arp_srv) entry->itf->arp_srv = NULL; Q_REMOVE(entry->itf->table,entry); } if (entry->addr) free(entry->addr); free(entry); } /* * Returns: * <0 there's no ARP server * 0 ARP server connection establishment in progress * >0 ready to send ARP_REQUEST */ static int want_arp_srv(const ITF *itf) { VCC *vcc; int fd; if (!itf->arp_srv) return -1; for (vcc = itf->arp_srv->vccs; vcc; vcc = vcc->next) if (!vcc->connecting) return 1; if (itf->arp_srv->vccs) return 0; if ((fd = connect_vcc((struct sockaddr *) itf->arp_srv->addr, &itf->arp_srv->qos,itf->arp_srv->sndbuf,CLIP_DEFAULT_IDLETIMER)) < 0) return 0; vcc = alloc_t(VCC); vcc->active = 1; vcc->connecting = 1; vcc->fd = fd; vcc->entry = itf->arp_srv; Q_INSERT_HEAD(itf->arp_srv->vccs,vcc); return 0; } static void timer_expiration(void *user); void vcc_detach(ENTRY *entry) { ENTRY *walk; /* immediately try to bring it up again if this was the connection to the ATMARP server and we still need it. Should delay this in case the ARP server has a real problem. @@@ */ /* UPDATE: the delay might work now ... */ if (entry->itf && entry->itf->arp_srv == entry) for (walk = entry->itf->table; walk; walk = walk->next) { if (walk->state == as_resolv) break; if (walk) { if (entry->state == as_valid) START_TIMER(entry,REGISTER); /* (void) want_arp_srv(entry->itf); */ return; } } if (!entry->vccs && entry->state == as_invalid && !(entry->flags & ATF_PERM)) discard_entry(entry); } static void put_ip(unsigned char **here,uint32_t ip,unsigned char *len) { if (!ip) { *len = 0; return; } /*ip = htonl(ip); - no keep 'em in network byte order */ memcpy(*here,&ip,4); *len = 4; (*here) += 4; } static void put_addr(unsigned char **here,struct sockaddr_atmsvc *addr, unsigned char *num_tl,unsigned char *sub_tl) { if (!addr) return; if (addr->sas_family != AF_ATMSVC) return; if (!*addr->sas_addr.pub) *sub_tl = 0; else { *num_tl = strlen(addr->sas_addr.pub) | TL_E164; memcpy(*here,addr->sas_addr.pub,*num_tl & TL_LEN); (*here) += *num_tl & TL_LEN; num_tl = sub_tl; } if (!*addr->sas_addr.prv) *num_tl = 0; else { *num_tl = ATM_ESA_LEN; memcpy(*here,addr->sas_addr.prv,ATM_ESA_LEN); (*here) += ATM_ESA_LEN; } } static const unsigned char llc_oui_arp[] = { 0xaa, /* DSAP: non-ISO */ 0xaa, /* SSAP: non-ISO */ 0x03, /* Ctrl: Unnumbered Information Command PDU */ 0x00, /* OUI: EtherType */ 0x00, 0x00, 0x08, /* ARP protocol */ 0x06 }; static void send_arp(int fd,unsigned short op,uint32_t local_ip, struct sockaddr_atmsvc *local_addr,uint32_t remote_ip, struct sockaddr_atmsvc *remote_addr) { struct sockaddr_atmsvc local; struct atmarphdr *hdr; unsigned char *buffer,*here; if (!local_addr) { if (get_local(fd,&local) < 0) return; local_addr = &local; } buffer = alloc(MAX_ATMARP_SIZE+RFC1483LLC_LEN); memcpy(buffer,llc_oui_arp,RFC1483LLC_LEN); hdr = (struct atmarphdr *) (buffer+RFC1483LLC_LEN); memset(hdr,0,MAX_ATMARP_SIZE); hdr->ar_hrd = htons(ARPHRD_ATM); hdr->ar_pro = htons(ETH_P_IP); hdr->ar_op = htons(op); here = hdr->data; put_addr(&here,local_addr,&hdr->ar_shtl,&hdr->ar_sstl); put_ip(&here,local_ip,&hdr->ar_spln); put_addr(&here,remote_addr,&hdr->ar_thtl,&hdr->ar_tstl); put_ip(&here,remote_ip,&hdr->ar_tpln); send_packet(fd,buffer,here-buffer); free(buffer); } static void arp_request(ITF *itf,uint32_t ip) { VCC *vcc; diag(COMPONENT,DIAG_DEBUG,"sending ARP request"); if (!itf->arp_srv) { diag(COMPONENT,DIAG_ERROR,"no ARP server"); return; } for (vcc = itf->arp_srv->vccs; vcc; vcc = vcc->next) if (!vcc->connecting) break; if (!vcc) { diag(COMPONENT,DIAG_ERROR,"ARP server has no usable VCC"); return; } send_arp(vcc->fd,ARPOP_REQUEST,itf->local_ip,NULL,ip,NULL); } static void inarp_for_itf(const VCC *vcc,const ITF *itf) { if (itf->local_ip) { diag(COMPONENT,DIAG_DEBUG," for itf %d",itf->number); assert(vcc->entry); send_arp(vcc->fd,ARPOP_InREQUEST,itf->local_ip,NULL,0, vcc->entry->svc ? vcc->entry->addr : NULL); } } static void inarp_request(ENTRY *entry) { VCC *vcc; ITF *itf; diag(COMPONENT,DIAG_DEBUG,"sending an InARP request for each IP interface"); for (vcc = entry->vccs; vcc; vcc = vcc->next) { if (vcc->connecting) continue; if (entry->itf) inarp_for_itf(vcc,entry->itf); else for (itf = itfs; itf; itf = itf->next) inarp_for_itf(vcc,itf); } } static void arp_nak(VCC *vcc,uint32_t src_ip,uint32_t tgt_ip, struct sockaddr_atmsvc *tgt_addr) { diag(COMPONENT,DIAG_DEBUG,"sending ARP NAK"); send_arp(vcc->fd,ARPOP_NAK,tgt_ip,tgt_addr,src_ip,NULL); } static void arp_reply(VCC *vcc,uint32_t src_ip, struct sockaddr_atmsvc *src_addr,uint32_t tgt_ip, struct sockaddr_atmsvc *tgt_addr) { diag(COMPONENT,DIAG_DEBUG,"sending ARP reply"); send_arp(vcc->fd,ARPOP_REPLY,src_ip,src_addr,tgt_ip,tgt_addr); } static void inarp_reply(VCC *vcc,uint32_t ip,struct sockaddr_atmsvc *addr) { ITF *itf; diag(COMPONENT,DIAG_DEBUG,"sending InARP reply"); itf = lookup_itf_by_ip(ip); if (!itf) { diag(COMPONENT,DIAG_ERROR,"InARP request is unroutable"); return; } send_arp(vcc->fd,ARPOP_InREPLY,itf->local_ip,NULL,ip,addr); } static void send_notifications(ENTRY *entry,int success) { NOTIFY *this; while ((this = entry->notify)) { entry->notify = this->next; notify(&this->ctx,entry->ip,success ? entry : NULL); free(this); } } static void revalidate(ENTRY *entry) { entry->state = as_resolv; if (want_arp_srv(entry->itf) <= 0) return; arp_request(entry->itf,entry->ip); START_TIMER(entry,REPLY); } static void timer(ENTRY *entry) { switch (entry->state) { case as_resolv: arp_request(entry->itf,entry->ip); break; case as_invalid: if (entry->vccs) inarp_request(entry); else if (!(entry->flags & ATF_ARPSRV)) diag(COMPONENT,DIAG_INFO,"as_invalid, no VCCs, but not " "ARP server entry"); /* this is a valid condition if we're the ATMARP server and the remote disconnected */ else if (!want_arp_srv(entry->itf)) STOP_TIMER(entry); break; case as_valid: if (entry->flags & ATF_ARPSRV) { if (!want_arp_srv(entry->itf)) STOP_TIMER(entry); break; } /* fall through */ default: diag(COMPONENT,DIAG_FATAL,"timer in state %d",entry->state); } } static void timeout(ENTRY *entry) { VCC *vcc,*next; entry->timer = NULL; switch (entry->state) { case as_resolv: send_notifications(entry,0); if ((entry->flags & ATF_ARPSRV) && !entry->vccs) { if (entry->itf) want_arp_srv(entry->itf); break; } if (!entry->vccs && !(entry->flags & (ATF_PERM | ATF_ARPSRV))) discard_entry(entry); else entry->state = as_invalid; break; case as_valid: if (!entry->vccs && !(entry->flags & (ATF_PERM | ATF_ARPSRV)) && entry->itf->arp_srv) { discard_entry(entry); break; } for (vcc = entry->vccs; vcc; vcc = next) { next = vcc->next; if (!vcc->connecting) if (set_ip(vcc->fd,0) < 0) { diag(COMPONENT,DIAG_ERROR,"set_ip(0): %s", strerror(errno)); disconnect_vcc(vcc); } } if (entry->svc && entry->itf->arp_srv && !(entry->flags & ATF_ARPSRV)) revalidate(entry); else { inarp_request(entry); START_TIMER(entry,REPLY); entry->state = as_invalid; } break; case as_invalid: if (!entry->svc) { inarp_request(entry); START_TIMER(entry,REPLY); } else if ((!entry->itf || !entry->itf->arp_srv) && !(entry->flags & ATF_PERM)) discard_entry(entry); break; default: diag(COMPONENT,DIAG_FATAL,"timed out in state %s", entry_state_name[entry->state]); } } static void timer_expiration(void *user) { ENTRY *entry; unsigned char *ipp; entry = user; ipp = (unsigned char *) &entry->ip; diag(COMPONENT,DIAG_DEBUG,"TIMEOUT (entry %d.%d.%d.%d), %d retries",ipp[0], ipp[1],ipp[2],ipp[3],entry->retries); if (!entry->retries) { entry->timer = NULL; timeout(entry); } else { entry->retries--; entry->timeout *= 2; if (entry->timeout > MAX_DELAY) entry->timeout = MAX_DELAY; entry->timer = start_timer(entry->timeout,timer_expiration,entry); timer(entry); } } static void connect_me(ENTRY *entry) { VCC *vcc; int fd; assert(entry->addr->sas_family == AF_ATMSVC); if ((fd = connect_vcc((struct sockaddr *) entry->addr,&entry->qos, entry->sndbuf,CLIP_DEFAULT_IDLETIMER)) < 0) return; vcc = alloc_t(VCC); vcc->active = 1; vcc->connecting = 1; vcc->fd = fd; vcc->entry = entry; Q_INSERT_HEAD(entry->vccs,vcc); } static void adjust_qos(ITF *itf,struct atm_qos *qos,int null_encap) { if (!qos->txtp.max_sdu) qos->txtp.max_sdu = RFC1626_MTU+(null_encap ? 0 : RFC1483LLC_LEN); if (!qos->rxtp.max_sdu) qos->rxtp.max_sdu = RFC1626_MTU+(null_encap ? 0 : RFC1483LLC_LEN); } /* * Return -1 if VCC has been closed */ static int learn(VCC *vcc,uint32_t ip,struct sockaddr_atmsvc *addr) { ENTRY *entry; ITF *itf; VCC *walk,*next; unsigned char *ipp; int result = 0; if (!ip) return 0; ipp = (unsigned char *) &ip; itf = lookup_itf_by_ip(ip); if (!itf) { diag(COMPONENT,DIAG_ERROR,"got unroutable IP address %d.%d.%d.%d", ipp[0],ipp[1],ipp[2],ipp[3]); return 0; } entry = lookup_ip(itf,ip); assert(!vcc || vcc->entry); /* * If the entry on which we received the update isn't dangling but it * doesn't correspond to the one with the address, ... */ if (entry && vcc && vcc->entry->itf && entry != vcc->entry) { diag(COMPONENT,DIAG_DEBUG,"collision on %d.%d.%d.%d",ipp[0],ipp[1], ipp[2],ipp[3]); return 0; } /* * If the entry on which we received the update is dangling and we found * an entry that already describes that IP address, ... */ if (entry && vcc && !vcc->entry->itf) { if (!entry->svc) { diag(COMPONENT,DIAG_ERROR,"attempt to overwrite PVC for IP " "%d.%d.%d.%d",ipp[0],ipp[1],ipp[2],ipp[3]); return 0; } STOP_TIMER(vcc->entry); Q_REMOVE(unknown_incoming,vcc->entry); free(vcc->entry); vcc->entry = entry; Q_INSERT_HEAD(entry->vccs,vcc); set_sndbuf(vcc); entry->flags &= ~ATF_NOVC; assert(!vcc->connecting); if (set_ip(vcc->fd,ip) < 0) { diag(COMPONENT,DIAG_ERROR,"set_ip: %s",strerror(errno)); disconnect_vcc(vcc); vcc = NULL; result = -1; } } /* * If we still don't have an entry, we try to use the entry that already * belongs to the VCC (InARP), or we create a new one (ARP). */ if (!entry) { if (vcc) { entry = vcc->entry; if (!entry->itf) { Q_REMOVE(unknown_incoming,entry); entry->sndbuf = itf->sndbuf; set_sndbuf(vcc); } else if (entry->ip && entry->ip != ip && (entry->flags & ATF_PERM) && !(entry->flags & ATF_ARPSRV)) { diag(COMPONENT,DIAG_ERROR,"ignoring attempt to change IP " "address of permanent entry (to %d.%d.%d.%d)",ipp[0], ipp[1],ipp[2],ipp[3]); return result; } } else { entry = alloc_entry(1); entry->flags = ATF_PUBL; } } if (!atmsvc_addr_in_use(*addr)) addr = NULL; if (entry->addr && addr && (entry->flags & ATF_PERM) && !atm_equal((struct sockaddr *) entry->addr,(struct sockaddr *) addr,0,0)) { diag(COMPONENT,DIAG_ERROR,"ignoring attempt to change ATM address of " "permanent entry"); return result; } if (entry->state == as_valid && entry->ip == ip && (!addr || (entry->addr && atm_equal((struct sockaddr *) entry->addr,(struct sockaddr *) addr,0, 0)))) return result; /* no news */ STOP_TIMER(entry); if (entry->ip != ip) send_notifications(entry,0); entry->ip = ip; if (!entry->itf) { entry->itf = itf; /* @@@ * Need to fix this is in case we allow entries without a valid IP * address but with a pre-set QOS, e.g. a VC on a given PVC with an * unknown remote end. */ entry->qos = entry->itf->qos; adjust_qos(entry->itf,&entry->qos,0); Q_INSERT_HEAD(itf->table,entry); } if (entry->itf != itf) diag(COMPONENT,DIAG_ERROR,"interesting, interface has changed ... " "(%d -> %d)",entry->itf->number,itf->number); if (addr) { if (!entry->addr) entry->addr = alloc(sizeof(*addr)); *entry->addr = *addr; if (merge) { ENTRY *incoming; while ((incoming = lookup_incoming(addr))) { STOP_TIMER(incoming); Q_REMOVE(unknown_incoming,incoming); incoming->vccs->entry = entry; Q_INSERT_HEAD(entry->vccs,incoming->vccs); set_sndbuf(incoming->vccs); free(incoming); } } } for (walk = entry->vccs; walk; walk = next) { next = walk->next; if (!walk->connecting) if (set_ip(walk->fd,ip) < 0) { diag(COMPONENT,DIAG_ERROR,"set_ip: %s",strerror(errno)); disconnect_vcc(walk); if (walk == vcc) result = -1; } } if (entry->state != as_valid) { if (!entry->vccs && itf->arp_srv && !(entry->flags & ATF_NOVC)) connect_me(entry); send_notifications(entry,1); } if ((entry->flags & ATF_ARPSRV) || !(entry->flags & ATF_PERM)) { if (entry->itf->arp_srv) START_TIMER(entry,CREVAL); else START_TIMER(entry,SREVAL); } entry->state = as_valid; return result; } static void learn_nak(uint32_t ip) { ITF *itf; ENTRY *entry; if (!ip) return; itf = lookup_itf_by_ip(ip); if (!itf) return; entry = lookup_ip(itf,ip); if (!entry || entry->state != as_resolv) return; send_notifications(entry,0); if (entry->flags & ATF_PERM) return; if (entry->vccs) entry->state = as_invalid; else discard_entry(entry); } /* * Returns: * <0 resolution is not possible (*entry may be clobbered) * 0 resolution has succeeded (entry in *entry) * >0 resolution is proceeding (entry in *entry) */ static int resolve(ITF *itf,uint32_t ip,ENTRY **entry,int want_vc) { *entry = lookup_ip(itf,ip); if ((!*entry || (*entry)->state != as_valid) && !itf->arp_srv) return -1; /* bad luck - no ARP server when we need one */ if (*entry) { if (want_vc) (*entry)->flags &= ~ATF_NOVC; switch ((*entry)->state) { case as_resolv: return 1; /* somebody else is already taking care of that */ case as_valid: if (!(*entry)->vccs && !((*entry)->flags & ATF_NOVC)) connect_me(*entry); return 0; case as_invalid: if ((*entry)->svc && (*entry)->itf && (*entry)->itf->arp_srv && !((*entry)->flags & ATF_ARPSRV)) break; return -1; default: diag(COMPONENT,DIAG_FATAL,"bad state %d",(*entry)->state); } } else { *entry = alloc_entry(1); (*entry)->flags = ATF_PUBL | (want_vc ? 0 : ATF_NOVC); (*entry)->ip = ip; (*entry)->itf = itf; Q_INSERT_HEAD(itf->table,*entry); (*entry)->qos = itf->qos; } revalidate(*entry); return 1; } void need_ip(int itf_num,uint32_t ip) { ITF *itf; ENTRY *entry; diag(COMPONENT,DIAG_DEBUG,"itf %d needs %d.%d.%d.%d",itf_num, ((unsigned char *) &ip)[0],((unsigned char *) &ip)[1], ((unsigned char *) &ip)[2],((unsigned char *) &ip)[3]); if (!(itf = lookup_itf(itf_num))) { diag(COMPONENT,DIAG_ERROR,"itf %d not found",itf_num); return; } (void) resolve(itf,ip,&entry,1); } void query_ip(const UN_CTX *ctx,uint32_t ip) { ITF *itf; ENTRY *entry; NOTIFY *notifier; int result; diag(COMPONENT,DIAG_DEBUG,"query for %d.%d.%d.%d", ((unsigned char *) &ip)[0],((unsigned char *) &ip)[1], ((unsigned char *) &ip)[2],((unsigned char *) &ip)[3]); if (!(itf = lookup_itf_by_ip(ip))) { diag(COMPONENT,DIAG_WARN,"itf for %d.%d.%d.%d not found", ((unsigned char *) &ip)[0],((unsigned char *) &ip)[1], ((unsigned char *) &ip)[2],((unsigned char *) &ip)[3]); notify(ctx,ip,NULL); return; } result = resolve(itf,ip,&entry,0); if (result < 0) { notify(ctx,ip,NULL); return; } if (!result) { notify(ctx,ip,entry); return; } notifier = alloc_t(NOTIFY); notifier->ctx = *ctx; notifier->next = entry->notify; entry->notify = notifier; } static void *get_addr(unsigned char **here,int len) { if (!len) return NULL; (*here) += len; return *here-len; } static void set_addr(struct sockaddr_atmsvc *addr,void *num,void *sub, int num_tl,int sub_tl) { memset(addr,0,sizeof(struct sockaddr_atmsvc)); addr->sas_family = AF_ATMSVC; if (num_tl & TL_E164) if ((num_tl & TL_LEN) > ATM_E164_LEN) diag(COMPONENT,DIAG_ERROR,"bad E.164 length (%d)",num_tl & TL_LEN); else { memcpy(addr->sas_addr.pub,num,num_tl & TL_LEN); if (sub) { if (sub_tl != ATM_ESA_LEN) { diag(COMPONENT,DIAG_ERROR,"bad ESA length (%d)",sub_tl); *addr->sas_addr.pub = 0; } else memcpy(addr->sas_addr.prv,sub,ATM_ESA_LEN); } } else if (num) { if (num_tl != ATM_ESA_LEN) diag(COMPONENT,DIAG_ERROR,"bad ESA length (%d)",num_tl); else memcpy(addr->sas_addr.prv,num,ATM_ESA_LEN); } } static uint32_t get_ip(unsigned char *ptr) { if (!ptr) return 0; /* awkward, but this way we avoid bus errors on architectures that don't support mis-aligned accesses */ return htonl((ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3]); } void incoming_arp(VCC *vcc,struct atmarphdr *hdr,int len) { ITF *itf; ENTRY *entry; void *sha,*ssa,*spa,*tha,*tsa,*tpa; struct sockaddr_atmsvc source,target; uint32_t src_ip,tgt_ip; unsigned char *here; if (len < hdr->data-(unsigned char *) hdr) { diag(COMPONENT,DIAG_ERROR,"got truncated ARP packet (%d bytes)",len); return; } if (hdr->ar_hrd != htons(ARPHRD_ATM)) { diag(COMPONENT,DIAG_ERROR,"unknown hw protocol 0x%04x", ntohs(hdr->ar_hrd)); return; } if (hdr->ar_pro != htons(ETH_P_IP)) { diag(COMPONENT,DIAG_ERROR,"unknown upper protocol 0x%04x", ntohs(hdr->ar_pro)); return; } if (!(hdr->ar_shtl & TL_LEN)) hdr->ar_shtl = 0; /* paranoia */ if (!(hdr->ar_thtl & TL_LEN)) hdr->ar_thtl = 0; here = hdr->data; sha = get_addr(&here,hdr->ar_shtl & TL_LEN); ssa = get_addr(&here,hdr->ar_sstl & TL_LEN); spa = get_addr(&here,hdr->ar_spln); tha = get_addr(&here,hdr->ar_thtl & TL_LEN); tsa = get_addr(&here,hdr->ar_tstl & TL_LEN); tpa = get_addr(&here,hdr->ar_tpln); if (here-(unsigned char *) hdr > len) { diag(COMPONENT,DIAG_ERROR,"message too short (got %d, need %d)",len, here-(unsigned char *) hdr); return; } set_addr(&source,sha,ssa,hdr->ar_shtl,hdr->ar_sstl); set_addr(&target,tha,tsa,hdr->ar_thtl,hdr->ar_tstl); src_ip = get_ip(spa); tgt_ip = get_ip(tpa); { unsigned char *ipp; char buffer[MAX_ATM_ADDR_LEN+1]; ipp = (unsigned char *) &src_ip; diag(COMPONENT,DIAG_DEBUG," SRC IP: %d.%d.%d.%d",ipp[0],ipp[1],ipp[2], ipp[3]); if (atm2text(buffer,MAX_ATM_ADDR_LEN+1,(struct sockaddr *) &source,pretty) >= 0) diag(COMPONENT,DIAG_DEBUG," SRC ATM: %s",buffer); ipp = (unsigned char *) &tgt_ip; diag(COMPONENT,DIAG_DEBUG," DST IP: %d.%d.%d.%d",ipp[0],ipp[1],ipp[2], ipp[3]); if (atm2text(buffer,MAX_ATM_ADDR_LEN+1,(struct sockaddr *) &target,pretty) >= 0) diag(COMPONENT,DIAG_DEBUG," DST ATM: %s",buffer); } switch (ntohs(hdr->ar_op)) { case ARPOP_REQUEST: diag(COMPONENT,DIAG_DEBUG,"got ARP_REQ"); if (learn(vcc,src_ip,&source)) break; entry = NULL; itf = lookup_itf_by_ip(tgt_ip); entry = itf ? lookup_ip(itf,tgt_ip) : NULL; if (entry && entry->state == as_valid && (entry->flags & ATF_PUBL)) { if (entry->addr) arp_reply(vcc,tgt_ip,entry->addr,src_ip,&source); else arp_nak(vcc,tgt_ip,src_ip,&source); } else { if (itf && itf->local_ip == tgt_ip) arp_reply(vcc,tgt_ip,NULL,src_ip,&source); else arp_nak(vcc,tgt_ip,src_ip,&source); } break; case ARPOP_REPLY: diag(COMPONENT,DIAG_DEBUG,"got ARP_REP"); if (!vcc->entry || !(vcc->entry->flags & ATF_ARPSRV)) { diag(COMPONENT,DIAG_ERROR,"got ARP response from charlatan"); break; } (void) learn(NULL,src_ip,&source); break; case ARPOP_InREQUEST: diag(COMPONENT,DIAG_DEBUG,"got InARP_REQ"); if (!learn(vcc,src_ip,&source)) inarp_reply(vcc,src_ip,&source); break; case ARPOP_InREPLY: diag(COMPONENT,DIAG_DEBUG,"got InARP_REP"); (void) learn(vcc,src_ip,&source); break; case ARPOP_NAK: diag(COMPONENT,DIAG_DEBUG,"got ARP_NAK"); if (!vcc->entry || !(vcc->entry->flags & ATF_ARPSRV)) { diag(COMPONENT,DIAG_ERROR,"got ARP response from charlatan"); break; } learn_nak(tgt_ip); break; default: diag(COMPONENT,DIAG_ERROR,"unrecognized ARP op 0x%x", ntohs(hdr->ar_op)); } } static int ioctl_set_pvc(ITF *itf,uint32_t ip,struct sockaddr_atmpvc *addr, const struct atm_qos *qos,int sndbuf,int flags) { ENTRY *entry; VCC *vcc; int fd,result; if (lookup_ip(itf,ip)) return -EEXIST; if ((fd = connect_vcc((struct sockaddr *) addr,qos,sndbuf,0)) < 0) return fd; if ((result = set_ip(fd,ip)) < 0) { do_close(fd); return result; } if (flags & ATF_NULL) { if ((result = set_encap(fd,0)) < 0) return result; flags |= ATF_PERM; } entry = alloc_entry(0); entry->state = as_valid; entry->ip = ip; entry->qos = *qos; entry->sndbuf = sndbuf; entry->flags = flags; entry->itf = itf; vcc = alloc_t(VCC); vcc->active = 1; vcc->connecting = 0; vcc->fd = fd; vcc->entry = entry; if (!(flags & ATF_PERM)) START_TIMER(entry,CREVAL); Q_INSERT_HEAD(entry->vccs,vcc); Q_INSERT_HEAD(itf->table,entry); return 0; } static int ioctl_set_svc(ITF *itf,uint32_t ip,struct sockaddr_atmsvc *addr, const struct atm_qos *qos,int sndbuf,int flags) { ENTRY *entry; if (flags & ATF_ARPSRV) flags |= ATF_PERM; if (lookup_ip(itf,ip)) return -EEXIST; entry = alloc_entry(1); entry->state = as_valid; entry->ip = ip; entry->addr = alloc_t(struct sockaddr_atmsvc); *entry->addr = *addr; entry->qos = *qos; entry->sndbuf = sndbuf; entry->flags = flags; if (!(flags & ATF_PERM) || (flags & ATF_ARPSRV)) { if (itf->arp_srv) START_TIMER(entry,CREVAL); else START_TIMER(entry,SREVAL); } entry->itf = itf; Q_INSERT_HEAD(itf->table,entry); if (!(flags & ATF_ARPSRV)) return 0; entry->state = as_invalid; itf->arp_srv = entry; (void) want_arp_srv(itf); return 0; } static int ioctl_delete(ITF *itf,uint32_t ip,int flags) { ENTRY *entry,*walk,*next; if (!(entry = lookup_ip(itf,ip))) { diag(COMPONENT,DIAG_WARN,"ioctl_delete didn't find entry"); return -ENOENT; } if ((flags ^ entry->flags) & ATF_ARPSRV) return -EINVAL; send_notifications(entry,0); if ((entry->flags & ATF_ARPSRV) && entry->itf) for (walk = entry->itf->table; walk; walk = next) { next = walk->next; if (walk != entry && walk->state == as_resolv) { send_notifications(walk,0); if (!walk->vccs && !(walk->flags & ATF_PERM)) /* PERM is rather unlikely here, since this would be a second ARP server (only ARP servers can go as_resolv if PERM), but we'll check for it anyway. */ discard_entry(walk); else { STOP_TIMER(walk); walk->state = as_invalid; } } } discard_entry(entry); return 0; } static int qos_present(const struct atm_qos *qos) { return qos->txtp.traffic_class || qos->rxtp.traffic_class; } int arp_ioctl(struct atmarp_req *req) { ITF *itf; char buffer[MAX_ATM_ADDR_LEN+1]; unsigned char *ipp; diag(COMPONENT,DIAG_DEBUG,"arp_ioctl %d",req->type); itf = lookup_itf_by_ip(req->ip); if (!itf) return -EHOSTUNREACH; if (!(req->ip & ~itf->netmask) && !(req->flags & ATF_ARPSRV) && req->type != art_qos) return -EADDRNOTAVAIL; ipp = (unsigned char *) &req->ip; switch (req->type) { case art_qos: diag(COMPONENT,DIAG_DEBUG,"got art_qos for itf %d",itf->number); if (qos_present(&req->qos)) itf->qos = req->qos; if (req->sndbuf) itf->sndbuf = req->sndbuf; return 0; case art_set: if (atm2text(buffer,MAX_ATM_ADDR_LEN+1, (struct sockaddr *) &req->addr,pretty) < 0) { diag(COMPONENT,DIAG_ERROR,"a2t fails on art_set"); return -EINVAL; } diag(COMPONENT,DIAG_DEBUG,"got art_set for itf %d, IP %d.%d.%d.%d" ", ATM %s, flags 0x%x",itf->number,ipp[0],ipp[1],ipp[2],ipp[3], buffer,req->flags); if (!qos_present(&req->qos)) req->qos = itf->qos; if (!req->sndbuf) req->sndbuf = itf->sndbuf; switch (req->addr.sas_family) { case AF_ATMPVC: adjust_qos(itf,&req->qos,req->flags & ATF_NULL); return ioctl_set_pvc(itf,req->ip, (struct sockaddr_atmpvc *) &req->addr,&req->qos, req->sndbuf,req->flags); case AF_ATMSVC: adjust_qos(itf,&req->qos,0); return ioctl_set_svc(itf,req->ip, (struct sockaddr_atmsvc *) &req->addr,&req->qos, req->sndbuf,req->flags); default: /* not reached - atm2text complains before */ diag(COMPONENT,DIAG_ERROR,"art_set: bad HA AF 0x%x", req->addr.sas_family); return -EINVAL; } case art_delete: diag(COMPONENT,DIAG_DEBUG,"got art_delete for itf %d, IP " "%d.%d.%d.%d",itf->number,ipp[0],ipp[1],ipp[2],ipp[3]); return ioctl_delete(itf,req->ip,req->flags); default: diag(COMPONENT,DIAG_ERROR,"unrecognized type %d",req->type); return -EINVAL; } } void vcc_failed(VCC *vcc) { ENTRY *entry,*next; diag(COMPONENT,DIAG_DEBUG,"failed VCC 0x%p",vcc); Q_REMOVE(vcc->entry->vccs,vcc); if (!(vcc->entry->flags & ATF_ARPSRV) || vcc->entry->vccs) { /* VCC is already closed */ vcc_detach(vcc->entry); free(vcc); return; } for (entry = vcc->entry->itf->table; entry; entry = next) { next = entry->next; if (entry == vcc->entry || entry->state != as_resolv) continue; if (entry->vccs || (entry->flags & ATF_PERM)) entry->state = as_invalid; else discard_entry(entry); } START_TIMER(vcc->entry,RETRY); free(vcc); } void vcc_connected(VCC *vcc) { ENTRY *entry; diag(COMPONENT,DIAG_DEBUG,"connected VCC 0x%p",vcc); if (!vcc->connecting) diag(COMPONENT,DIAG_FATAL,"non-connecting VCC connected"); vcc->connecting = 0; if (vcc->entry->flags & ATF_ARPSRV) for (entry = vcc->entry->itf->table; entry; entry = entry->next) if (entry->state == as_resolv) { START_TIMER(entry,REPLY); arp_request(entry->itf,entry->ip); } inarp_request(vcc->entry); if (vcc->entry->state == as_valid) if (set_ip(vcc->fd,vcc->entry->ip) < 0) { diag(COMPONENT,DIAG_ERROR,"can't set IP (%s)",strerror(errno)); disconnect_vcc(vcc); } } void disconnect_vcc(VCC *vcc) { ENTRY *entry; diag(COMPONENT,DIAG_DEBUG,"disconnected VCC 0x%p",vcc); entry = vcc->entry; discard_vcc(vcc); if (entry) vcc_detach(entry); } void incoming_call(VCC *vcc) { diag(COMPONENT,DIAG_DEBUG,"incoming VCC 0x%p",vcc); START_TIMER(vcc->entry,REPLY); inarp_request(vcc->entry); } void incoming_unidirectional(VCC *vcc) { diag(COMPONENT,DIAG_DEBUG,"incoming unidirectional VCC 0x%p",vcc); /* don't put it into ATMARP table */ }