/* Lower half of IP, consisting of gateway routines * Includes routing and options processing code */ #include #include "machdep.h" #include "mbuf.h" #include "internet.h" #include "timer.h" #include "netuser.h" #include "ip.h" #include "icmp.h" #include "iface.h" #ifdef TRACE #include "trace.h" #endif struct route *routes[32][NROUTE]; /* Routing table */ struct route r_default; /* Default route entry */ int32 ip_addr; struct ip_stats ip_stats; /* Route an IP datagram. This is the "hopper" through which all IP datagrams, * coming or going, must pass. * * This router is a temporary hack, since it only does host-specific or * default routing (no hierarchical routing yet). * * "rxbroadcast" is set to indicate that the packet came in on a subnet * broadcast. The router will kick the packet upstairs regardless of the * IP destination address. */ void ip_route(bp,rxbroadcast) struct mbuf *bp; char rxbroadcast; /* True if packet had link broadcast address */ { register struct ip_header *ip; /* IP header being processed */ int16 ip_len; /* IP header length */ int16 buflen; /* Length of mbuf */ int16 length; /* Total datagram length */ int32 target; /* Target IP address */ int32 gateway; /* Gateway IP address */ register struct route *rp; /* Route table entry */ struct route *rt_lookup(); int opi; /* Index into options field */ int opt_len; /* Length of current option */ int strict; /* Strict source routing flag */ struct mbuf *sbp; /* IP header for fragmenting */ int16 fl_offs; /* fl_offs field of datagram */ int16 offset; /* Offset of fragment */ char precedence; /* Extracted from tos field */ char delay; char throughput; char reliability; ip_stats.total++; buflen = len_mbuf(bp); if(buflen < sizeof(struct ip_header)){ /* The packet is shorter than a legal IP header */ ip_stats.runt++; free_p(bp); return; } ip = (struct ip_header *)bp->data; length = ntohs(ip->length); if(buflen > length){ /* Packet has excess garbage (e.g., Ethernet padding); trim */ if(bp->next == NULLBUF){ /* One mbuf, just adjust count */ bp->cnt = length; } else { struct mbuf *nbp; /* Copy to a new one */ nbp = copy_p(bp,length); free((char *)bp); bp = nbp; ip = (struct ip_header *)bp->data; } } ip_len = lonibble(ip->v_ihl) * sizeof(int32); if(ip_len < sizeof(struct ip_header)){ /* The IP header length field is too small */ ip_stats.length++; free_p(bp); return; } if(cksum(NULLHEADER,bp,ip_len) != 0){ /* Bad IP header checksum; discard */ ip_stats.checksum++; free_p(bp); return; } if(hinibble(ip->v_ihl) != IPVERSION){ /* We can't handle this version of IP */ ip_stats.version++; free_p(bp); return; } /* See if it's a broadcast or addressed to us, and kick it upstairs */ if(ntohl(ip->dest) == ip_addr || rxbroadcast){ #ifdef GWONLY /* We're only a gateway, we have no host level protocols */ if(!rxbroadcast) icmp_output(bp,DEST_UNREACH,PROT_UNREACH,(union icmp_args *)NULL); free_p(bp); #else #ifdef TRACE if(trace & TRACE_SELF && ntohl(ip->source) == ip_addr){ printf("loopback:\r\n"); if((trace & TRACE_HDR) > 2) ip_dump(bp); if(trace & TRACE_DUMP) hexdump(bp); fflush(stdout); } #endif ip_recv(bp,rxbroadcast); #endif return; } /* If we get here, we must forward the packet. * Process options, if any. Also compute length of secondary IP * header in case fragmentation is needed later */ strict = 0; for(opi = sizeof(struct ip_header);opi < ip_len; opi += opt_len){ char *opt; /* Points to current option */ int opt_type; /* Type of current option */ int pointer; /* Pointer field of current option */ int32 *addr; /* Pointer to an IP address field in option */ opt = (char *)ip + opi; opt_type = opt[0] & OPT_NUMBER; /* Handle special 1-byte do-nothing options */ if(opt_type == IP_EOL) break; /* End of options list, we're done */ if(opt_type == IP_NOOP){ opt_len = 1; /* No operation, skip to next option */ continue; } /* Other options have a length field */ opt_len = opt[1] & 0xff; /* Process options */ switch(opt_type){ case IP_SSROUTE:/* Strict source route & record route */ strict = 1; case IP_LSROUTE:/* Loose source route & record route */ /* Source routes are ignored unless the datagram appears to * be for us */ if(ntohl(ip->dest) != ip_addr) continue; case IP_RROUTE: /* Record route */ pointer = (opt[2] & 0xff) - 1; if(pointer + sizeof(int32) <= opt_len){ /* Insert our address in the list */ addr = (int32 *)&opt[pointer]; if(opt_type != IP_RROUTE) /* Old value is next dest only for source routing */ ip->dest = *addr; *addr = htonl(ip_addr); opt[2] += 4; } else { /* Out of space; return a parameter problem and drop */ union icmp_args icmp_args; icmp_args.unused = 0; icmp_args.pointer = sizeof(struct ip_header) + opi; icmp_output(bp,PARAM_PROB,0,&icmp_args); free_p(bp); return; } break; } } /* Decrement TTL and discard if zero */ if(--ip->ttl == 0){ /* Send ICMP "Time Exceeded" message */ icmp_output(bp,TIME_EXCEED,0,NULLICMP); free_p(bp); return; } /* Note this address may have been modified by source routing */ target = ntohl(ip->dest); /* Look up target address in routing table */ if((rp = rt_lookup(target)) == NULLROUTE){ /* No route exists, return unreachable message */ icmp_output(bp,DEST_UNREACH,HOST_UNREACH,NULLICMP); free_p(bp); return; } /* Find gateway; zero gateway in routing table means "send direct" */ if(rp->gateway == (int32)0) gateway = target; else gateway = rp->gateway; if(strict && gateway != target){ /* Strict source routing requires a direct entry */ icmp_output(bp,DEST_UNREACH,ROUTE_FAIL,NULLICMP); free_p(bp); return; } precedence = PREC(ip->tos); delay = ip->tos & DELAY; throughput = ip->tos & THRUPUT; reliability = ip->tos & RELIABILITY; if(length <= rp->interface->mtu){ /* Datagram smaller than interface MTU; send normally */ /* Recompute header checksum */ ip->checksum = 0; ip->checksum = cksum(NULLHEADER,bp,ip_len); (*rp->interface->send)(bp,rp->interface,gateway, precedence,delay,throughput,reliability); return; } /* Fragmentation needed */ fl_offs = ntohs(ip->fl_offs); if(fl_offs & DF){ /* Don't Fragment set; return ICMP message and drop */ icmp_output(bp,DEST_UNREACH,FRAG_NEEDED,NULLICMP); free_p(bp); return; } /* Create copy of IP header for each fragment */ sbp = copy_p(bp,ip_len); pullup(&bp,NULLCHAR,ip_len); length -= ip_len; /* Create fragments */ offset = (fl_offs & F_OFFSET) << 3; while(length != 0){ int16 fragsize; /* Size of this fragment's data */ struct mbuf *f_header; /* Header portion of fragment */ struct ip_header *fip; /* IP header */ struct mbuf *f_data; /* Data portion of fragment */ f_header = copy_p(sbp,ip_len); fip = (struct ip_header *)f_header->data; fip->fl_offs = htons(offset >> 3); if(length + ip_len <= rp->interface->mtu){ /* Last fragment; send all that remains */ fragsize = length; } else { /* More to come, so send multiple of 8 bytes */ fragsize = (rp->interface->mtu - ip_len) & 0xfff8; fip->fl_offs |= htons(MF); } fip->length = htons(fragsize + ip_len); /* Recompute header checksum */ fip->checksum = 0; fip->checksum = cksum(NULLHEADER,f_header,ip_len); /* Extract portion of data and link in */ f_data = copy_p(bp,fragsize); pullup(&bp,NULLCHAR,fragsize); f_header->next = f_data; (*rp->interface->send)(f_header,rp->interface,gateway, precedence,delay,throughput,reliability); offset += fragsize; length -= fragsize; } free_p(sbp); } /* Add an entry to the IP routing table. Returns 0 on success, -1 on failure */ int rt_add(target,bits,gateway,metric,interface) int32 target; /* Target IP address prefix */ unsigned bits; /* Size of target address prefix in bits (0-32) */ int32 gateway; int metric; struct interface *interface; { struct route *rp,**hp,*rt_lookup(); int16 hash_ip(),i; char *malloc(); if(interface == NULLIF) return -1; /* Zero bits refers to the default route */ if(bits == 0){ rp = &r_default; } else { if(bits > 32) bits = 32; /* Mask off don't-care bits */ for(i=31;i >= bits;i--) target &= ~(0x80000000 >> i); /* Search appropriate chain for existing entry */ for(rp = routes[bits-1][hash_ip(target)];rp != NULLROUTE;rp = rp->next){ if(rp->target == target) break; } } if(rp == NULLROUTE){ /* The target is not already in the table, so create a new * entry and put it in. */ if((rp = (struct route *)malloc(sizeof(struct route))) == NULLROUTE) return -1; /* No space */ /* Insert at head of table */ rp->prev = NULLROUTE; hp = &routes[bits-1][hash_ip(target)]; rp->next = *hp; if(rp->next != NULLROUTE) rp->next->prev = rp; *hp = rp; } rp->target = target; rp->gateway = gateway; rp->metric = metric; rp->interface = interface; return 0; } /* Remove an entry from the IP routing table. Returns 0 on success, -1 * if entry was not in table. */ int rt_drop(target,bits) int32 target; unsigned bits; { register struct route *rp; struct route *rt_lookup(); unsigned i; if(bits == 0){ /* Nail the default entry */ r_default.interface = NULLIF; return 0; } if(bits > 32) bits = 32; /* Mask off don't-care bits */ for(i=31;i > bits;i--) target &= ~(0x80000000 >> i); /* Search appropriate chain for existing entry */ for(rp = routes[bits-1][hash_ip(target)];rp != NULLROUTE;rp = rp->next){ if(rp->target == target) break; } if(rp == NULLROUTE) return -1; /* Not in table */ if(rp->next != NULLROUTE) rp->next->prev = rp->prev; if(rp->prev != NULLROUTE) rp->prev->next = rp->next; else routes[bits-1][hash_ip(target)] = rp->next; free((char *)rp); return 0; } /* Compute hash function on IP address */ static int16 hash_ip(addr) register int32 addr; { register int16 ret; ret = hiword(addr); ret ^= loword(addr); ret %= NROUTE; return ret; } #ifndef GWONLY /* Given an IP address, return the MTU of the local interface used to * reach that destination. This is used by TCP to avoid local fragmentation */ int16 ip_mtu(addr) int32 addr; { register struct route *rp; struct route *rt_lookup(); rp = rt_lookup(addr); if(rp != NULLROUTE && rp->interface != NULLIF) return rp->interface->mtu; else return 0; } #endif /* Look up target in hash table, matching the entry having the largest number * of leading bits in common. Return default route if not found; * if default route not set, return NULLROUTE */ static struct route * rt_lookup(target) int32 target; { register struct route *rp; int16 hash_ip(); unsigned bits; for(bits = 32;bits != 0; bits--){ if(bits != 32) target &= ~(0x80000000 >> bits); for(rp = routes[bits-1][hash_ip(target)];rp != NULLROUTE;rp = rp->next){ if(rp->target == target) return rp; } } if(r_default.interface != NULLIF) return &r_default; else return NULLROUTE; } /* Internet checksum routines * Improved portability courtesy Rick Spanbauer, WB2CFV */ #define SLOWCHECK #ifdef SLOWCHECK /* * Word aligned linear buffer checksum routine. Called from mbuf checksum * routine with simple args. Intent is that this routine may be replaced * by assembly language routine for speed if so desired. */ static int16 lcsum(sum, wp, len) register int32 sum; register int16 *wp; int16 len; { register int16 csum; while(len-- != 0) sum += *wp++; while((csum = sum >> 16) != 0) sum = csum + (sum & 0xffff); return sum & 0xffff; } #endif SLOWCHECK /* Perform end-around-carry adjustment */ static int16 eac(sum) register int32 sum; /* Carries in high order 16 bits */ { register int16 csum; while((csum = sum >> 16) != 0) sum = csum + (sum & 0xffff); return sum; /* Chops to 16 bits */ } /* Checksum a mbuf chain, with optional pseudo-header */ int16 cksum(ph,m,len) struct pseudo_header *ph; register struct mbuf *m; int16 len; { register unsigned int cnt, total; register int32 sum, csum; register unsigned char *up; sum = 0l; /* Sum pseudo-header, if present */ if(ph != NULLHEADER){ sum = hiword(ph->source); sum += loword(ph->source); sum += hiword(ph->dest); sum += loword(ph->dest); sum += ph->protocol & 0xff; sum += ph->length; /* Swapping the sum is equivalent to summing the swapped * elements, but faster. Do end-around-carry first. */ sum = htons(eac(sum)); } /* Now do each mbuf on the chain */ for(total = 0; m != NULLBUF && total < len; m = m->next) { cnt = min(m->cnt, len - total); up = (unsigned char *)m->data; /* Handle odd leading byte */ if(((long)up) & 1){ csum = (int16)ntohs(*up++); cnt--; } else csum = 0; /* Handle odd trailing byte */ if(cnt & 1) csum += (int16)ntohs(up[--cnt]<<8); if(cnt != 0){ /* Have the primitive checksumming routine do most of * the work. At this point, up is guaranteed to be on * a short boundary and cnt is guaranteed to be even */ csum = lcsum(csum, (unsigned short *)up, cnt >> 1); } /* If the mbuf we just did wasn't on a word boundary within * the whole packet, then byteswap the checksum for this mbuf */ if((total&1) ^ (((long)m->data)&1)){ csum = eac(csum); csum = (csum >> 8) + ((csum&0xff) << 8); } sum += csum; total += m->cnt; } /* Do final end-around carry, complement and return */ return ~eac(sum) & 0xffff; } #ifdef TRACE #include "trace.h" void ip_dump(bp) struct mbuf *bp; { void tcp_dump(),udp_dump(),icmp_dump(); register struct ip_header *ip; int32 source,dest; int16 ip_len; int16 length; struct mbuf *tbp; int16 offset; int i; int check; char tmpbuf; if(bp == NULLBUF) return; /* If packet isn't in a single buffer, make a temporary copy and * note the fact so we free it later */ if(bp->next != NULLBUF){ bp = copy_p(bp,len_mbuf(bp)); tmpbuf = 1; } else tmpbuf = 0; ip = (struct ip_header *)bp->data; ip_len = lonibble(ip->v_ihl) * sizeof(int32); length = ntohs(ip->length); offset = (ntohs(ip->fl_offs) & F_OFFSET) << 3 ; source = ntohl(ip->source); dest = ntohl(ip->dest); printf("IP: %s",inet_ntoa(source)); printf("->%s len %u ihl %u ttl %u prot %u", inet_ntoa(dest),length,ip_len,ip->ttl & 0xff, ip->protocol & 0xff); if(ip->tos != 0) printf(" tos %u",ip->tos); if(offset != 0 || (ntohs(ip->fl_offs) & MF)) printf(" id %u offs %u",ntohs(ip->id),offset); if(ntohs(ip->fl_offs) & DF) printf(" DF"); if(ntohs(ip->fl_offs) & MF){ printf(" MF"); check = 0; /* Bypass host-level checksum verify */ } else { check = 1; } if((i = cksum(NULLHEADER,bp,ip_len)) != 0) printf(" CHECKSUM ERROR (%u)",i); printf("\r\n"); if((trace & TRACE_HDR) > 3){ if(offset == 0){ dup_p(&tbp,bp,ip_len,length - ip_len); switch(ip->protocol & 0xff){ case TCP_PTCL: tcp_dump(tbp,source,dest,check); break; case UDP_PTCL: udp_dump(tbp,source,dest,check); break; case ICMP_PTCL: icmp_dump(tbp,source,dest,check); break; } free_p(tbp); } } if(tmpbuf) free_p(bp); fflush(stdout); } /* Dump IP routing table * Dest Length Interface Gateway Metric * 192.001.002.003 32 sl0 192.002.003.004 4 */ int dumproute() { register unsigned int i,bits; register struct route *rp; printf("Dest Length Interface Gateway Metric\r\n"); if(r_default.interface != NULLIF){ printf("default 0 %-13s", r_default.interface->name); if(r_default.gateway != 0) printf("%-17s",inet_ntoa(r_default.gateway)); else printf("%-17s",""); printf("%6u\r\n",r_default.metric); } for(bits=1;bits<=32;bits++){ for(i=0;inext){ printf("%-18s",inet_ntoa(rp->target)); printf("%-10u",bits); printf("%-13s",rp->interface->name); if(rp->gateway != 0) printf("%-17s",inet_ntoa(rp->gateway)); else printf("%-17s",""); printf("%6u\r\n",rp->metric); } } } return 0; } #endif