blob: e9e7dbc7c3078b133f2233254293c23dbc6c72f6 [file] [log] [blame]
/*
* Fastpath Ndisc
*
* 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.
*/
#include <net/ndisc.h>
#include <net/ipv6.h>
#include <net/addrconf.h>
#include <net/ip6_route.h>
#include "fp_common.h"
#include "fp_device.h"
#include "fp_ndisc.h"
#define IN6ADDR_LINKLOCAL_ADDR \
{ { { 0xfe,0x80,0,0,0,0,0,0,0,0,0,0,0,0,0,1 } } }
const struct in6_addr in6addr_linklocal_addr = IN6ADDR_LINKLOCAL_ADDR;
static struct nd_opt_hdr *fpnd_next_option(struct nd_opt_hdr *cur,
struct nd_opt_hdr *end)
{
int type;
if (!cur || !end || cur >= end)
return NULL;
type = cur->nd_opt_type;
do {
cur = ((void *)cur) + (cur->nd_opt_len << 3);
} while (cur < end && cur->nd_opt_type != type);
return cur <= end && cur->nd_opt_type == type ? cur : NULL;
}
static inline bool fpnd_icmp6_type_eq(struct sk_buff *skb, u32 type)
{
struct ipv6hdr *iph = (struct ipv6hdr *)skb->data;
struct icmp6hdr *icmph = (struct icmp6hdr *)(iph + 1);
if (likely(iph->nexthdr != IPPROTO_ICMPV6 ||
icmph->icmp6_code != 0 ||
icmph->icmp6_type != type)) {
return false;
}
return true;
}
bool fpnd_is_ra(struct sk_buff *skb)
{
return fpnd_icmp6_type_eq(skb, NDISC_ROUTER_ADVERTISEMENT);
}
bool fpnd_is_rs(struct sk_buff *skb)
{
return fpnd_icmp6_type_eq(skb, NDISC_ROUTER_SOLICITATION);
}
static void fpnd_set_pref(struct fp_net_device *src, u8 *opt, int len)
{
struct prefix_info *pinfo;
__u32 valid_lft;
__u32 prefered_lft;
int addr_type;
pinfo = (struct prefix_info *) opt;
if (len < sizeof(struct prefix_info)) {
pr_debug("prefix option too short\n");
return;
}
addr_type = ipv6_addr_type(&pinfo->prefix);
if (addr_type & (IPV6_ADDR_MULTICAST | IPV6_ADDR_LINKLOCAL))
return;
valid_lft = ntohl(pinfo->valid);
prefered_lft = ntohl(pinfo->prefered);
if (prefered_lft > valid_lft) {
pr_debug("prefix option has invalid lifetime\n");
return;
}
src->prefixlen = pinfo->prefix_len;
memcpy(&src->gb6addr, &pinfo->prefix, sizeof(struct in6_addr));
fpdev_set_gb6(src);
pr_debug("prefix for dev (%s) is (%pI6c) len (%d), sending to USB\n",
src->dev->name, &pinfo->prefix, pinfo->prefix_len);
}
void fpnd_process_ra(struct net_device *src, struct sk_buff *skb)
{
struct fp_net_device *fpdev;
struct ndisc_options ndopts;
struct nd_opt_hdr *p;
int optlen;
struct ipv6hdr *iph = (struct ipv6hdr *)skb->data;
struct ra_msg *ra_msg = (struct ra_msg *)(iph + 1);
__u8 *opt = (__u8 *)(ra_msg + 1);
optlen = (skb->tail - (u8 *)ra_msg) - sizeof(struct ra_msg);
if (!(ipv6_addr_type(&iph->saddr) & IPV6_ADDR_LINKLOCAL)) {
pr_debug("source address is not link-local\n");
return;
}
if (optlen < 0) {
pr_debug("packet too short\n");
return;
}
if (!ndisc_parse_options(skb->dev, opt, optlen, &ndopts)) {
pr_debug("invalid ND options\n");
return;
}
fpdev = fpdev_get_if(src);
if (unlikely(!fpdev))
return;
if (ndopts.nd_opts_mtu) {
struct mtu_option *mtuinfo = (struct mtu_option *) ndopts.nd_opts_mtu;
fpdev->mtu = ntohl(mtuinfo->mtu);
fpdev_set_mtu(fpdev);
}
if (ndopts.nd_opts_pi) {
for (p = ndopts.nd_opts_pi;
p;
p = fpnd_next_option(p, ndopts.nd_opts_pi_end)) {
fpnd_set_pref(fpdev, (u8 *)p, (p->nd_opt_len) << 3);
}
}
fpdev_put(fpdev);
}
static void fp_ip6_nd_hdr(struct sk_buff *skb,
const struct in6_addr *saddr,
const struct in6_addr *daddr,
int hop_limit, int len)
{
struct ipv6hdr *hdr;
skb_push(skb, sizeof(*hdr));
skb_reset_network_header(skb);
hdr = ipv6_hdr(skb);
ip6_flow_hdr(hdr, 0, 0);
hdr->payload_len = htons(len);
hdr->nexthdr = IPPROTO_ICMPV6;
hdr->hop_limit = hop_limit;
hdr->saddr = *saddr;
hdr->daddr = *daddr;
}
static struct sk_buff *fp_ndisc_alloc_skb(struct net_device *dev,
int len)
{
int hlen = LL_RESERVED_SPACE(dev);
int tlen = dev->needed_tailroom;
struct sock *sk = dev_net(dev)->ipv6.ndisc_sk;
struct sk_buff *skb;
skb = alloc_skb(hlen + sizeof(struct ipv6hdr) + len + tlen, GFP_ATOMIC);
if (!skb) {
printk("ndisc: %s failed to allocate an skb\n", __func__);
return NULL;
}
skb->protocol = htons(ETH_P_IPV6);
skb->dev = dev;
skb_reserve(skb, hlen + sizeof(struct ipv6hdr));
skb_reset_transport_header(skb);
/* Manually assign socket ownership as we avoid calling
* sock_alloc_send_pskb() to bypass wmem buffer limits
*/
skb_set_owner_w(skb, sk);
return skb;
}
int fpnd_process_rs(struct sk_buff *skb)
{
struct sk_buff *nskb;
struct icmp6hdr *icmp6h;
struct ra_msg *msg;
struct prefix_info *prefix;
struct mtu_option *mtu;
struct fp_net_device *fpdev;
int err;
fpdev = fpdev_get_ccinet();
if (unlikely(!fpdev))
return 0;
nskb = fp_ndisc_alloc_skb(fpdev->dev, sizeof(*msg) + sizeof(struct prefix_info) + sizeof(struct mtu_option));
if (!nskb) {
fpdev_put(fpdev);
return 0;
}
msg = (struct ra_msg *)skb_put(nskb, sizeof(*msg));
*msg = (struct ra_msg) {
.icmph = {
.icmp6_type = NDISC_ROUTER_ADVERTISEMENT,
.icmp6_hop_limit = 0xFF,
.icmp6_rt_lifetime = htons(0xFFFF),
},
};
prefix = (struct prefix_info *)skb_put(nskb, sizeof(struct prefix_info));
prefix->type = ND_OPT_PREFIX_INFO;
prefix->length = 4;
prefix->prefix_len = fpdev->prefixlen;
prefix->autoconf = 1;
prefix->valid = htonl(0xFFFFFFFF);
prefix->prefered = htonl(0xFFFFFFFF);
memcpy(&prefix->prefix, &fpdev->gb6addr, sizeof(struct in6_addr));
mtu = (struct mtu_option *)skb_put(nskb, sizeof(struct mtu_option));
mtu->type = ND_OPT_MTU;
mtu->length = 1;
mtu->mtu = htonl(fpdev->mtu);
icmp6h = icmp6_hdr(nskb);
icmp6h->icmp6_cksum = csum_ipv6_magic(&in6addr_linklocal_addr, &fpdev->ll6addr, nskb->len,
IPPROTO_ICMPV6,
csum_partial(icmp6h,
nskb->len, 0));
fp_ip6_nd_hdr(nskb, &in6addr_linklocal_addr, &fpdev->ll6addr, 0xFF, nskb->len);
err = netif_rx(nskb);
fpdev_put(fpdev);
if(!err) {
dev_kfree_skb_any(skb);
return 1;
}
return 0;
}