| /* |
| * Copyright (C)2003-2006 Helsinki University of Technology |
| * Copyright (C)2003-2006 USAGI/WIDE Project |
| * |
| * 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. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| /* |
| * Authors: |
| * Noriaki TAKAMIYA @USAGI |
| * Masahide NAKAMURA @USAGI |
| */ |
| |
| #include <linux/config.h> |
| #include <linux/module.h> |
| #include <linux/skbuff.h> |
| #include <linux/ipv6.h> |
| #include <net/ipv6.h> |
| #include <net/xfrm.h> |
| #include <net/mip6.h> |
| |
| static xfrm_address_t *mip6_xfrm_addr(struct xfrm_state *x, xfrm_address_t *addr) |
| { |
| return x->coaddr; |
| } |
| |
| static inline unsigned int calc_padlen(unsigned int len, unsigned int n) |
| { |
| return (n - len + 16) & 0x7; |
| } |
| |
| static inline void *mip6_padn(__u8 *data, __u8 padlen) |
| { |
| if (!data) |
| return NULL; |
| if (padlen == 1) { |
| data[0] = MIP6_OPT_PAD_1; |
| } else if (padlen > 1) { |
| data[0] = MIP6_OPT_PAD_N; |
| data[1] = padlen - 2; |
| if (padlen > 2) |
| memset(data+2, 0, data[1]); |
| } |
| return data + padlen; |
| } |
| |
| static int mip6_destopt_input(struct xfrm_state *x, struct sk_buff *skb) |
| { |
| struct ipv6hdr *iph = skb->nh.ipv6h; |
| struct ipv6_destopt_hdr *destopt = (struct ipv6_destopt_hdr *)skb->data; |
| |
| if (!ipv6_addr_equal(&iph->saddr, (struct in6_addr *)x->coaddr) && |
| !ipv6_addr_any((struct in6_addr *)x->coaddr)) |
| return -ENOENT; |
| |
| return destopt->nexthdr; |
| } |
| |
| /* Destination Option Header is inserted. |
| * IP Header's src address is replaced with Home Address Option in |
| * Destination Option Header. |
| */ |
| static int mip6_destopt_output(struct xfrm_state *x, struct sk_buff *skb) |
| { |
| struct ipv6hdr *iph; |
| struct ipv6_destopt_hdr *dstopt; |
| struct ipv6_destopt_hao *hao; |
| u8 nexthdr; |
| int len; |
| |
| iph = (struct ipv6hdr *)skb->data; |
| iph->payload_len = htons(skb->len - sizeof(*iph)); |
| |
| nexthdr = *skb->nh.raw; |
| *skb->nh.raw = IPPROTO_DSTOPTS; |
| |
| dstopt = (struct ipv6_destopt_hdr *)skb->h.raw; |
| dstopt->nexthdr = nexthdr; |
| |
| hao = mip6_padn((char *)(dstopt + 1), |
| calc_padlen(sizeof(*dstopt), 6)); |
| |
| hao->type = IPV6_TLV_HAO; |
| hao->length = sizeof(*hao) - 2; |
| BUG_TRAP(hao->length == 16); |
| |
| len = ((char *)hao - (char *)dstopt) + sizeof(*hao); |
| |
| memcpy(&hao->addr, &iph->saddr, sizeof(hao->addr)); |
| memcpy(&iph->saddr, x->coaddr, sizeof(iph->saddr)); |
| |
| BUG_TRAP(len == x->props.header_len); |
| dstopt->hdrlen = (x->props.header_len >> 3) - 1; |
| |
| return 0; |
| } |
| |
| static int mip6_destopt_offset(struct xfrm_state *x, struct sk_buff *skb, |
| u8 **nexthdr) |
| { |
| u16 offset = sizeof(struct ipv6hdr); |
| struct ipv6_opt_hdr *exthdr = (struct ipv6_opt_hdr*)(skb->nh.ipv6h + 1); |
| unsigned int packet_len = skb->tail - skb->nh.raw; |
| int found_rhdr = 0; |
| |
| *nexthdr = &skb->nh.ipv6h->nexthdr; |
| |
| while (offset + 1 <= packet_len) { |
| |
| switch (**nexthdr) { |
| case NEXTHDR_HOP: |
| break; |
| case NEXTHDR_ROUTING: |
| found_rhdr = 1; |
| break; |
| case NEXTHDR_DEST: |
| /* |
| * HAO MUST NOT appear more than once. |
| * XXX: It is better to try to find by the end of |
| * XXX: packet if HAO exists. |
| */ |
| if (ipv6_find_tlv(skb, offset, IPV6_TLV_HAO) >= 0) { |
| LIMIT_NETDEBUG(KERN_WARNING "mip6: hao exists already, override\n"); |
| return offset; |
| } |
| |
| if (found_rhdr) |
| return offset; |
| |
| break; |
| default: |
| return offset; |
| } |
| |
| offset += ipv6_optlen(exthdr); |
| *nexthdr = &exthdr->nexthdr; |
| exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset); |
| } |
| |
| return offset; |
| } |
| |
| static int mip6_destopt_init_state(struct xfrm_state *x) |
| { |
| if (x->id.spi) { |
| printk(KERN_INFO "%s: spi is not 0: %u\n", __FUNCTION__, |
| x->id.spi); |
| return -EINVAL; |
| } |
| if (x->props.mode != XFRM_MODE_ROUTEOPTIMIZATION) { |
| printk(KERN_INFO "%s: state's mode is not %u: %u\n", |
| __FUNCTION__, XFRM_MODE_ROUTEOPTIMIZATION, x->props.mode); |
| return -EINVAL; |
| } |
| |
| x->props.header_len = sizeof(struct ipv6_destopt_hdr) + |
| calc_padlen(sizeof(struct ipv6_destopt_hdr), 6) + |
| sizeof(struct ipv6_destopt_hao); |
| BUG_TRAP(x->props.header_len == 24); |
| |
| return 0; |
| } |
| |
| /* |
| * Do nothing about destroying since it has no specific operation for |
| * destination options header unlike IPsec protocols. |
| */ |
| static void mip6_destopt_destroy(struct xfrm_state *x) |
| { |
| } |
| |
| static struct xfrm_type mip6_destopt_type = |
| { |
| .description = "MIP6DESTOPT", |
| .owner = THIS_MODULE, |
| .proto = IPPROTO_DSTOPTS, |
| .flags = XFRM_TYPE_NON_FRAGMENT, |
| .init_state = mip6_destopt_init_state, |
| .destructor = mip6_destopt_destroy, |
| .input = mip6_destopt_input, |
| .output = mip6_destopt_output, |
| .hdr_offset = mip6_destopt_offset, |
| .local_addr = mip6_xfrm_addr, |
| }; |
| |
| static int mip6_rthdr_input(struct xfrm_state *x, struct sk_buff *skb) |
| { |
| struct rt2_hdr *rt2 = (struct rt2_hdr *)skb->data; |
| |
| if (!ipv6_addr_equal(&rt2->addr, (struct in6_addr *)x->coaddr) && |
| !ipv6_addr_any((struct in6_addr *)x->coaddr)) |
| return -ENOENT; |
| |
| return rt2->rt_hdr.nexthdr; |
| } |
| |
| /* Routing Header type 2 is inserted. |
| * IP Header's dst address is replaced with Routing Header's Home Address. |
| */ |
| static int mip6_rthdr_output(struct xfrm_state *x, struct sk_buff *skb) |
| { |
| struct ipv6hdr *iph; |
| struct rt2_hdr *rt2; |
| u8 nexthdr; |
| |
| iph = (struct ipv6hdr *)skb->data; |
| iph->payload_len = htons(skb->len - sizeof(*iph)); |
| |
| nexthdr = *skb->nh.raw; |
| *skb->nh.raw = IPPROTO_ROUTING; |
| |
| rt2 = (struct rt2_hdr *)skb->h.raw; |
| rt2->rt_hdr.nexthdr = nexthdr; |
| rt2->rt_hdr.hdrlen = (x->props.header_len >> 3) - 1; |
| rt2->rt_hdr.type = IPV6_SRCRT_TYPE_2; |
| rt2->rt_hdr.segments_left = 1; |
| memset(&rt2->reserved, 0, sizeof(rt2->reserved)); |
| |
| BUG_TRAP(rt2->rt_hdr.hdrlen == 2); |
| |
| memcpy(&rt2->addr, &iph->daddr, sizeof(rt2->addr)); |
| memcpy(&iph->daddr, x->coaddr, sizeof(iph->daddr)); |
| |
| return 0; |
| } |
| |
| static int mip6_rthdr_offset(struct xfrm_state *x, struct sk_buff *skb, |
| u8 **nexthdr) |
| { |
| u16 offset = sizeof(struct ipv6hdr); |
| struct ipv6_opt_hdr *exthdr = (struct ipv6_opt_hdr*)(skb->nh.ipv6h + 1); |
| unsigned int packet_len = skb->tail - skb->nh.raw; |
| int found_rhdr = 0; |
| |
| *nexthdr = &skb->nh.ipv6h->nexthdr; |
| |
| while (offset + 1 <= packet_len) { |
| |
| switch (**nexthdr) { |
| case NEXTHDR_HOP: |
| break; |
| case NEXTHDR_ROUTING: |
| if (offset + 3 <= packet_len) { |
| struct ipv6_rt_hdr *rt; |
| rt = (struct ipv6_rt_hdr *)(skb->nh.raw + offset); |
| if (rt->type != 0) |
| return offset; |
| } |
| found_rhdr = 1; |
| break; |
| case NEXTHDR_DEST: |
| if (ipv6_find_tlv(skb, offset, IPV6_TLV_HAO) >= 0) |
| return offset; |
| |
| if (found_rhdr) |
| return offset; |
| |
| break; |
| default: |
| return offset; |
| } |
| |
| offset += ipv6_optlen(exthdr); |
| *nexthdr = &exthdr->nexthdr; |
| exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset); |
| } |
| |
| return offset; |
| } |
| |
| static int mip6_rthdr_init_state(struct xfrm_state *x) |
| { |
| if (x->id.spi) { |
| printk(KERN_INFO "%s: spi is not 0: %u\n", __FUNCTION__, |
| x->id.spi); |
| return -EINVAL; |
| } |
| if (x->props.mode != XFRM_MODE_ROUTEOPTIMIZATION) { |
| printk(KERN_INFO "%s: state's mode is not %u: %u\n", |
| __FUNCTION__, XFRM_MODE_ROUTEOPTIMIZATION, x->props.mode); |
| return -EINVAL; |
| } |
| |
| x->props.header_len = sizeof(struct rt2_hdr); |
| |
| return 0; |
| } |
| |
| /* |
| * Do nothing about destroying since it has no specific operation for routing |
| * header type 2 unlike IPsec protocols. |
| */ |
| static void mip6_rthdr_destroy(struct xfrm_state *x) |
| { |
| } |
| |
| static struct xfrm_type mip6_rthdr_type = |
| { |
| .description = "MIP6RT", |
| .owner = THIS_MODULE, |
| .proto = IPPROTO_ROUTING, |
| .flags = XFRM_TYPE_NON_FRAGMENT, |
| .init_state = mip6_rthdr_init_state, |
| .destructor = mip6_rthdr_destroy, |
| .input = mip6_rthdr_input, |
| .output = mip6_rthdr_output, |
| .hdr_offset = mip6_rthdr_offset, |
| .remote_addr = mip6_xfrm_addr, |
| }; |
| |
| int __init mip6_init(void) |
| { |
| printk(KERN_INFO "Mobile IPv6\n"); |
| |
| if (xfrm_register_type(&mip6_destopt_type, AF_INET6) < 0) { |
| printk(KERN_INFO "%s: can't add xfrm type(destopt)\n", __FUNCTION__); |
| goto mip6_destopt_xfrm_fail; |
| } |
| if (xfrm_register_type(&mip6_rthdr_type, AF_INET6) < 0) { |
| printk(KERN_INFO "%s: can't add xfrm type(rthdr)\n", __FUNCTION__); |
| goto mip6_rthdr_xfrm_fail; |
| } |
| return 0; |
| |
| mip6_rthdr_xfrm_fail: |
| xfrm_unregister_type(&mip6_destopt_type, AF_INET6); |
| mip6_destopt_xfrm_fail: |
| return -EAGAIN; |
| } |
| |
| void __exit mip6_fini(void) |
| { |
| if (xfrm_unregister_type(&mip6_rthdr_type, AF_INET6) < 0) |
| printk(KERN_INFO "%s: can't remove xfrm type(rthdr)\n", __FUNCTION__); |
| if (xfrm_unregister_type(&mip6_destopt_type, AF_INET6) < 0) |
| printk(KERN_INFO "%s: can't remove xfrm type(destopt)\n", __FUNCTION__); |
| } |