diff options
author | Ralf Baechle <ralf@linux-mips.org> | 1997-04-29 21:13:14 +0000 |
---|---|---|
committer | <ralf@linux-mips.org> | 1997-04-29 21:13:14 +0000 |
commit | 19c9bba94152148523ba0f7ef7cffe3d45656b11 (patch) | |
tree | 40b1cb534496a7f1ca0f5c314a523c69f1fee464 /net/ipv4/ip_options.c | |
parent | 7206675c40394c78a90e74812bbdbf8cf3cca1be (diff) |
Import of Linux/MIPS 2.1.36
Diffstat (limited to 'net/ipv4/ip_options.c')
-rw-r--r-- | net/ipv4/ip_options.c | 414 |
1 files changed, 277 insertions, 137 deletions
diff --git a/net/ipv4/ip_options.c b/net/ipv4/ip_options.c index 517c0e219..2c7974506 100644 --- a/net/ipv4/ip_options.c +++ b/net/ipv4/ip_options.c @@ -10,6 +10,7 @@ */ #include <linux/types.h> +#include <asm/uaccess.h> #include <linux/skbuff.h> #include <linux/ip.h> #include <linux/icmp.h> @@ -17,6 +18,7 @@ #include <net/sock.h> #include <net/ip.h> #include <net/icmp.h> +#include <linux/net_alias.h> /* * Write options to IP header, record destination address to @@ -24,30 +26,30 @@ * (we should already know it, so that this function is allowed be * called only after routing decision) and timestamp, * if we originate this datagram. + * + * daddr is real destination address, next hop is recorded in IP header. + * saddr is address of outgoing interface. */ -void ip_options_build(struct sk_buff * skb, struct options * opt, - __u32 daddr, __u32 saddr, - int is_frag) +void ip_options_build(struct sk_buff * skb, struct ip_options * opt, + u32 daddr, u32 saddr, int is_frag) { - unsigned char * iph = (unsigned char*)skb->ip_hdr; + unsigned char * iph = skb->nh.raw; - memcpy(skb->proto_priv, opt, sizeof(struct options)); + memcpy(&(IPCB(skb)->opt), opt, sizeof(struct ip_options)); memcpy(iph+sizeof(struct iphdr), opt->__data, opt->optlen); - opt = (struct options*)skb->proto_priv; + opt = &(IPCB(skb)->opt); opt->is_data = 0; if (opt->srr) memcpy(iph+opt->srr+iph[opt->srr+1]-4, &daddr, 4); - if (!is_frag) - { + if (!is_frag) { if (opt->rr_needaddr) memcpy(iph+opt->rr+iph[opt->rr+2]-5, &saddr, 4); if (opt->ts_needaddr) memcpy(iph+opt->ts+iph[opt->ts+2]-9, &saddr, 4); - if (opt->ts_needtime) - { + if (opt->ts_needtime) { struct timeval tv; __u32 midtime; do_gettimeofday(&tv); @@ -56,88 +58,96 @@ void ip_options_build(struct sk_buff * skb, struct options * opt, } return; } - if (opt->rr) - { + if (opt->rr) { memset(iph+opt->rr, IPOPT_NOP, iph[opt->rr+1]); opt->rr = 0; opt->rr_needaddr = 0; } - if (opt->ts) - { + if (opt->ts) { memset(iph+opt->ts, IPOPT_NOP, iph[opt->ts+1]); opt->ts = 0; opt->ts_needaddr = opt->ts_needtime = 0; } } -int ip_options_echo(struct options * dopt, struct options * sopt, - __u32 daddr, __u32 saddr, - struct sk_buff * skb) +/* + * Provided (sopt, skb) points to received options, + * build in dopt compiled option set appropriate for answering. + * i.e. invert SRR option, copy anothers, + * and grab room in RR/TS options. + * + * NOTE: dopt cannot point to skb. + */ + +int ip_options_echo(struct ip_options * dopt, struct sk_buff * skb) { + struct ip_options *sopt; unsigned char *sptr, *dptr; int soffset, doffset; int optlen; + u32 daddr; - memset(dopt, 0, sizeof(struct options)); +#if 111 + if (skb == NULL) { + printk(KERN_DEBUG "no skb in ip_options_echo\n"); + return -EINVAL; + } +#endif + memset(dopt, 0, sizeof(struct ip_options)); dopt->is_data = 1; - if (!sopt) - sopt = (struct options*)skb->proto_priv; + sopt = &(IPCB(skb)->opt); - if (sopt->optlen == 0) - { + if (sopt->optlen == 0) { dopt->optlen = 0; return 0; } - sptr = (sopt->is_data ? sopt->__data - sizeof(struct iphdr) : - (unsigned char *)skb->ip_hdr); + sptr = skb->nh.raw; dptr = dopt->__data; - if (sopt->rr) - { + if (skb->dst) + daddr = ((struct rtable*)skb->dst)->rt_spec_dst; + else + daddr = skb->nh.iph->daddr; + + if (sopt->rr) { optlen = sptr[sopt->rr+1]; soffset = sptr[sopt->rr+2]; dopt->rr = dopt->optlen + sizeof(struct iphdr); memcpy(dptr, sptr+sopt->rr, optlen); if (sopt->rr_needaddr && soffset <= optlen) { if (soffset + 3 > optlen) - return -EINVAL; + return -EINVAL; dptr[2] = soffset + 4; dopt->rr_needaddr = 1; } - dptr += optlen; + dptr += optlen; dopt->optlen += optlen; } - if (sopt->ts) - { + if (sopt->ts) { optlen = sptr[sopt->ts+1]; soffset = sptr[sopt->ts+2]; dopt->ts = dopt->optlen + sizeof(struct iphdr); memcpy(dptr, sptr+sopt->ts, optlen); - if (soffset <= optlen) - { - if (sopt->ts_needaddr) - { + if (soffset <= optlen) { + if (sopt->ts_needaddr) { if (soffset + 3 > optlen) return -EINVAL; dopt->ts_needaddr = 1; soffset += 4; } - if (sopt->ts_needtime) - { + if (sopt->ts_needtime) { if (soffset + 3 > optlen) return -EINVAL; dopt->ts_needtime = 1; soffset += 4; } - if (((struct timestamp*)(dptr+1))->flags == IPOPT_TS_PRESPEC) - { + if (((struct timestamp*)(dptr+1))->flags == IPOPT_TS_PRESPEC) { __u32 addr; memcpy(&addr, sptr+soffset-9, 4); - if (ip_chk_addr(addr) == 0) - { + if (__ip_chk_addr(addr) == 0) { dopt->ts_needtime = 0; dopt->ts_needaddr = 0; soffset -= 8; @@ -148,10 +158,9 @@ int ip_options_echo(struct options * dopt, struct options * sopt, dptr += optlen; dopt->optlen += optlen; } - if (sopt->srr) - { + if (sopt->srr) { unsigned char * start = sptr+sopt->srr; - __u32 faddr; + u32 faddr; optlen = start[1]; soffset = start[2]; @@ -159,19 +168,17 @@ int ip_options_echo(struct options * dopt, struct options * sopt, if (soffset > optlen) soffset = optlen + 1; soffset -= 4; - if (soffset > 3) - { + if (soffset > 3) { memcpy(&faddr, &start[soffset-1], 4); for (soffset-=4, doffset=4; soffset > 3; soffset-=4, doffset+=4) memcpy(&dptr[doffset-1], &start[soffset-1], 4); /* * RFC1812 requires to fix illegal source routes. */ - if (memcmp(&saddr, &start[soffset+3], 4) == 0) + if (memcmp(&skb->nh.iph->saddr, &start[soffset+3], 4) == 0) doffset -= 4; } - if (doffset > 3) - { + if (doffset > 3) { memcpy(&start[doffset-1], &daddr, 4); dopt->faddr = faddr; dptr[0] = start[0]; @@ -183,28 +190,31 @@ int ip_options_echo(struct options * dopt, struct options * sopt, dopt->is_strictroute = sopt->is_strictroute; } } - while (dopt->optlen & 3) - { + while (dopt->optlen & 3) { *dptr++ = IPOPT_END; dopt->optlen++; } return 0; } +/* + * Options "fragmenting", just fill options not + * allowed in fragments with NOOPs. + * Simple and stupid 8), but the most efficient way. + */ + void ip_options_fragment(struct sk_buff * skb) { - unsigned char * optptr = (unsigned char*)skb->ip_hdr; - struct options * opt = (struct options*)skb->proto_priv; + unsigned char * optptr = skb->nh.raw; + struct ip_options * opt = &(IPCB(skb)->opt); int l = opt->optlen; int optlen; - while (l > 0) - { - switch (*optptr) - { - case IPOPT_END: + while (l > 0) { + switch (*optptr) { + case IPOPT_END: return; - case IPOPT_NOOP: + case IPOPT_NOOP: l--; optptr++; continue; @@ -212,7 +222,7 @@ void ip_options_fragment(struct sk_buff * skb) optlen = optptr[1]; if (optlen<2 || optlen>l) return; - if (!(*optptr & 0x80)) + if (!IPOPT_COPIED(*optptr)) memset(optptr, IPOPT_NOOP, optlen); l -= optlen; optptr += optlen; @@ -231,7 +241,7 @@ void ip_options_fragment(struct sk_buff * skb) * If opt == NULL, then skb->data should point to IP header. */ -int ip_options_compile(struct options * opt, struct sk_buff * skb) +int ip_options_compile(struct ip_options * opt, struct sk_buff * skb) { int l; unsigned char * iph; @@ -239,30 +249,23 @@ int ip_options_compile(struct options * opt, struct sk_buff * skb) int optlen; unsigned char * pp_ptr = NULL; - if (!opt) - { - opt = (struct options*)skb->proto_priv; - memset(opt, 0, sizeof(struct options)); - iph = (unsigned char*)skb->ip_hdr; + if (!opt) { + opt = &(IPCB(skb)->opt); + memset(opt, 0, sizeof(struct ip_options)); + iph = skb->nh.raw; opt->optlen = ((struct iphdr *)iph)->ihl*4 - sizeof(struct iphdr); optptr = iph + sizeof(struct iphdr); opt->is_data = 0; - } - else - { - optptr = opt->is_data ? opt->__data : (unsigned char*)&skb->ip_hdr[1]; + } else { + optptr = opt->is_data ? opt->__data : (unsigned char*)&(skb->nh.iph[1]); iph = optptr - sizeof(struct iphdr); } - for (l = opt->optlen; l > 0; ) - { - switch (*optptr) - { + for (l = opt->optlen; l > 0; ) { + switch (*optptr) { case IPOPT_END: - for (optptr++, l--; l>0; l--) - { - if (*optptr != IPOPT_END) - { + for (optptr++, l--; l>0; l--) { + if (*optptr != IPOPT_END) { *optptr = IPOPT_END; opt->is_changed = 1; } @@ -274,35 +277,28 @@ int ip_options_compile(struct options * opt, struct sk_buff * skb) continue; } optlen = optptr[1]; - if (optlen<2 || optlen>l) - { + if (optlen<2 || optlen>l) { pp_ptr = optptr; goto error; } - switch (*optptr) - { + switch (*optptr) { case IPOPT_SSRR: case IPOPT_LSRR: - if (optlen < 3) - { + if (optlen < 3) { pp_ptr = optptr + 1; goto error; } - if (optptr[2] < 4) - { + if (optptr[2] < 4) { pp_ptr = optptr + 2; goto error; } /* NB: cf RFC-1812 5.2.4.1 */ - if (opt->srr) - { + if (opt->srr) { pp_ptr = optptr; goto error; } - if (!skb) - { - if (optptr[2] != 4 || optlen < 7 || ((optlen-3) & 3)) - { + if (!skb) { + if (optptr[2] != 4 || optlen < 7 || ((optlen-3) & 3)) { pp_ptr = optptr + 1; goto error; } @@ -314,30 +310,24 @@ int ip_options_compile(struct options * opt, struct sk_buff * skb) opt->srr = optptr - iph; break; case IPOPT_RR: - if (opt->rr) - { + if (opt->rr) { pp_ptr = optptr; goto error; } - if (optlen < 3) - { + if (optlen < 3) { pp_ptr = optptr + 1; goto error; } - if (optptr[2] < 4) - { + if (optptr[2] < 4) { pp_ptr = optptr + 2; goto error; } - if (optptr[2] <= optlen) - { - if (optptr[2]+3 > optlen) - { + if (optptr[2] <= optlen) { + if (optptr[2]+3 > optlen) { pp_ptr = optptr + 2; goto error; } - if (skb) - { + if (skb) { memcpy(&optptr[optptr[2]-1], &skb->dev->pa_addr, 4); opt->is_changed = 1; } @@ -347,32 +337,26 @@ int ip_options_compile(struct options * opt, struct sk_buff * skb) opt->rr = optptr - iph; break; case IPOPT_TIMESTAMP: - if (opt->ts) - { + if (opt->ts) { pp_ptr = optptr; goto error; } - if (optlen < 4) - { + if (optlen < 4) { pp_ptr = optptr + 1; goto error; } - if (optptr[2] < 5) - { + if (optptr[2] < 5) { pp_ptr = optptr + 2; goto error; } - if (optptr[2] <= optlen) - { + if (optptr[2] <= optlen) { struct timestamp * ts = (struct timestamp*)(optptr+1); __u32 * timeptr = NULL; - if (ts->ptr+3 > ts->len) - { + if (ts->ptr+3 > ts->len) { pp_ptr = optptr + 2; goto error; } - switch (ts->flags) - { + switch (ts->flags) { case IPOPT_TS_TSONLY: opt->ts = optptr - iph; if (skb) @@ -381,14 +365,12 @@ int ip_options_compile(struct options * opt, struct sk_buff * skb) ts->ptr += 4; break; case IPOPT_TS_TSANDADDR: - if (ts->ptr+7 > ts->len) - { + if (ts->ptr+7 > ts->len) { pp_ptr = optptr + 2; goto error; } opt->ts = optptr - iph; - if (skb) - { + if (skb) { memcpy(&optptr[ts->ptr-1], &skb->dev->pa_addr, 4); timeptr = (__u32*)&optptr[ts->ptr+3]; } @@ -397,16 +379,15 @@ int ip_options_compile(struct options * opt, struct sk_buff * skb) ts->ptr += 8; break; case IPOPT_TS_PRESPEC: - if (ts->ptr+7 > ts->len) - { + if (ts->ptr+7 > ts->len) { pp_ptr = optptr + 2; goto error; } opt->ts = optptr - iph; { - __u32 addr; + u32 addr; memcpy(&addr, &optptr[ts->ptr-1], 4); - if (ip_chk_addr(addr) == 0) + if (__ip_chk_addr(addr) == 0) break; if (skb) timeptr = (__u32*)&optptr[ts->ptr+3]; @@ -419,8 +400,7 @@ int ip_options_compile(struct options * opt, struct sk_buff * skb) pp_ptr = optptr + 3; goto error; } - if (timeptr) - { + if (timeptr) { struct timeval tv; __u32 midtime; do_gettimeofday(&tv); @@ -428,28 +408,31 @@ int ip_options_compile(struct options * opt, struct sk_buff * skb) memcpy(timeptr, &midtime, sizeof(__u32)); opt->is_changed = 1; } - } - else - { + } else { struct timestamp * ts = (struct timestamp*)(optptr+1); - if (ts->overflow == 15) - { + if (ts->overflow == 15) { pp_ptr = optptr + 3; goto error; } opt->ts = optptr - iph; - if (skb) - { + if (skb) { ts->overflow++; opt->is_changed = 1; } } break; + case IPOPT_RA: + if (optlen < 4) { + pp_ptr = optptr + 1; + goto error; + } + if (optptr[2] == 0 && optptr[3] == 0) + opt->router_alert = optptr - iph; + break; case IPOPT_SEC: case IPOPT_SID: default: - if (!skb) - { + if (!skb) { pp_ptr = optptr; goto error; } @@ -464,11 +447,168 @@ eol: return 0; error: - if (skb) - { - icmp_send(skb, ICMP_PARAMETERPROB, 0, pp_ptr-iph, skb->dev); + if (skb) { + icmp_send(skb, ICMP_PARAMETERPROB, 0, pp_ptr-iph); kfree_skb(skb, FREE_READ); } return -EINVAL; } + +/* + * Undo all the changes done by ip_options_compile(). + */ + +void ip_options_undo(struct ip_options * opt) +{ + if (opt->srr) { + unsigned char * optptr = opt->__data+opt->srr-sizeof(struct iphdr); + memmove(optptr+7, optptr+3, optptr[1]-7); + memcpy(optptr+3, &opt->faddr, 4); + } + if (opt->rr_needaddr) { + unsigned char * optptr = opt->__data+opt->rr-sizeof(struct iphdr); + memset(&optptr[optptr[2]-1], 0, 4); + optptr[2] -= 4; + } + if (opt->ts) { + unsigned char * optptr = opt->__data+opt->ts-sizeof(struct iphdr); + if (opt->ts_needtime) { + memset(&optptr[optptr[2]-1], 0, 4); + optptr[2] -= 4; + } + if (opt->ts_needaddr) { + memset(&optptr[optptr[2]-1], 0, 4); + optptr[2] -= 4; + } + } +} + +int ip_options_get(struct ip_options **optp, unsigned char *data, int optlen, int user) +{ + struct ip_options *opt; + + opt = kmalloc(sizeof(struct ip_options)+((optlen+3)&~3), GFP_KERNEL); + if (!opt) + return -ENOMEM; + memset(opt, 0, sizeof(struct ip_options)); + if (optlen) { + if (user) { + if (copy_from_user(opt->__data, data, optlen)) + return -EFAULT; + } else + memcpy(opt->__data, data, optlen); + } + while (optlen & 3) + opt->__data[optlen++] = IPOPT_END; + opt->optlen = optlen; + opt->is_data = 1; + opt->is_setbyuser = 1; + if (optlen && ip_options_compile(opt, NULL)) { + kfree_s(opt, sizeof(struct options) + optlen); + return -EINVAL; + } + *optp = opt; + return 0; +} + +void ip_forward_options(struct sk_buff *skb) +{ + struct ip_options * opt = &(IPCB(skb)->opt); + unsigned char * optptr; + struct rtable *rt = (struct rtable*)skb->dst; + unsigned char *raw = skb->nh.raw; + + if (opt->rr_needaddr) { + optptr = (unsigned char *)raw + opt->rr; + memcpy(&optptr[optptr[2]-5], &rt->u.dst.dev->pa_addr, 4); + opt->is_changed = 1; + } + if (opt->srr_is_hit) { + int srrptr, srrspace; + + optptr = raw + opt->srr; + + for ( srrptr=optptr[2], srrspace = optptr[1]; + srrptr <= srrspace; + srrptr += 4 + ) { + if (srrptr + 3 > srrspace) + break; + if (memcmp(&rt->rt_dst, &optptr[srrptr-1], 4) == 0) + break; + } + if (srrptr + 3 <= srrspace) { + opt->is_changed = 1; + memcpy(&optptr[srrptr-1], &rt->u.dst.dev->pa_addr, 4); + skb->nh.iph->daddr = rt->rt_dst; + optptr[2] = srrptr+4; + } else + printk(KERN_CRIT "ip_forward(): Argh! Destination lost!\n"); + if (opt->ts_needaddr) { + optptr = raw + opt->ts; + memcpy(&optptr[optptr[2]-9], &rt->u.dst.dev->pa_addr, 4); + opt->is_changed = 1; + } + if (opt->is_changed) { + opt->is_changed = 0; + ip_send_check(skb->nh.iph); + } + } +} + +int ip_options_rcv_srr(struct sk_buff *skb) +{ + struct ip_options *opt = &(IPCB(skb)->opt); + int srrspace, srrptr; + u32 nexthop; + struct iphdr *iph = skb->nh.iph; + unsigned char * optptr = skb->nh.raw + opt->srr; + struct rtable *rt = (struct rtable*)skb->dst; + struct rtable *rt2; + int err; + + if (!opt->srr) + return 0; + + if (rt->rt_flags&(RTF_BROADCAST|RTF_MULTICAST|RTF_NAT) + || skb->pkt_type != PACKET_HOST) + return -EINVAL; + + if (!(rt->rt_flags & RTF_LOCAL)) { + if (!opt->is_strictroute) + return 0; + icmp_send(skb, ICMP_PARAMETERPROB, 0, 16); + return -EINVAL; + } + + for (srrptr=optptr[2], srrspace = optptr[1]; srrptr <= srrspace; srrptr += 4) { + if (srrptr + 3 > srrspace) { + icmp_send(skb, ICMP_PARAMETERPROB, 0, opt->srr+2); + return -EINVAL; + } + memcpy(&nexthop, &optptr[srrptr-1], 4); + + rt = (struct rtable*)skb->dst; + skb->dst = NULL; + err = ip_route_input(skb, nexthop, iph->saddr, iph->tos, + net_alias_main_dev(skb->dev)); + rt2 = (struct rtable*)skb->dst; + if (err || rt2->rt_flags&(RTF_BROADCAST|RTF_MULTICAST|RTF_NAT)) { + ip_rt_put(rt2); + skb->dst = &rt->u.dst; + return -EINVAL; + } + ip_rt_put(rt); + if (!(rt2->rt_flags&RTF_LOCAL)) + break; + /* Superfast 8) loopback forward */ + memcpy(&iph->daddr, &optptr[srrptr-1], 4); + opt->is_changed = 1; + } + if (srrptr <= srrspace) { + opt->srr_is_hit = 1; + opt->is_changed = 1; + } + return 0; +} |