diff options
author | Ralf Baechle <ralf@linux-mips.org> | 1997-01-07 02:33:00 +0000 |
---|---|---|
committer | <ralf@linux-mips.org> | 1997-01-07 02:33:00 +0000 |
commit | beb116954b9b7f3bb56412b2494b562f02b864b1 (patch) | |
tree | 120e997879884e1b9d93b265221b939d2ef1ade1 /net/ipv6/icmp.c | |
parent | 908d4681a1dc3792ecafbe64265783a86c4cccb6 (diff) |
Import of Linux/MIPS 2.1.14
Diffstat (limited to 'net/ipv6/icmp.c')
-rw-r--r-- | net/ipv6/icmp.c | 560 |
1 files changed, 560 insertions, 0 deletions
diff --git a/net/ipv6/icmp.c b/net/ipv6/icmp.c new file mode 100644 index 000000000..f959189c6 --- /dev/null +++ b/net/ipv6/icmp.c @@ -0,0 +1,560 @@ +/* + * Internet Control Message Protocol (ICMPv6) + * Linux INET6 implementation + * + * Authors: + * Pedro Roque <roque@di.fc.ul.pt> + * + * Based on net/ipv4/icmp.c + * + * RFC 1885 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +/* + * Changes: + * + * Andi Kleen : exception handling + */ + +#define __NO_VERSION__ +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/socket.h> +#include <linux/in.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/sched.h> +#include <linux/timer.h> +#include <linux/string.h> +#include <linux/sockios.h> +#include <linux/net.h> +#include <linux/fcntl.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/proc_fs.h> +#include <linux/stat.h> +#include <linux/skbuff.h> + +#include <linux/inet.h> +#include <linux/netdevice.h> +#include <linux/icmpv6.h> + +#include <net/ip.h> +#include <net/sock.h> + +#include <net/ipv6.h> +#include <net/protocol.h> +#include <net/route.h> +#include <net/ndisc.h> +#include <net/raw.h> +#include <net/inet_common.h> +#include <net/transp_v6.h> +#include <net/ipv6_route.h> +#include <net/addrconf.h> +#include <net/rawv6.h> + +#include <asm/uaccess.h> +#include <asm/system.h> + +/* + * ICMP socket for flow control. + */ + +static struct socket icmpv6_socket; + +int icmpv6_rcv(struct sk_buff *skb, struct device *dev, + struct in6_addr *saddr, struct in6_addr *daddr, + struct ipv6_options *opt, unsigned short len, + int redo, struct inet6_protocol *protocol); + +static struct inet6_protocol icmpv6_protocol = +{ + icmpv6_rcv, /* handler */ + NULL, /* error control */ + NULL, /* next */ + IPPROTO_ICMPV6, /* protocol ID */ + 0, /* copy */ + NULL, /* data */ + "ICMPv6" /* name */ +}; + + + +struct icmpv6_msg { + struct icmpv6hdr icmph; + __u8 *data; + struct in6_addr *daddr; + int len; + __u32 csum; +}; + + + +/* + * getfrag callback + * not static because it's needed in ndisc.c + */ + +static int icmpv6_getfrag(const void *data, struct in6_addr *saddr, + char *buff, unsigned int offset, unsigned int len) +{ + struct icmpv6_msg *msg = (struct icmpv6_msg *) data; + struct icmpv6hdr *icmph; + __u32 csum; + + /* + * in theory offset must be 0 since we never send more + * than 576 bytes on an error or more than the path mtu + * on an echo reply. (those are the rules on RFC 1883) + */ + + if (offset) + { + csum = csum_partial_copy((void *) msg->data + + offset - sizeof(struct icmpv6hdr), + buff, len, msg->csum); + msg->csum = csum; + return 0; + } + + csum = csum_partial_copy((void *) &msg->icmph, buff, + sizeof(struct icmpv6hdr), msg->csum); + + csum = csum_partial_copy((void *) msg->data, + buff + sizeof(struct icmpv6hdr), + len - sizeof(struct icmpv6hdr), csum); + + icmph = (struct icmpv6hdr *) buff; + + icmph->checksum = csum_ipv6_magic(saddr, msg->daddr, msg->len, + IPPROTO_ICMPV6, csum); + return 0; +} + +/* + * an inline helper for the "simple" if statement bellow + * checks if parameter problem report is caused by an + * unrecognized IPv6 option that has the Option Type + * highest-order two bits set to 10 + */ +static __inline__ int opt_unrec(struct sk_buff *skb, __u32 offset) +{ + char *buff = (char *) skb->ipv6_hdr; + + return ( ( *(buff + offset) & 0xC0 ) == 0x80 ); +} + +/* + * Send an ICMP message in response to a packet in error + */ + +void icmpv6_send(struct sk_buff *skb, int type, int code, __u32 info, + struct device *dev) +{ + struct ipv6hdr *hdr = skb->ipv6_hdr; + struct sock *sk = (struct sock *) icmpv6_socket.data; + struct in6_addr *saddr = NULL; + struct device *src_dev = NULL; + struct icmpv6_msg msg; + int addr_type = 0; + int optlen; + int len; + + /* + * sanity check pointer in case of parameter problem + */ + + if (type == ICMPV6_PARAMETER_PROB && + (info > (skb->tail - ((unsigned char *) hdr)))) + { + printk(KERN_DEBUG "icmpv6_send: bug! pointer > skb\n"); + return; + } + + /* + * Make sure we respect the rules + * i.e. RFC 1885 2.4(e) + * Rule (e.1) is enforced by not using icmpv6_send + * in any code that processes icmp errors. + */ + + addr_type = ipv6_addr_type(&hdr->daddr); + + if (ipv6_chk_addr(&hdr->daddr)) + { + saddr = &hdr->daddr; + } + + /* + * Dest addr check + */ + + if ((addr_type & IPV6_ADDR_MULTICAST || skb->pkt_type != PACKET_HOST)) + { + if (type != ICMPV6_PKT_TOOBIG && + !(type == ICMPV6_PARAMETER_PROB && + code == ICMPV6_UNK_OPTION && + (opt_unrec(skb, info)))) + { + return; + } + + saddr = NULL; + } + + addr_type = ipv6_addr_type(&hdr->saddr); + + /* + * Source addr check + */ + + if (addr_type & IPV6_ADDR_LINKLOCAL) + { + src_dev = skb->dev; + } + + /* + * Must not send if we know that source is Anycast also. + * for now we don't know that. + */ + if ((addr_type == IPV6_ADDR_ANY) || (addr_type & IPV6_ADDR_MULTICAST)) + { + printk(KERN_DEBUG "icmpv6_send: addr_any/mcast source\n"); + return; + } + + /* + * ok. kick it. checksum will be provided by the + * getfrag_t callback. + */ + + msg.icmph.type = type; + msg.icmph.code = code; + msg.icmph.checksum = 0; + msg.icmph.icmp6_pointer = htonl(info); + + msg.data = (__u8 *) skb->ipv6_hdr; + msg.csum = 0; + msg.daddr = &hdr->saddr; + /* + if (skb->opt) + optlen = skb->opt->optlen; + else + */ + + optlen = 0; + + len = min(skb->tail - ((unsigned char *) hdr), + 576 - sizeof(struct ipv6hdr) - sizeof(struct icmpv6hdr) + - optlen); + + if (len < 0) + { + printk(KERN_DEBUG "icmp: len problem\n"); + return; + } + + len += sizeof(struct icmpv6hdr); + + msg.len = len; + + + ipv6_build_xmit(sk, icmpv6_getfrag, &msg, &hdr->saddr, len, + saddr, src_dev, NULL, IPPROTO_ICMPV6, 1); +} + +static void icmpv6_echo_reply(struct sk_buff *skb) +{ + struct sock *sk = (struct sock *) icmpv6_socket.data; + struct ipv6hdr *hdr = skb->ipv6_hdr; + struct icmpv6hdr *icmph = (struct icmpv6hdr *) skb->h.raw; + struct in6_addr *saddr; + struct icmpv6_msg msg; + unsigned char *data; + int len; + + data = (char *) (icmph + 1); + + saddr = &hdr->daddr; + + if (ipv6_addr_type(saddr) & IPV6_ADDR_MULTICAST) + saddr = NULL; + + len = skb->tail - data; + len += sizeof(struct icmpv6hdr); + + msg.icmph.type = ICMPV6_ECHO_REPLY; + msg.icmph.code = 0; + msg.icmph.checksum = 0; + msg.icmph.icmp6_identifier = icmph->icmp6_identifier; + msg.icmph.icmp6_sequence = icmph->icmp6_sequence; + + msg.data = data; + msg.csum = 0; + msg.len = len; + msg.daddr = &hdr->saddr; + + ipv6_build_xmit(sk, icmpv6_getfrag, &msg, &hdr->saddr, len, saddr, + skb->dev, NULL, IPPROTO_ICMPV6, 1); +} + +static __inline__ int ipv6_ext_hdr(u8 nexthdr) +{ + /* + * find out if nexthdr is an extension header or a protocol + */ + return ( (nexthdr == NEXTHDR_HOP) || + (nexthdr == NEXTHDR_ROUTING) || + (nexthdr == NEXTHDR_FRAGMENT) || + (nexthdr == NEXTHDR_ESP) || + (nexthdr == NEXTHDR_AUTH) || + (nexthdr == NEXTHDR_NONE) || + (nexthdr == NEXTHDR_DEST) ); + +} + +static void icmpv6_notify(int type, int code, unsigned char *buff, int len, + struct in6_addr *saddr, struct in6_addr *daddr, + struct inet6_protocol *protocol) +{ + struct ipv6hdr *hdr = (struct ipv6hdr *) buff; + struct inet6_protocol *ipprot; + struct sock *sk; + char * pbuff; + __u32 info = 0; + int hash; + u8 nexthdr; + + /* now skip over extension headers */ + + nexthdr = hdr->nexthdr; + + pbuff = (char *) (hdr + 1); + len -= sizeof(struct ipv6hdr); + + while (ipv6_ext_hdr(nexthdr)) + { + int hdrlen; + + if (nexthdr == NEXTHDR_NONE) + return; + + nexthdr = *pbuff; + hdrlen = *(pbuff+1); + + if (((hdrlen + 1) << 3) > len) + return; + + pbuff += hdrlen; + len -= hdrlen; + } + + hash = nexthdr & (MAX_INET_PROTOS -1); + + for (ipprot = (struct inet6_protocol *) inet6_protos[hash]; + ipprot != NULL; + ipprot=(struct inet6_protocol *)ipprot->next) + { + if (ipprot->protocol != nexthdr) + continue; + + if (ipprot->err_handler) + { + ipprot->err_handler(type, code, pbuff, info, + saddr, daddr, ipprot); + } + return; + } + + /* delivery to upper layer protocols failed. try raw sockets */ + + sk = rawv6_prot.sock_array[hash]; + + if (sk == NULL) + { + return; + } + + while ((sk = inet6_get_sock_raw(sk, nexthdr, daddr, saddr))) + { + rawv6_err(sk, type, code, pbuff, saddr, daddr); + sk = sk->next; + } + + return; +} + +/* + * Handle icmp messages + */ + +int icmpv6_rcv(struct sk_buff *skb, struct device *dev, + struct in6_addr *saddr, struct in6_addr *daddr, + struct ipv6_options *opt, unsigned short len, + int redo, struct inet6_protocol *protocol) +{ + struct ipv6hdr *orig_hdr; + struct icmpv6hdr *hdr = (struct icmpv6hdr *) skb->h.raw; + int ulen; + + /* perform checksum */ + + + switch (skb->ip_summed) { + case CHECKSUM_NONE: + skb->csum = csum_partial((char *)hdr, len, 0); + case CHECKSUM_HW: + if (csum_ipv6_magic(saddr, daddr, len, IPPROTO_ICMPV6, + skb->csum)) + { + printk(KERN_DEBUG "icmpv6 checksum failed\n"); + goto discard_it; + } + default: + /* CHECKSUM_UNNECESSARY */ + } + + /* + * length of original packet carried in skb + */ + ulen = skb->tail - (unsigned char *) (hdr + 1); + + switch (hdr->type) { + + case ICMPV6_ECHO_REQUEST: + icmpv6_echo_reply(skb); + break; + + case ICMPV6_ECHO_REPLY: + /* we coulnd't care less */ + break; + + case ICMPV6_PKT_TOOBIG: + orig_hdr = (struct ipv6hdr *) (hdr + 1); + if (ulen >= sizeof(struct ipv6hdr)) + { + rt6_handle_pmtu(&orig_hdr->daddr, + ntohl(hdr->icmp6_mtu)); + } + + /* + * Drop through to notify + */ + + case ICMPV6_DEST_UNREACH: + case ICMPV6_TIME_EXCEEDED: + case ICMPV6_PARAMETER_PROB: + + icmpv6_notify(hdr->type, hdr->code, (char *) (hdr + 1), ulen, + saddr, daddr, protocol); + break; + + case NDISC_ROUTER_SOLICITATION: + case NDISC_ROUTER_ADVERTISEMENT: + case NDISC_NEIGHBOUR_SOLICITATION: + case NDISC_NEIGHBOUR_ADVERTISEMENT: + case NDISC_REDIRECT: + ndisc_rcv(skb, dev, saddr, daddr, opt, len); + break; + + case ICMPV6_MEMBERSHIP_QUERY: + case ICMPV6_MEMBERSHIP_REPORT: + case ICMPV6_MEMBERSHIP_REDUCTION: + /* forward the packet to the igmp module */ + break; + + default: + printk(KERN_DEBUG "icmpv6: msg of unkown type\n"); + + /* informational */ + if (hdr->type & 0x80) + { + goto discard_it; + } + + /* + * error of unkown type. + * must pass to upper level + */ + + icmpv6_notify(hdr->type, hdr->code, (char *) (hdr + 1), ulen, + saddr, daddr, protocol); + } + + discard_it: + + kfree_skb(skb, FREE_READ); + return 0; +} + +void icmpv6_init(struct proto_ops *ops) +{ + struct sock *sk; + int err; + + icmpv6_socket.type=SOCK_RAW; + icmpv6_socket.ops=ops; + + if((err=ops->create(&icmpv6_socket, IPPROTO_ICMPV6))<0) + printk(KERN_DEBUG + "Failed to create the ICMP control socket.\n"); + + MOD_DEC_USE_COUNT; + + sk = icmpv6_socket.data; + sk->allocation = GFP_ATOMIC; + sk->num = 256; /* Don't receive any data */ + + inet6_add_protocol(&icmpv6_protocol); +} + +static struct icmp6_err { + int err; + int fatal; +} tab_unreach[] = { + { ENETUNREACH, 0}, /* NOROUTE */ + { EACCES, 1}, /* ADM_PROHIBITED */ + { EOPNOTSUPP, 1}, /* NOT_NEIGHBOUR */ + { EHOSTUNREACH, 0}, /* ADDR_UNREACH */ + { ECONNREFUSED, 1}, /* PORT_UNREACH */ +}; + +int icmpv6_err_convert(int type, int code, int *err) +{ + int fatal = 0; + + *err = 0; + + switch (type) { + case ICMPV6_DEST_UNREACH: + if (code <= ICMPV6_PORT_UNREACH) + { + *err = tab_unreach[code].err; + fatal = tab_unreach[code].fatal; + } + break; + + case ICMPV6_PKT_TOOBIG: + *err = EMSGSIZE; + break; + + case ICMPV6_PARAMETER_PROB: + *err = EPROTO; + fatal = 1; + break; + }; + + return fatal; +} + +/* + * Local variables: + * compile-command: "gcc -D__KERNEL__ -I/usr/src/linux/include -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -fno-strength-reduce -pipe -m486 -DCPU=486 -DMODULE -DMODVERSIONS -include /usr/src/linux/include/linux/modversions.h -c -o icmp.o icmp.c" + * End: + */ |