Commit b06f9527 authored by David S. Miller's avatar David S. Miller

Merge branch '6lowpan-ndisc'

Alexander Aring says:

====================
6lowpan: introduce 6lowpan-nd

David can you please pick-up this patch-serie for your net-next tree?
Thanks in advance.

This patch series introduces the ndisc ops callback structure to add different
handling for IPv6 neighbour discovery cache functionality. It implements at first
the two following use-cases:

 - 6CO handling as userspace option (For all 6LoWPAN layers, BTLE/802.15.4) [0]
 - short address handling for 802.15.4 6LoWPAN only [1]

Since my last patch series, I completely changed the whole ndisc_ops callback
structure to not replace the whole ndisc functionality at recv/send level of
NS/NA/RS/RA which I send in my previous patch-series "6lowpan: introduce basic
6lowpan-nd". I changed it now to add different handling in a very low-level way
of ndisc functionality.

The ndisc_ops don't must be registered to dev->ndisc_ops anymore, if they are not
set, then no additional ipv6 ndisc handling will be done.

This patch series now introduce a complete handling of short address for
802.15.4 6LoWPAN in case of send/recv of NA/NS/RS and RA. In case of RA
(receive only) and PIO we also need a second prefix + short-address based
address.

This callback structure can be used later (I hope) for RFC 6775 [0]. This RFC
defines some new option fields and messages for 6LoWPAN-ND. This patch series
does not implement RFC 6775 (except we decide now to handle 6CO in userspace).

Additional we can use the current ops for parse/fill ndisc options for kernel
handled ndisc messages to add 6CIO, see [2].

I tested RA/NS/NA/RS messages with short address which seems to work, what I
didn't test is the redirect messages since I don't know how to generate them.
The short address for redirect messages are also some special case here, because
the short address by a L3 target address lookuped by neighbour cache need to be
added.

btw:
According to [3] sending redirect messages should be also disabled by default
on 6lowpan interfaces, but can be activated afterwards. This is maybe
something for the ipv6_devconf structure. There is a "accept_redirects" but
no "disable_redirects".

- Alex

[0] https://tools.ietf.org/html/rfc6775
[1] https://tools.ietf.org/html/rfc4944#section-8
[2] https://tools.ietf.org/html/rfc7400

changes since v3:
 - add acked-by and reviewed-by tags
 - fix url references in cover-letter
 - add cover-letter that this patch series is okay to go through net-next tree

changes since RFC:
 - add lowlevel functions __ndisc_opt_addr_space,
   __ndisc_opt_addr_data and __ndisc_fill_addr_option for corresponding
   functions which doesn't requires net_device argument.
 - move ndisc_ops e.g. ndisc_ops_fill_addr_option function call into the
   corresponding device argument function ndisc_fill_addr_option.
   (Introduced a special static inline function for redirect handling).
 - fix error handling in addrconf_prefix_rcv_add_addr.
   (Please see, introduce new API handling that second address registration
    (in case of 802.15.4 6LoWPAN) will still be notified if failed, because
    dev->addr was successful.
 - add ieee802154 sub-directory in short address entry for 6lowpan UAPI.
 - add lowpan_802154_is_valid_src_short_addr, because 802.15.4 6lowpan
   defines the first bit as multicast (don't know how this can be working
   at the end, because some hardware addresses will handle such addresses
   in L2 as unicast. See:
   https://www.iana.org/assignments/_6lowpan-parameters/_6lowpan-parameters.xhtml#_6lowpan-parameters-2

changes since v2:
 - Introduce ndisc_ops to have our own implementation for dealing with NS/NA
   which allows also to support RFC6775 (e.g. ARO).
 - add handling for handling 6CO as userspace option for RA messages in
   case of 6LoWPAN interfaces.
 - change lowpan_is_ll to check on linklayer type only.
 - added some reviewed-by's.
 - move short addr slaac to net/6lowpan instead ipv6 handling.
 - add handling for context based address compression in case for
   short address as link-layer address.
 - change strategy to use short address, a short address will always be used
   when it's available.
 - Handle override flag in NA messages to update short address information or
   not.
====================
Acked-by: default avatarYOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 60100978 eab560e5
...@@ -1456,6 +1456,8 @@ enum netdev_priv_flags { ...@@ -1456,6 +1456,8 @@ enum netdev_priv_flags {
* @netdev_ops: Includes several pointers to callbacks, * @netdev_ops: Includes several pointers to callbacks,
* if one wants to override the ndo_*() functions * if one wants to override the ndo_*() functions
* @ethtool_ops: Management operations * @ethtool_ops: Management operations
* @ndisc_ops: Includes callbacks for different IPv6 neighbour
* discovery handling. Necessary for e.g. 6LoWPAN.
* @header_ops: Includes callbacks for creating,parsing,caching,etc * @header_ops: Includes callbacks for creating,parsing,caching,etc
* of Layer 2 headers. * of Layer 2 headers.
* *
...@@ -1483,8 +1485,7 @@ enum netdev_priv_flags { ...@@ -1483,8 +1485,7 @@ enum netdev_priv_flags {
* @perm_addr: Permanent hw address * @perm_addr: Permanent hw address
* @addr_assign_type: Hw address assignment type * @addr_assign_type: Hw address assignment type
* @addr_len: Hardware address length * @addr_len: Hardware address length
* @neigh_priv_len; Used in neigh_alloc(), * @neigh_priv_len: Used in neigh_alloc()
* initialized only in atm/clip.c
* @dev_id: Used to differentiate devices that share * @dev_id: Used to differentiate devices that share
* the same link layer address * the same link layer address
* @dev_port: Used to differentiate devices that share * @dev_port: Used to differentiate devices that share
...@@ -1673,6 +1674,9 @@ struct net_device { ...@@ -1673,6 +1674,9 @@ struct net_device {
#ifdef CONFIG_NET_L3_MASTER_DEV #ifdef CONFIG_NET_L3_MASTER_DEV
const struct l3mdev_ops *l3mdev_ops; const struct l3mdev_ops *l3mdev_ops;
#endif #endif
#if IS_ENABLED(CONFIG_IPV6)
const struct ndisc_ops *ndisc_ops;
#endif
const struct header_ops *header_ops; const struct header_ops *header_ops;
......
...@@ -141,6 +141,16 @@ struct lowpan_dev { ...@@ -141,6 +141,16 @@ struct lowpan_dev {
u8 priv[0] __aligned(sizeof(void *)); u8 priv[0] __aligned(sizeof(void *));
}; };
struct lowpan_802154_neigh {
__le16 short_addr;
};
static inline
struct lowpan_802154_neigh *lowpan_802154_neigh(void *neigh_priv)
{
return neigh_priv;
}
static inline static inline
struct lowpan_dev *lowpan_dev(const struct net_device *dev) struct lowpan_dev *lowpan_dev(const struct net_device *dev)
{ {
...@@ -244,6 +254,12 @@ static inline bool lowpan_fetch_skb(struct sk_buff *skb, void *data, ...@@ -244,6 +254,12 @@ static inline bool lowpan_fetch_skb(struct sk_buff *skb, void *data,
return false; return false;
} }
static inline bool lowpan_802154_is_valid_src_short_addr(__le16 addr)
{
/* First bit of addr is multicast, reserved or 802.15.4 specific */
return !(addr & cpu_to_le16(0x8000));
}
static inline void lowpan_push_hc_data(u8 **hc_ptr, const void *data, static inline void lowpan_push_hc_data(u8 **hc_ptr, const void *data,
const size_t len) const size_t len)
{ {
......
...@@ -94,6 +94,16 @@ int ipv6_rcv_saddr_equal(const struct sock *sk, const struct sock *sk2, ...@@ -94,6 +94,16 @@ int ipv6_rcv_saddr_equal(const struct sock *sk, const struct sock *sk2,
void addrconf_join_solict(struct net_device *dev, const struct in6_addr *addr); void addrconf_join_solict(struct net_device *dev, const struct in6_addr *addr);
void addrconf_leave_solict(struct inet6_dev *idev, const struct in6_addr *addr); void addrconf_leave_solict(struct inet6_dev *idev, const struct in6_addr *addr);
void addrconf_add_linklocal(struct inet6_dev *idev,
const struct in6_addr *addr, u32 flags);
int addrconf_prefix_rcv_add_addr(struct net *net, struct net_device *dev,
const struct prefix_info *pinfo,
struct inet6_dev *in6_dev,
const struct in6_addr *addr, int addr_type,
u32 addr_flags, bool sllao, bool tokenized,
__u32 valid_lft, u32 prefered_lft);
static inline int addrconf_ifid_eui48(u8 *eui, struct net_device *dev) static inline int addrconf_ifid_eui48(u8 *eui, struct net_device *dev)
{ {
if (dev->addr_len != ETH_ALEN) if (dev->addr_len != ETH_ALEN)
......
...@@ -35,6 +35,7 @@ enum { ...@@ -35,6 +35,7 @@ enum {
ND_OPT_ROUTE_INFO = 24, /* RFC4191 */ ND_OPT_ROUTE_INFO = 24, /* RFC4191 */
ND_OPT_RDNSS = 25, /* RFC5006 */ ND_OPT_RDNSS = 25, /* RFC5006 */
ND_OPT_DNSSL = 31, /* RFC6106 */ ND_OPT_DNSSL = 31, /* RFC6106 */
ND_OPT_6CO = 34, /* RFC6775 */
__ND_OPT_MAX __ND_OPT_MAX
}; };
...@@ -53,11 +54,21 @@ enum { ...@@ -53,11 +54,21 @@ enum {
#include <net/neighbour.h> #include <net/neighbour.h>
/* Set to 3 to get tracing... */
#define ND_DEBUG 1
#define ND_PRINTK(val, level, fmt, ...) \
do { \
if (val <= ND_DEBUG) \
net_##level##_ratelimited(fmt, ##__VA_ARGS__); \
} while (0)
struct ctl_table; struct ctl_table;
struct inet6_dev; struct inet6_dev;
struct net_device; struct net_device;
struct net_proto_family; struct net_proto_family;
struct sk_buff; struct sk_buff;
struct prefix_info;
extern struct neigh_table nd_tbl; extern struct neigh_table nd_tbl;
...@@ -99,20 +110,201 @@ struct ndisc_options { ...@@ -99,20 +110,201 @@ struct ndisc_options {
#endif #endif
struct nd_opt_hdr *nd_useropts; struct nd_opt_hdr *nd_useropts;
struct nd_opt_hdr *nd_useropts_end; struct nd_opt_hdr *nd_useropts_end;
#if IS_ENABLED(CONFIG_IEEE802154_6LOWPAN)
struct nd_opt_hdr *nd_802154_opt_array[ND_OPT_TARGET_LL_ADDR + 1];
#endif
}; };
#define nd_opts_src_lladdr nd_opt_array[ND_OPT_SOURCE_LL_ADDR] #define nd_opts_src_lladdr nd_opt_array[ND_OPT_SOURCE_LL_ADDR]
#define nd_opts_tgt_lladdr nd_opt_array[ND_OPT_TARGET_LL_ADDR] #define nd_opts_tgt_lladdr nd_opt_array[ND_OPT_TARGET_LL_ADDR]
#define nd_opts_pi nd_opt_array[ND_OPT_PREFIX_INFO] #define nd_opts_pi nd_opt_array[ND_OPT_PREFIX_INFO]
#define nd_opts_pi_end nd_opt_array[__ND_OPT_PREFIX_INFO_END] #define nd_opts_pi_end nd_opt_array[__ND_OPT_PREFIX_INFO_END]
#define nd_opts_rh nd_opt_array[ND_OPT_REDIRECT_HDR] #define nd_opts_rh nd_opt_array[ND_OPT_REDIRECT_HDR]
#define nd_opts_mtu nd_opt_array[ND_OPT_MTU] #define nd_opts_mtu nd_opt_array[ND_OPT_MTU]
#define nd_802154_opts_src_lladdr nd_802154_opt_array[ND_OPT_SOURCE_LL_ADDR]
#define nd_802154_opts_tgt_lladdr nd_802154_opt_array[ND_OPT_TARGET_LL_ADDR]
#define NDISC_OPT_SPACE(len) (((len)+2+7)&~7) #define NDISC_OPT_SPACE(len) (((len)+2+7)&~7)
struct ndisc_options *ndisc_parse_options(u8 *opt, int opt_len, struct ndisc_options *ndisc_parse_options(const struct net_device *dev,
u8 *opt, int opt_len,
struct ndisc_options *ndopts); struct ndisc_options *ndopts);
void __ndisc_fill_addr_option(struct sk_buff *skb, int type, void *data,
int data_len, int pad);
#define NDISC_OPS_REDIRECT_DATA_SPACE 2
/*
* This structure defines the hooks for IPv6 neighbour discovery.
* The following hooks can be defined; unless noted otherwise, they are
* optional and can be filled with a null pointer.
*
* int (*is_useropt)(u8 nd_opt_type):
* This function is called when IPv6 decide RA userspace options. if
* this function returns 1 then the option given by nd_opt_type will
* be handled as userspace option additional to the IPv6 options.
*
* int (*parse_options)(const struct net_device *dev,
* struct nd_opt_hdr *nd_opt,
* struct ndisc_options *ndopts):
* This function is called while parsing ndisc ops and put each position
* as pointer into ndopts. If this function return unequal 0, then this
* function took care about the ndisc option, if 0 then the IPv6 ndisc
* option parser will take care about that option.
*
* void (*update)(const struct net_device *dev, struct neighbour *n,
* u32 flags, u8 icmp6_type,
* const struct ndisc_options *ndopts):
* This function is called when IPv6 ndisc updates the neighbour cache
* entry. Additional options which can be updated may be previously
* parsed by parse_opts callback and accessible over ndopts parameter.
*
* int (*opt_addr_space)(const struct net_device *dev, u8 icmp6_type,
* struct neighbour *neigh, u8 *ha_buf,
* u8 **ha):
* This function is called when the necessary option space will be
* calculated before allocating a skb. The parameters neigh, ha_buf
* abd ha are available on NDISC_REDIRECT messages only.
*
* void (*fill_addr_option)(const struct net_device *dev,
* struct sk_buff *skb, u8 icmp6_type,
* const u8 *ha):
* This function is called when the skb will finally fill the option
* fields inside skb. NOTE: this callback should fill the option
* fields to the skb which are previously indicated by opt_space
* parameter. That means the decision to add such option should
* not lost between these two callbacks, e.g. protected by interface
* up state.
*
* void (*prefix_rcv_add_addr)(struct net *net, struct net_device *dev,
* const struct prefix_info *pinfo,
* struct inet6_dev *in6_dev,
* struct in6_addr *addr,
* int addr_type, u32 addr_flags,
* bool sllao, bool tokenized,
* __u32 valid_lft, u32 prefered_lft,
* bool dev_addr_generated):
* This function is called when a RA messages is received with valid
* PIO option fields and an IPv6 address will be added to the interface
* for autoconfiguration. The parameter dev_addr_generated reports about
* if the address was based on dev->dev_addr or not. This can be used
* to add a second address if link-layer operates with two link layer
* addresses. E.g. 802.15.4 6LoWPAN.
*/
struct ndisc_ops {
int (*is_useropt)(u8 nd_opt_type);
int (*parse_options)(const struct net_device *dev,
struct nd_opt_hdr *nd_opt,
struct ndisc_options *ndopts);
void (*update)(const struct net_device *dev, struct neighbour *n,
u32 flags, u8 icmp6_type,
const struct ndisc_options *ndopts);
int (*opt_addr_space)(const struct net_device *dev, u8 icmp6_type,
struct neighbour *neigh, u8 *ha_buf,
u8 **ha);
void (*fill_addr_option)(const struct net_device *dev,
struct sk_buff *skb, u8 icmp6_type,
const u8 *ha);
void (*prefix_rcv_add_addr)(struct net *net, struct net_device *dev,
const struct prefix_info *pinfo,
struct inet6_dev *in6_dev,
struct in6_addr *addr,
int addr_type, u32 addr_flags,
bool sllao, bool tokenized,
__u32 valid_lft, u32 prefered_lft,
bool dev_addr_generated);
};
#if IS_ENABLED(CONFIG_IPV6)
static inline int ndisc_ops_is_useropt(const struct net_device *dev,
u8 nd_opt_type)
{
if (dev->ndisc_ops && dev->ndisc_ops->is_useropt)
return dev->ndisc_ops->is_useropt(nd_opt_type);
else
return 0;
}
static inline int ndisc_ops_parse_options(const struct net_device *dev,
struct nd_opt_hdr *nd_opt,
struct ndisc_options *ndopts)
{
if (dev->ndisc_ops && dev->ndisc_ops->parse_options)
return dev->ndisc_ops->parse_options(dev, nd_opt, ndopts);
else
return 0;
}
static inline void ndisc_ops_update(const struct net_device *dev,
struct neighbour *n, u32 flags,
u8 icmp6_type,
const struct ndisc_options *ndopts)
{
if (dev->ndisc_ops && dev->ndisc_ops->update)
dev->ndisc_ops->update(dev, n, flags, icmp6_type, ndopts);
}
static inline int ndisc_ops_opt_addr_space(const struct net_device *dev,
u8 icmp6_type)
{
if (dev->ndisc_ops && dev->ndisc_ops->opt_addr_space &&
icmp6_type != NDISC_REDIRECT)
return dev->ndisc_ops->opt_addr_space(dev, icmp6_type, NULL,
NULL, NULL);
else
return 0;
}
static inline int ndisc_ops_redirect_opt_addr_space(const struct net_device *dev,
struct neighbour *neigh,
u8 *ha_buf, u8 **ha)
{
if (dev->ndisc_ops && dev->ndisc_ops->opt_addr_space)
return dev->ndisc_ops->opt_addr_space(dev, NDISC_REDIRECT,
neigh, ha_buf, ha);
else
return 0;
}
static inline void ndisc_ops_fill_addr_option(const struct net_device *dev,
struct sk_buff *skb,
u8 icmp6_type)
{
if (dev->ndisc_ops && dev->ndisc_ops->fill_addr_option &&
icmp6_type != NDISC_REDIRECT)
dev->ndisc_ops->fill_addr_option(dev, skb, icmp6_type, NULL);
}
static inline void ndisc_ops_fill_redirect_addr_option(const struct net_device *dev,
struct sk_buff *skb,
const u8 *ha)
{
if (dev->ndisc_ops && dev->ndisc_ops->fill_addr_option)
dev->ndisc_ops->fill_addr_option(dev, skb, NDISC_REDIRECT, ha);
}
static inline void ndisc_ops_prefix_rcv_add_addr(struct net *net,
struct net_device *dev,
const struct prefix_info *pinfo,
struct inet6_dev *in6_dev,
struct in6_addr *addr,
int addr_type, u32 addr_flags,
bool sllao, bool tokenized,
__u32 valid_lft,
u32 prefered_lft,
bool dev_addr_generated)
{
if (dev->ndisc_ops && dev->ndisc_ops->prefix_rcv_add_addr)
dev->ndisc_ops->prefix_rcv_add_addr(net, dev, pinfo, in6_dev,
addr, addr_type,
addr_flags, sllao,
tokenized, valid_lft,
prefered_lft,
dev_addr_generated);
}
#endif
/* /*
* Return the padding between the option length and the start of the * Return the padding between the option length and the start of the
* link addr. Currently only IP-over-InfiniBand needs this, although * link addr. Currently only IP-over-InfiniBand needs this, although
...@@ -127,23 +319,48 @@ static inline int ndisc_addr_option_pad(unsigned short type) ...@@ -127,23 +319,48 @@ static inline int ndisc_addr_option_pad(unsigned short type)
} }
} }
static inline int ndisc_opt_addr_space(struct net_device *dev) static inline int __ndisc_opt_addr_space(unsigned char addr_len, int pad)
{ {
return NDISC_OPT_SPACE(dev->addr_len + return NDISC_OPT_SPACE(addr_len + pad);
ndisc_addr_option_pad(dev->type));
} }
static inline u8 *ndisc_opt_addr_data(struct nd_opt_hdr *p, #if IS_ENABLED(CONFIG_IPV6)
struct net_device *dev) static inline int ndisc_opt_addr_space(struct net_device *dev, u8 icmp6_type)
{
return __ndisc_opt_addr_space(dev->addr_len,
ndisc_addr_option_pad(dev->type)) +
ndisc_ops_opt_addr_space(dev, icmp6_type);
}
static inline int ndisc_redirect_opt_addr_space(struct net_device *dev,
struct neighbour *neigh,
u8 *ops_data_buf,
u8 **ops_data)
{
return __ndisc_opt_addr_space(dev->addr_len,
ndisc_addr_option_pad(dev->type)) +
ndisc_ops_redirect_opt_addr_space(dev, neigh, ops_data_buf,
ops_data);
}
#endif
static inline u8 *__ndisc_opt_addr_data(struct nd_opt_hdr *p,
unsigned char addr_len, int prepad)
{ {
u8 *lladdr = (u8 *)(p + 1); u8 *lladdr = (u8 *)(p + 1);
int lladdrlen = p->nd_opt_len << 3; int lladdrlen = p->nd_opt_len << 3;
int prepad = ndisc_addr_option_pad(dev->type); if (lladdrlen != __ndisc_opt_addr_space(addr_len, prepad))
if (lladdrlen != ndisc_opt_addr_space(dev))
return NULL; return NULL;
return lladdr + prepad; return lladdr + prepad;
} }
static inline u8 *ndisc_opt_addr_data(struct nd_opt_hdr *p,
struct net_device *dev)
{
return __ndisc_opt_addr_data(p, dev->addr_len,
ndisc_addr_option_pad(dev->type));
}
static inline u32 ndisc_hashfn(const void *pkey, const struct net_device *dev, __u32 *hash_rnd) static inline u32 ndisc_hashfn(const void *pkey, const struct net_device *dev, __u32 *hash_rnd)
{ {
const u32 *p32 = pkey; const u32 *p32 = pkey;
...@@ -194,6 +411,9 @@ void ndisc_send_redirect(struct sk_buff *skb, const struct in6_addr *target); ...@@ -194,6 +411,9 @@ void ndisc_send_redirect(struct sk_buff *skb, const struct in6_addr *target);
int ndisc_mc_map(const struct in6_addr *addr, char *buf, struct net_device *dev, int ndisc_mc_map(const struct in6_addr *addr, char *buf, struct net_device *dev,
int dir); int dir);
void ndisc_update(const struct net_device *dev, struct neighbour *neigh,
const u8 *lladdr, u8 new, u32 flags, u8 icmp6_type,
struct ndisc_options *ndopts);
/* /*
* IGMP * IGMP
......
...@@ -12,6 +12,10 @@ static inline bool lowpan_is_ll(const struct net_device *dev, ...@@ -12,6 +12,10 @@ static inline bool lowpan_is_ll(const struct net_device *dev,
return lowpan_dev(dev)->lltype == lltype; return lowpan_dev(dev)->lltype == lltype;
} }
extern const struct ndisc_ops lowpan_ndisc_ops;
int addrconf_ifid_802154_6lowpan(u8 *eui, struct net_device *dev);
#ifdef CONFIG_6LOWPAN_DEBUGFS #ifdef CONFIG_6LOWPAN_DEBUGFS
int lowpan_dev_debugfs_init(struct net_device *dev); int lowpan_dev_debugfs_init(struct net_device *dev);
void lowpan_dev_debugfs_exit(struct net_device *dev); void lowpan_dev_debugfs_exit(struct net_device *dev);
......
obj-$(CONFIG_6LOWPAN) += 6lowpan.o obj-$(CONFIG_6LOWPAN) += 6lowpan.o
6lowpan-y := core.o iphc.o nhc.o 6lowpan-y := core.o iphc.o nhc.o ndisc.o
6lowpan-$(CONFIG_6LOWPAN_DEBUGFS) += debugfs.o 6lowpan-$(CONFIG_6LOWPAN_DEBUGFS) += debugfs.o
#rfc6282 nhcs #rfc6282 nhcs
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include <linux/module.h> #include <linux/module.h>
#include <net/6lowpan.h> #include <net/6lowpan.h>
#include <net/addrconf.h>
#include "6lowpan_i.h" #include "6lowpan_i.h"
...@@ -33,6 +34,8 @@ int lowpan_register_netdevice(struct net_device *dev, ...@@ -33,6 +34,8 @@ int lowpan_register_netdevice(struct net_device *dev,
for (i = 0; i < LOWPAN_IPHC_CTX_TABLE_SIZE; i++) for (i = 0; i < LOWPAN_IPHC_CTX_TABLE_SIZE; i++)
lowpan_dev(dev)->ctx.table[i].id = i; lowpan_dev(dev)->ctx.table[i].id = i;
dev->ndisc_ops = &lowpan_ndisc_ops;
ret = register_netdevice(dev); ret = register_netdevice(dev);
if (ret < 0) if (ret < 0)
return ret; return ret;
...@@ -72,16 +75,61 @@ void lowpan_unregister_netdev(struct net_device *dev) ...@@ -72,16 +75,61 @@ void lowpan_unregister_netdev(struct net_device *dev)
} }
EXPORT_SYMBOL(lowpan_unregister_netdev); EXPORT_SYMBOL(lowpan_unregister_netdev);
int addrconf_ifid_802154_6lowpan(u8 *eui, struct net_device *dev)
{
struct wpan_dev *wpan_dev = lowpan_802154_dev(dev)->wdev->ieee802154_ptr;
/* Set short_addr autoconfiguration if short_addr is present only */
if (!lowpan_802154_is_valid_src_short_addr(wpan_dev->short_addr))
return -1;
/* For either address format, all zero addresses MUST NOT be used */
if (wpan_dev->pan_id == cpu_to_le16(0x0000) &&
wpan_dev->short_addr == cpu_to_le16(0x0000))
return -1;
/* Alternatively, if no PAN ID is known, 16 zero bits may be used */
if (wpan_dev->pan_id == cpu_to_le16(IEEE802154_PAN_ID_BROADCAST))
memset(eui, 0, 2);
else
ieee802154_le16_to_be16(eui, &wpan_dev->pan_id);
/* The "Universal/Local" (U/L) bit shall be set to zero */
eui[0] &= ~2;
eui[2] = 0;
eui[3] = 0xFF;
eui[4] = 0xFE;
eui[5] = 0;
ieee802154_le16_to_be16(&eui[6], &wpan_dev->short_addr);
return 0;
}
static int lowpan_event(struct notifier_block *unused, static int lowpan_event(struct notifier_block *unused,
unsigned long event, void *ptr) unsigned long event, void *ptr)
{ {
struct net_device *dev = netdev_notifier_info_to_dev(ptr); struct net_device *dev = netdev_notifier_info_to_dev(ptr);
struct inet6_dev *idev;
struct in6_addr addr;
int i; int i;
if (dev->type != ARPHRD_6LOWPAN) if (dev->type != ARPHRD_6LOWPAN)
return NOTIFY_DONE; return NOTIFY_DONE;
idev = __in6_dev_get(dev);
if (!idev)
return NOTIFY_DONE;
switch (event) { switch (event) {
case NETDEV_UP:
case NETDEV_CHANGE:
/* (802.15.4 6LoWPAN short address slaac handling */
if (lowpan_is_ll(dev, LOWPAN_LLTYPE_IEEE802154) &&
addrconf_ifid_802154_6lowpan(addr.s6_addr + 8, dev) == 0) {
__ipv6_addr_set_half(&addr.s6_addr32[0],
htonl(0xFE800000), 0);
addrconf_add_linklocal(idev, &addr, 0);
}
break;
case NETDEV_DOWN: case NETDEV_DOWN:
for (i = 0; i < LOWPAN_IPHC_CTX_TABLE_SIZE; i++) for (i = 0; i < LOWPAN_IPHC_CTX_TABLE_SIZE; i++)
clear_bit(LOWPAN_IPHC_CTX_FLAG_ACTIVE, clear_bit(LOWPAN_IPHC_CTX_FLAG_ACTIVE,
...@@ -112,8 +160,6 @@ static int __init lowpan_module_init(void) ...@@ -112,8 +160,6 @@ static int __init lowpan_module_init(void)
return ret; return ret;
} }
request_module_nowait("ipv6");
request_module_nowait("nhc_dest"); request_module_nowait("nhc_dest");
request_module_nowait("nhc_fragment"); request_module_nowait("nhc_fragment");
request_module_nowait("nhc_hop"); request_module_nowait("nhc_hop");
......
...@@ -245,6 +245,41 @@ static const struct file_operations lowpan_context_fops = { ...@@ -245,6 +245,41 @@ static const struct file_operations lowpan_context_fops = {
.release = single_release, .release = single_release,
}; };
static int lowpan_short_addr_get(void *data, u64 *val)
{
struct wpan_dev *wdev = data;
rtnl_lock();
*val = le16_to_cpu(wdev->short_addr);
rtnl_unlock();
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(lowpan_short_addr_fops, lowpan_short_addr_get,
NULL, "0x%04llx\n");
static int lowpan_dev_debugfs_802154_init(const struct net_device *dev,
struct lowpan_dev *ldev)
{
struct dentry *dentry, *root;
if (!lowpan_is_ll(dev, LOWPAN_LLTYPE_IEEE802154))
return 0;
root = debugfs_create_dir("ieee802154", ldev->iface_debugfs);
if (!root)
return -EINVAL;
dentry = debugfs_create_file("short_addr", 0444, root,
lowpan_802154_dev(dev)->wdev->ieee802154_ptr,
&lowpan_short_addr_fops);
if (!dentry)
return -EINVAL;
return 0;
}
int lowpan_dev_debugfs_init(struct net_device *dev) int lowpan_dev_debugfs_init(struct net_device *dev)
{ {
struct lowpan_dev *ldev = lowpan_dev(dev); struct lowpan_dev *ldev = lowpan_dev(dev);
...@@ -272,6 +307,10 @@ int lowpan_dev_debugfs_init(struct net_device *dev) ...@@ -272,6 +307,10 @@ int lowpan_dev_debugfs_init(struct net_device *dev)
goto remove_root; goto remove_root;
} }
ret = lowpan_dev_debugfs_802154_init(dev, ldev);
if (ret < 0)
goto remove_root;
return 0; return 0;
remove_root: remove_root:
......
...@@ -761,22 +761,75 @@ static const u8 lowpan_iphc_dam_to_sam_value[] = { ...@@ -761,22 +761,75 @@ static const u8 lowpan_iphc_dam_to_sam_value[] = {
[LOWPAN_IPHC_DAM_11] = LOWPAN_IPHC_SAM_11, [LOWPAN_IPHC_DAM_11] = LOWPAN_IPHC_SAM_11,
}; };
static u8 lowpan_compress_ctx_addr(u8 **hc_ptr, const struct in6_addr *ipaddr, static inline bool
lowpan_iphc_compress_ctx_802154_lladdr(const struct in6_addr *ipaddr,
const struct lowpan_iphc_ctx *ctx,
const void *lladdr)
{
const struct ieee802154_addr *addr = lladdr;
unsigned char extended_addr[EUI64_ADDR_LEN];
bool lladdr_compress = false;
struct in6_addr tmp = {};
switch (addr->mode) {
case IEEE802154_ADDR_LONG:
ieee802154_le64_to_be64(&extended_addr, &addr->extended_addr);
/* check for SAM/DAM = 11 */
memcpy(&tmp.s6_addr[8], &extended_addr, EUI64_ADDR_LEN);
/* second bit-flip (Universe/Local) is done according RFC2464 */
tmp.s6_addr[8] ^= 0x02;
/* context information are always used */
ipv6_addr_prefix_copy(&tmp, &ctx->pfx, ctx->plen);
if (ipv6_addr_equal(&tmp, ipaddr))
lladdr_compress = true;
break;
case IEEE802154_ADDR_SHORT:
tmp.s6_addr[11] = 0xFF;
tmp.s6_addr[12] = 0xFE;
ieee802154_le16_to_be16(&tmp.s6_addr16[7],
&addr->short_addr);
/* context information are always used */
ipv6_addr_prefix_copy(&tmp, &ctx->pfx, ctx->plen);
if (ipv6_addr_equal(&tmp, ipaddr))
lladdr_compress = true;
break;
default:
/* should never handled and filtered by 802154 6lowpan */
WARN_ON_ONCE(1);
break;
}
return lladdr_compress;
}
static u8 lowpan_compress_ctx_addr(u8 **hc_ptr, const struct net_device *dev,
const struct in6_addr *ipaddr,
const struct lowpan_iphc_ctx *ctx, const struct lowpan_iphc_ctx *ctx,
const unsigned char *lladdr, bool sam) const unsigned char *lladdr, bool sam)
{ {
struct in6_addr tmp = {}; struct in6_addr tmp = {};
u8 dam; u8 dam;
/* check for SAM/DAM = 11 */ switch (lowpan_dev(dev)->lltype) {
memcpy(&tmp.s6_addr[8], lladdr, 8); case LOWPAN_LLTYPE_IEEE802154:
/* second bit-flip (Universe/Local) is done according RFC2464 */ if (lowpan_iphc_compress_ctx_802154_lladdr(ipaddr, ctx,
tmp.s6_addr[8] ^= 0x02; lladdr)) {
/* context information are always used */ dam = LOWPAN_IPHC_DAM_11;
ipv6_addr_prefix_copy(&tmp, &ctx->pfx, ctx->plen); goto out;
if (ipv6_addr_equal(&tmp, ipaddr)) { }
dam = LOWPAN_IPHC_DAM_11; break;
goto out; default:
/* check for SAM/DAM = 11 */
memcpy(&tmp.s6_addr[8], lladdr, EUI64_ADDR_LEN);
/* second bit-flip (Universe/Local) is done according RFC2464 */
tmp.s6_addr[8] ^= 0x02;
/* context information are always used */
ipv6_addr_prefix_copy(&tmp, &ctx->pfx, ctx->plen);
if (ipv6_addr_equal(&tmp, ipaddr)) {
dam = LOWPAN_IPHC_DAM_11;
goto out;
}
break;
} }
memset(&tmp, 0, sizeof(tmp)); memset(&tmp, 0, sizeof(tmp));
...@@ -813,28 +866,85 @@ static u8 lowpan_compress_ctx_addr(u8 **hc_ptr, const struct in6_addr *ipaddr, ...@@ -813,28 +866,85 @@ static u8 lowpan_compress_ctx_addr(u8 **hc_ptr, const struct in6_addr *ipaddr,
return dam; return dam;
} }
static u8 lowpan_compress_addr_64(u8 **hc_ptr, const struct in6_addr *ipaddr, static inline bool
lowpan_iphc_compress_802154_lladdr(const struct in6_addr *ipaddr,
const void *lladdr)
{
const struct ieee802154_addr *addr = lladdr;
unsigned char extended_addr[EUI64_ADDR_LEN];
bool lladdr_compress = false;
struct in6_addr tmp = {};
switch (addr->mode) {
case IEEE802154_ADDR_LONG:
ieee802154_le64_to_be64(&extended_addr, &addr->extended_addr);
if (is_addr_mac_addr_based(ipaddr, extended_addr))
lladdr_compress = true;
break;
case IEEE802154_ADDR_SHORT:
/* fe:80::ff:fe00:XXXX
* \__/
* short_addr
*
* Universe/Local bit is zero.
*/
tmp.s6_addr[0] = 0xFE;
tmp.s6_addr[1] = 0x80;
tmp.s6_addr[11] = 0xFF;
tmp.s6_addr[12] = 0xFE;
ieee802154_le16_to_be16(&tmp.s6_addr16[7],
&addr->short_addr);
if (ipv6_addr_equal(&tmp, ipaddr))
lladdr_compress = true;
break;
default:
/* should never handled and filtered by 802154 6lowpan */
WARN_ON_ONCE(1);
break;
}
return lladdr_compress;
}
static u8 lowpan_compress_addr_64(u8 **hc_ptr, const struct net_device *dev,
const struct in6_addr *ipaddr,
const unsigned char *lladdr, bool sam) const unsigned char *lladdr, bool sam)
{ {
u8 dam = LOWPAN_IPHC_DAM_00; u8 dam = LOWPAN_IPHC_DAM_01;
if (is_addr_mac_addr_based(ipaddr, lladdr)) { switch (lowpan_dev(dev)->lltype) {
dam = LOWPAN_IPHC_DAM_11; /* 0-bits */ case LOWPAN_LLTYPE_IEEE802154:
pr_debug("address compression 0 bits\n"); if (lowpan_iphc_compress_802154_lladdr(ipaddr, lladdr)) {
} else if (lowpan_is_iid_16_bit_compressable(ipaddr)) { dam = LOWPAN_IPHC_DAM_11; /* 0-bits */
pr_debug("address compression 0 bits\n");
goto out;
}
break;
default:
if (is_addr_mac_addr_based(ipaddr, lladdr)) {
dam = LOWPAN_IPHC_DAM_11; /* 0-bits */
pr_debug("address compression 0 bits\n");
goto out;
}
break;
}
if (lowpan_is_iid_16_bit_compressable(ipaddr)) {
/* compress IID to 16 bits xxxx::XXXX */ /* compress IID to 16 bits xxxx::XXXX */
lowpan_push_hc_data(hc_ptr, &ipaddr->s6_addr16[7], 2); lowpan_push_hc_data(hc_ptr, &ipaddr->s6_addr16[7], 2);
dam = LOWPAN_IPHC_DAM_10; /* 16-bits */ dam = LOWPAN_IPHC_DAM_10; /* 16-bits */
raw_dump_inline(NULL, "Compressed ipv6 addr is (16 bits)", raw_dump_inline(NULL, "Compressed ipv6 addr is (16 bits)",
*hc_ptr - 2, 2); *hc_ptr - 2, 2);
} else { goto out;
/* do not compress IID => xxxx::IID */
lowpan_push_hc_data(hc_ptr, &ipaddr->s6_addr16[4], 8);
dam = LOWPAN_IPHC_DAM_01; /* 64-bits */
raw_dump_inline(NULL, "Compressed ipv6 addr is (64 bits)",
*hc_ptr - 8, 8);
} }
/* do not compress IID => xxxx::IID */
lowpan_push_hc_data(hc_ptr, &ipaddr->s6_addr16[4], 8);
raw_dump_inline(NULL, "Compressed ipv6 addr is (64 bits)",
*hc_ptr - 8, 8);
out:
if (sam) if (sam)
return lowpan_iphc_dam_to_sam_value[dam]; return lowpan_iphc_dam_to_sam_value[dam];
else else
...@@ -1013,9 +1123,6 @@ int lowpan_header_compress(struct sk_buff *skb, const struct net_device *dev, ...@@ -1013,9 +1123,6 @@ int lowpan_header_compress(struct sk_buff *skb, const struct net_device *dev,
iphc0 = LOWPAN_DISPATCH_IPHC; iphc0 = LOWPAN_DISPATCH_IPHC;
iphc1 = 0; iphc1 = 0;
raw_dump_inline(__func__, "saddr", saddr, EUI64_ADDR_LEN);
raw_dump_inline(__func__, "daddr", daddr, EUI64_ADDR_LEN);
raw_dump_table(__func__, "sending raw skb network uncompressed packet", raw_dump_table(__func__, "sending raw skb network uncompressed packet",
skb->data, skb->len); skb->data, skb->len);
...@@ -1088,14 +1195,15 @@ int lowpan_header_compress(struct sk_buff *skb, const struct net_device *dev, ...@@ -1088,14 +1195,15 @@ int lowpan_header_compress(struct sk_buff *skb, const struct net_device *dev,
iphc1 |= LOWPAN_IPHC_SAC; iphc1 |= LOWPAN_IPHC_SAC;
} else { } else {
if (sci) { if (sci) {
iphc1 |= lowpan_compress_ctx_addr(&hc_ptr, &hdr->saddr, iphc1 |= lowpan_compress_ctx_addr(&hc_ptr, dev,
&hdr->saddr,
&sci_entry, saddr, &sci_entry, saddr,
true); true);
iphc1 |= LOWPAN_IPHC_SAC; iphc1 |= LOWPAN_IPHC_SAC;
} else { } else {
if (ipv6_saddr_type & IPV6_ADDR_LINKLOCAL && if (ipv6_saddr_type & IPV6_ADDR_LINKLOCAL &&
lowpan_is_linklocal_zero_padded(hdr->saddr)) { lowpan_is_linklocal_zero_padded(hdr->saddr)) {
iphc1 |= lowpan_compress_addr_64(&hc_ptr, iphc1 |= lowpan_compress_addr_64(&hc_ptr, dev,
&hdr->saddr, &hdr->saddr,
saddr, true); saddr, true);
pr_debug("source address unicast link-local %pI6c iphc1 0x%02x\n", pr_debug("source address unicast link-local %pI6c iphc1 0x%02x\n",
...@@ -1123,14 +1231,15 @@ int lowpan_header_compress(struct sk_buff *skb, const struct net_device *dev, ...@@ -1123,14 +1231,15 @@ int lowpan_header_compress(struct sk_buff *skb, const struct net_device *dev,
} }
} else { } else {
if (dci) { if (dci) {
iphc1 |= lowpan_compress_ctx_addr(&hc_ptr, &hdr->daddr, iphc1 |= lowpan_compress_ctx_addr(&hc_ptr, dev,
&hdr->daddr,
&dci_entry, daddr, &dci_entry, daddr,
false); false);
iphc1 |= LOWPAN_IPHC_DAC; iphc1 |= LOWPAN_IPHC_DAC;
} else { } else {
if (ipv6_daddr_type & IPV6_ADDR_LINKLOCAL && if (ipv6_daddr_type & IPV6_ADDR_LINKLOCAL &&
lowpan_is_linklocal_zero_padded(hdr->daddr)) { lowpan_is_linklocal_zero_padded(hdr->daddr)) {
iphc1 |= lowpan_compress_addr_64(&hc_ptr, iphc1 |= lowpan_compress_addr_64(&hc_ptr, dev,
&hdr->daddr, &hdr->daddr,
daddr, false); daddr, false);
pr_debug("dest address unicast link-local %pI6c iphc1 0x%02x\n", pr_debug("dest address unicast link-local %pI6c iphc1 0x%02x\n",
......
/* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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.
*
* Authors:
* (C) 2016 Pengutronix, Alexander Aring <aar@pengutronix.de>
*/
#include <net/6lowpan.h>
#include <net/addrconf.h>
#include <net/ndisc.h>
#include "6lowpan_i.h"
static int lowpan_ndisc_is_useropt(u8 nd_opt_type)
{
return nd_opt_type == ND_OPT_6CO;
}
#if IS_ENABLED(CONFIG_IEEE802154_6LOWPAN)
#define NDISC_802154_SHORT_ADDR_LENGTH 1
static int lowpan_ndisc_parse_802154_options(const struct net_device *dev,
struct nd_opt_hdr *nd_opt,
struct ndisc_options *ndopts)
{
switch (nd_opt->nd_opt_len) {
case NDISC_802154_SHORT_ADDR_LENGTH:
if (ndopts->nd_802154_opt_array[nd_opt->nd_opt_type])
ND_PRINTK(2, warn,
"%s: duplicated short addr ND6 option found: type=%d\n",
__func__, nd_opt->nd_opt_type);
else
ndopts->nd_802154_opt_array[nd_opt->nd_opt_type] = nd_opt;
return 1;
default:
/* all others will be handled by ndisc IPv6 option parsing */
return 0;
}
}
static int lowpan_ndisc_parse_options(const struct net_device *dev,
struct nd_opt_hdr *nd_opt,
struct ndisc_options *ndopts)
{
switch (nd_opt->nd_opt_type) {
case ND_OPT_SOURCE_LL_ADDR:
case ND_OPT_TARGET_LL_ADDR:
return lowpan_ndisc_parse_802154_options(dev, nd_opt, ndopts);
default:
return 0;
}
}
static void lowpan_ndisc_802154_update(struct neighbour *n, u32 flags,
u8 icmp6_type,
const struct ndisc_options *ndopts)
{
struct lowpan_802154_neigh *neigh = lowpan_802154_neigh(neighbour_priv(n));
u8 *lladdr_short = NULL;
switch (icmp6_type) {
case NDISC_ROUTER_SOLICITATION:
case NDISC_ROUTER_ADVERTISEMENT:
case NDISC_NEIGHBOUR_SOLICITATION:
if (ndopts->nd_802154_opts_src_lladdr) {
lladdr_short = __ndisc_opt_addr_data(ndopts->nd_802154_opts_src_lladdr,
IEEE802154_SHORT_ADDR_LEN, 0);
if (!lladdr_short) {
ND_PRINTK(2, warn,
"NA: invalid short link-layer address length\n");
return;
}
}
break;
case NDISC_REDIRECT:
case NDISC_NEIGHBOUR_ADVERTISEMENT:
if (ndopts->nd_802154_opts_tgt_lladdr) {
lladdr_short = __ndisc_opt_addr_data(ndopts->nd_802154_opts_tgt_lladdr,
IEEE802154_SHORT_ADDR_LEN, 0);
if (!lladdr_short) {
ND_PRINTK(2, warn,
"NA: invalid short link-layer address length\n");
return;
}
}
break;
default:
break;
}
write_lock_bh(&n->lock);
if (lladdr_short)
ieee802154_be16_to_le16(&neigh->short_addr, lladdr_short);
else
neigh->short_addr = cpu_to_le16(IEEE802154_ADDR_SHORT_UNSPEC);
write_unlock_bh(&n->lock);
}
static void lowpan_ndisc_update(const struct net_device *dev,
struct neighbour *n, u32 flags, u8 icmp6_type,
const struct ndisc_options *ndopts)
{
if (!lowpan_is_ll(dev, LOWPAN_LLTYPE_IEEE802154))
return;
/* react on overrides only. TODO check if this is really right. */
if (flags & NEIGH_UPDATE_F_OVERRIDE)
lowpan_ndisc_802154_update(n, flags, icmp6_type, ndopts);
}
static int lowpan_ndisc_opt_addr_space(const struct net_device *dev,
u8 icmp6_type, struct neighbour *neigh,
u8 *ha_buf, u8 **ha)
{
struct lowpan_802154_neigh *n;
struct wpan_dev *wpan_dev;
int addr_space = 0;
if (!lowpan_is_ll(dev, LOWPAN_LLTYPE_IEEE802154))
return 0;
switch (icmp6_type) {
case NDISC_REDIRECT:
n = lowpan_802154_neigh(neighbour_priv(neigh));
read_lock_bh(&neigh->lock);
if (lowpan_802154_is_valid_src_short_addr(n->short_addr)) {
memcpy(ha_buf, &n->short_addr,
IEEE802154_SHORT_ADDR_LEN);
read_unlock_bh(&neigh->lock);
addr_space += __ndisc_opt_addr_space(IEEE802154_SHORT_ADDR_LEN, 0);
*ha = ha_buf;
}
read_unlock_bh(&neigh->lock);
break;
case NDISC_NEIGHBOUR_ADVERTISEMENT:
case NDISC_NEIGHBOUR_SOLICITATION:
case NDISC_ROUTER_SOLICITATION:
wpan_dev = lowpan_802154_dev(dev)->wdev->ieee802154_ptr;
if (lowpan_802154_is_valid_src_short_addr(wpan_dev->short_addr))
addr_space = __ndisc_opt_addr_space(IEEE802154_SHORT_ADDR_LEN, 0);
break;
default:
break;
}
return addr_space;
}
static void lowpan_ndisc_fill_addr_option(const struct net_device *dev,
struct sk_buff *skb, u8 icmp6_type,
const u8 *ha)
{
struct wpan_dev *wpan_dev;
__be16 short_addr;
u8 opt_type;
if (!lowpan_is_ll(dev, LOWPAN_LLTYPE_IEEE802154))
return;
switch (icmp6_type) {
case NDISC_REDIRECT:
if (ha) {
ieee802154_le16_to_be16(&short_addr, ha);
__ndisc_fill_addr_option(skb, ND_OPT_TARGET_LL_ADDR,
&short_addr,
IEEE802154_SHORT_ADDR_LEN, 0);
}
return;
case NDISC_NEIGHBOUR_ADVERTISEMENT:
opt_type = ND_OPT_TARGET_LL_ADDR;
break;
case NDISC_ROUTER_SOLICITATION:
case NDISC_NEIGHBOUR_SOLICITATION:
opt_type = ND_OPT_SOURCE_LL_ADDR;
break;
default:
return;
}
wpan_dev = lowpan_802154_dev(dev)->wdev->ieee802154_ptr;
if (lowpan_802154_is_valid_src_short_addr(wpan_dev->short_addr)) {
ieee802154_le16_to_be16(&short_addr,
&wpan_dev->short_addr);
__ndisc_fill_addr_option(skb, opt_type, &short_addr,
IEEE802154_SHORT_ADDR_LEN, 0);
}
}
static void lowpan_ndisc_prefix_rcv_add_addr(struct net *net,
struct net_device *dev,
const struct prefix_info *pinfo,
struct inet6_dev *in6_dev,
struct in6_addr *addr,
int addr_type, u32 addr_flags,
bool sllao, bool tokenized,
__u32 valid_lft,
u32 prefered_lft,
bool dev_addr_generated)
{
int err;
/* generates short based address for RA PIO's */
if (lowpan_is_ll(dev, LOWPAN_LLTYPE_IEEE802154) && dev_addr_generated &&
!addrconf_ifid_802154_6lowpan(addr->s6_addr + 8, dev)) {
err = addrconf_prefix_rcv_add_addr(net, dev, pinfo, in6_dev,
addr, addr_type, addr_flags,
sllao, tokenized, valid_lft,
prefered_lft);
if (err)
ND_PRINTK(2, warn,
"RA: could not add a short address based address for prefix: %pI6c\n",
&pinfo->prefix);
}
}
#endif
const struct ndisc_ops lowpan_ndisc_ops = {
.is_useropt = lowpan_ndisc_is_useropt,
#if IS_ENABLED(CONFIG_IEEE802154_6LOWPAN)
.parse_options = lowpan_ndisc_parse_options,
.update = lowpan_ndisc_update,
.opt_addr_space = lowpan_ndisc_opt_addr_space,
.fill_addr_option = lowpan_ndisc_fill_addr_option,
.prefix_rcv_add_addr = lowpan_ndisc_prefix_rcv_add_addr,
#endif
};
...@@ -81,11 +81,21 @@ static int lowpan_stop(struct net_device *dev) ...@@ -81,11 +81,21 @@ static int lowpan_stop(struct net_device *dev)
return 0; return 0;
} }
static int lowpan_neigh_construct(struct neighbour *n)
{
struct lowpan_802154_neigh *neigh = lowpan_802154_neigh(neighbour_priv(n));
/* default no short_addr is available for a neighbour */
neigh->short_addr = cpu_to_le16(IEEE802154_ADDR_SHORT_UNSPEC);
return 0;
}
static const struct net_device_ops lowpan_netdev_ops = { static const struct net_device_ops lowpan_netdev_ops = {
.ndo_init = lowpan_dev_init, .ndo_init = lowpan_dev_init,
.ndo_start_xmit = lowpan_xmit, .ndo_start_xmit = lowpan_xmit,
.ndo_open = lowpan_open, .ndo_open = lowpan_open,
.ndo_stop = lowpan_stop, .ndo_stop = lowpan_stop,
.ndo_neigh_construct = lowpan_neigh_construct,
}; };
static void lowpan_setup(struct net_device *ldev) static void lowpan_setup(struct net_device *ldev)
...@@ -150,6 +160,8 @@ static int lowpan_newlink(struct net *src_net, struct net_device *ldev, ...@@ -150,6 +160,8 @@ static int lowpan_newlink(struct net *src_net, struct net_device *ldev,
wdev->needed_headroom; wdev->needed_headroom;
ldev->needed_tailroom = wdev->needed_tailroom; ldev->needed_tailroom = wdev->needed_tailroom;
ldev->neigh_priv_len = sizeof(struct lowpan_802154_neigh);
ret = lowpan_register_netdevice(ldev, LOWPAN_LLTYPE_IEEE802154); ret = lowpan_register_netdevice(ldev, LOWPAN_LLTYPE_IEEE802154);
if (ret < 0) { if (ret < 0) {
dev_put(wdev); dev_put(wdev);
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
*/ */
#include <net/6lowpan.h> #include <net/6lowpan.h>
#include <net/ndisc.h>
#include <net/ieee802154_netdev.h> #include <net/ieee802154_netdev.h>
#include <net/mac802154.h> #include <net/mac802154.h>
...@@ -17,19 +18,9 @@ ...@@ -17,19 +18,9 @@
#define LOWPAN_FRAG1_HEAD_SIZE 0x4 #define LOWPAN_FRAG1_HEAD_SIZE 0x4
#define LOWPAN_FRAGN_HEAD_SIZE 0x5 #define LOWPAN_FRAGN_HEAD_SIZE 0x5
/* don't save pan id, it's intra pan */
struct lowpan_addr {
u8 mode;
union {
/* IPv6 needs big endian here */
__be64 extended_addr;
__be16 short_addr;
} u;
};
struct lowpan_addr_info { struct lowpan_addr_info {
struct lowpan_addr daddr; struct ieee802154_addr daddr;
struct lowpan_addr saddr; struct ieee802154_addr saddr;
}; };
static inline struct static inline struct
...@@ -48,12 +39,14 @@ lowpan_addr_info *lowpan_skb_priv(const struct sk_buff *skb) ...@@ -48,12 +39,14 @@ lowpan_addr_info *lowpan_skb_priv(const struct sk_buff *skb)
* RAW/DGRAM sockets. * RAW/DGRAM sockets.
*/ */
int lowpan_header_create(struct sk_buff *skb, struct net_device *ldev, int lowpan_header_create(struct sk_buff *skb, struct net_device *ldev,
unsigned short type, const void *_daddr, unsigned short type, const void *daddr,
const void *_saddr, unsigned int len) const void *saddr, unsigned int len)
{ {
const u8 *saddr = _saddr; struct wpan_dev *wpan_dev = lowpan_802154_dev(ldev)->wdev->ieee802154_ptr;
const u8 *daddr = _daddr; struct lowpan_addr_info *info = lowpan_skb_priv(skb);
struct lowpan_addr_info *info; struct lowpan_802154_neigh *llneigh = NULL;
const struct ipv6hdr *hdr = ipv6_hdr(skb);
struct neighbour *n;
/* TODO: /* TODO:
* if this package isn't ipv6 one, where should it be routed? * if this package isn't ipv6 one, where should it be routed?
...@@ -61,21 +54,50 @@ int lowpan_header_create(struct sk_buff *skb, struct net_device *ldev, ...@@ -61,21 +54,50 @@ int lowpan_header_create(struct sk_buff *skb, struct net_device *ldev,
if (type != ETH_P_IPV6) if (type != ETH_P_IPV6)
return 0; return 0;
if (!saddr) /* intra-pan communication */
saddr = ldev->dev_addr; info->saddr.pan_id = wpan_dev->pan_id;
info->daddr.pan_id = info->saddr.pan_id;
raw_dump_inline(__func__, "saddr", (unsigned char *)saddr, 8); if (!memcmp(daddr, ldev->broadcast, EUI64_ADDR_LEN)) {
raw_dump_inline(__func__, "daddr", (unsigned char *)daddr, 8); info->daddr.short_addr = cpu_to_le16(IEEE802154_ADDR_BROADCAST);
info->daddr.mode = IEEE802154_ADDR_SHORT;
} else {
__le16 short_addr = cpu_to_le16(IEEE802154_ADDR_SHORT_UNSPEC);
n = neigh_lookup(&nd_tbl, &hdr->daddr, ldev);
if (n) {
llneigh = lowpan_802154_neigh(neighbour_priv(n));
read_lock_bh(&n->lock);
short_addr = llneigh->short_addr;
read_unlock_bh(&n->lock);
}
info = lowpan_skb_priv(skb); if (llneigh &&
lowpan_802154_is_valid_src_short_addr(short_addr)) {
info->daddr.short_addr = short_addr;
info->daddr.mode = IEEE802154_ADDR_SHORT;
} else {
info->daddr.mode = IEEE802154_ADDR_LONG;
ieee802154_be64_to_le64(&info->daddr.extended_addr,
daddr);
}
/* TODO: Currently we only support extended_addr */ if (n)
info->daddr.mode = IEEE802154_ADDR_LONG; neigh_release(n);
memcpy(&info->daddr.u.extended_addr, daddr, }
sizeof(info->daddr.u.extended_addr));
info->saddr.mode = IEEE802154_ADDR_LONG; if (!saddr) {
memcpy(&info->saddr.u.extended_addr, saddr, if (lowpan_802154_is_valid_src_short_addr(wpan_dev->short_addr)) {
sizeof(info->daddr.u.extended_addr)); info->saddr.mode = IEEE802154_ADDR_SHORT;
info->saddr.short_addr = wpan_dev->short_addr;
} else {
info->saddr.mode = IEEE802154_ADDR_LONG;
info->saddr.extended_addr = wpan_dev->extended_addr;
}
} else {
info->saddr.mode = IEEE802154_ADDR_LONG;
ieee802154_be64_to_le64(&info->saddr.extended_addr, saddr);
}
return 0; return 0;
} }
...@@ -209,47 +231,26 @@ static int lowpan_header(struct sk_buff *skb, struct net_device *ldev, ...@@ -209,47 +231,26 @@ static int lowpan_header(struct sk_buff *skb, struct net_device *ldev,
u16 *dgram_size, u16 *dgram_offset) u16 *dgram_size, u16 *dgram_offset)
{ {
struct wpan_dev *wpan_dev = lowpan_802154_dev(ldev)->wdev->ieee802154_ptr; struct wpan_dev *wpan_dev = lowpan_802154_dev(ldev)->wdev->ieee802154_ptr;
struct ieee802154_addr sa, da;
struct ieee802154_mac_cb *cb = mac_cb_init(skb); struct ieee802154_mac_cb *cb = mac_cb_init(skb);
struct lowpan_addr_info info; struct lowpan_addr_info info;
void *daddr, *saddr;
memcpy(&info, lowpan_skb_priv(skb), sizeof(info)); memcpy(&info, lowpan_skb_priv(skb), sizeof(info));
/* TODO: Currently we only support extended_addr */
daddr = &info.daddr.u.extended_addr;
saddr = &info.saddr.u.extended_addr;
*dgram_size = skb->len; *dgram_size = skb->len;
lowpan_header_compress(skb, ldev, daddr, saddr); lowpan_header_compress(skb, ldev, &info.daddr, &info.saddr);
/* dgram_offset = (saved bytes after compression) + lowpan header len */ /* dgram_offset = (saved bytes after compression) + lowpan header len */
*dgram_offset = (*dgram_size - skb->len) + skb_network_header_len(skb); *dgram_offset = (*dgram_size - skb->len) + skb_network_header_len(skb);
cb->type = IEEE802154_FC_TYPE_DATA; cb->type = IEEE802154_FC_TYPE_DATA;
/* prepare wpan address data */ if (info.daddr.mode == IEEE802154_ADDR_SHORT &&
sa.mode = IEEE802154_ADDR_LONG; ieee802154_is_broadcast_short_addr(info.daddr.short_addr))
sa.pan_id = wpan_dev->pan_id;
sa.extended_addr = ieee802154_devaddr_from_raw(saddr);
/* intra-PAN communications */
da.pan_id = sa.pan_id;
/* if the destination address is the broadcast address, use the
* corresponding short address
*/
if (!memcmp(daddr, ldev->broadcast, EUI64_ADDR_LEN)) {
da.mode = IEEE802154_ADDR_SHORT;
da.short_addr = cpu_to_le16(IEEE802154_ADDR_BROADCAST);
cb->ackreq = false; cb->ackreq = false;
} else { else
da.mode = IEEE802154_ADDR_LONG;
da.extended_addr = ieee802154_devaddr_from_raw(daddr);
cb->ackreq = wpan_dev->ackreq; cb->ackreq = wpan_dev->ackreq;
}
return wpan_dev_hard_header(skb, lowpan_802154_dev(ldev)->wdev, &da, return wpan_dev_hard_header(skb, lowpan_802154_dev(ldev)->wdev,
&sa, 0); &info.daddr, &info.saddr, 0);
} }
netdev_tx_t lowpan_xmit(struct sk_buff *skb, struct net_device *ldev) netdev_tx_t lowpan_xmit(struct sk_buff *skb, struct net_device *ldev)
......
...@@ -2333,12 +2333,109 @@ static bool is_addr_mode_generate_stable(struct inet6_dev *idev) ...@@ -2333,12 +2333,109 @@ static bool is_addr_mode_generate_stable(struct inet6_dev *idev)
idev->addr_gen_mode == IN6_ADDR_GEN_MODE_RANDOM; idev->addr_gen_mode == IN6_ADDR_GEN_MODE_RANDOM;
} }
int addrconf_prefix_rcv_add_addr(struct net *net, struct net_device *dev,
const struct prefix_info *pinfo,
struct inet6_dev *in6_dev,
const struct in6_addr *addr, int addr_type,
u32 addr_flags, bool sllao, bool tokenized,
__u32 valid_lft, u32 prefered_lft)
{
struct inet6_ifaddr *ifp = ipv6_get_ifaddr(net, addr, dev, 1);
int create = 0, update_lft = 0;
if (!ifp && valid_lft) {
int max_addresses = in6_dev->cnf.max_addresses;
#ifdef CONFIG_IPV6_OPTIMISTIC_DAD
if (in6_dev->cnf.optimistic_dad &&
!net->ipv6.devconf_all->forwarding && sllao)
addr_flags |= IFA_F_OPTIMISTIC;
#endif
/* Do not allow to create too much of autoconfigured
* addresses; this would be too easy way to crash kernel.
*/
if (!max_addresses ||
ipv6_count_addresses(in6_dev) < max_addresses)
ifp = ipv6_add_addr(in6_dev, addr, NULL,
pinfo->prefix_len,
addr_type&IPV6_ADDR_SCOPE_MASK,
addr_flags, valid_lft,
prefered_lft);
if (IS_ERR_OR_NULL(ifp))
return -1;
update_lft = 0;
create = 1;
spin_lock_bh(&ifp->lock);
ifp->flags |= IFA_F_MANAGETEMPADDR;
ifp->cstamp = jiffies;
ifp->tokenized = tokenized;
spin_unlock_bh(&ifp->lock);
addrconf_dad_start(ifp);
}
if (ifp) {
u32 flags;
unsigned long now;
u32 stored_lft;
/* update lifetime (RFC2462 5.5.3 e) */
spin_lock_bh(&ifp->lock);
now = jiffies;
if (ifp->valid_lft > (now - ifp->tstamp) / HZ)
stored_lft = ifp->valid_lft - (now - ifp->tstamp) / HZ;
else
stored_lft = 0;
if (!update_lft && !create && stored_lft) {
const u32 minimum_lft = min_t(u32,
stored_lft, MIN_VALID_LIFETIME);
valid_lft = max(valid_lft, minimum_lft);
/* RFC4862 Section 5.5.3e:
* "Note that the preferred lifetime of the
* corresponding address is always reset to
* the Preferred Lifetime in the received
* Prefix Information option, regardless of
* whether the valid lifetime is also reset or
* ignored."
*
* So we should always update prefered_lft here.
*/
update_lft = 1;
}
if (update_lft) {
ifp->valid_lft = valid_lft;
ifp->prefered_lft = prefered_lft;
ifp->tstamp = now;
flags = ifp->flags;
ifp->flags &= ~IFA_F_DEPRECATED;
spin_unlock_bh(&ifp->lock);
if (!(flags&IFA_F_TENTATIVE))
ipv6_ifa_notify(0, ifp);
} else
spin_unlock_bh(&ifp->lock);
manage_tempaddrs(in6_dev, ifp, valid_lft, prefered_lft,
create, now);
in6_ifa_put(ifp);
addrconf_verify();
}
return 0;
}
EXPORT_SYMBOL_GPL(addrconf_prefix_rcv_add_addr);
void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao) void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao)
{ {
struct prefix_info *pinfo; struct prefix_info *pinfo;
__u32 valid_lft; __u32 valid_lft;
__u32 prefered_lft; __u32 prefered_lft;
int addr_type; int addr_type, err;
u32 addr_flags = 0; u32 addr_flags = 0;
struct inet6_dev *in6_dev; struct inet6_dev *in6_dev;
struct net *net = dev_net(dev); struct net *net = dev_net(dev);
...@@ -2432,10 +2529,8 @@ void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao) ...@@ -2432,10 +2529,8 @@ void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao)
/* Try to figure out our local address for this prefix */ /* Try to figure out our local address for this prefix */
if (pinfo->autoconf && in6_dev->cnf.autoconf) { if (pinfo->autoconf && in6_dev->cnf.autoconf) {
struct inet6_ifaddr *ifp;
struct in6_addr addr; struct in6_addr addr;
int create = 0, update_lft = 0; bool tokenized = false, dev_addr_generated = false;
bool tokenized = false;
if (pinfo->prefix_len == 64) { if (pinfo->prefix_len == 64) {
memcpy(&addr, &pinfo->prefix, 8); memcpy(&addr, &pinfo->prefix, 8);
...@@ -2453,106 +2548,36 @@ void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao) ...@@ -2453,106 +2548,36 @@ void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao)
goto ok; goto ok;
} else if (ipv6_generate_eui64(addr.s6_addr + 8, dev) && } else if (ipv6_generate_eui64(addr.s6_addr + 8, dev) &&
ipv6_inherit_eui64(addr.s6_addr + 8, in6_dev)) { ipv6_inherit_eui64(addr.s6_addr + 8, in6_dev)) {
in6_dev_put(in6_dev); goto put;
return; } else {
dev_addr_generated = true;
} }
goto ok; goto ok;
} }
net_dbg_ratelimited("IPv6 addrconf: prefix with wrong length %d\n", net_dbg_ratelimited("IPv6 addrconf: prefix with wrong length %d\n",
pinfo->prefix_len); pinfo->prefix_len);
in6_dev_put(in6_dev); goto put;
return;
ok: ok:
err = addrconf_prefix_rcv_add_addr(net, dev, pinfo, in6_dev,
&addr, addr_type,
addr_flags, sllao,
tokenized, valid_lft,
prefered_lft);
if (err)
goto put;
ifp = ipv6_get_ifaddr(net, &addr, dev, 1); /* Ignore error case here because previous prefix add addr was
* successful which will be notified.
if (!ifp && valid_lft) { */
int max_addresses = in6_dev->cnf.max_addresses; ndisc_ops_prefix_rcv_add_addr(net, dev, pinfo, in6_dev, &addr,
addr_type, addr_flags, sllao,
#ifdef CONFIG_IPV6_OPTIMISTIC_DAD tokenized, valid_lft,
if (in6_dev->cnf.optimistic_dad && prefered_lft,
!net->ipv6.devconf_all->forwarding && sllao) dev_addr_generated);
addr_flags |= IFA_F_OPTIMISTIC;
#endif
/* Do not allow to create too much of autoconfigured
* addresses; this would be too easy way to crash kernel.
*/
if (!max_addresses ||
ipv6_count_addresses(in6_dev) < max_addresses)
ifp = ipv6_add_addr(in6_dev, &addr, NULL,
pinfo->prefix_len,
addr_type&IPV6_ADDR_SCOPE_MASK,
addr_flags, valid_lft,
prefered_lft);
if (IS_ERR_OR_NULL(ifp)) {
in6_dev_put(in6_dev);
return;
}
update_lft = 0;
create = 1;
spin_lock_bh(&ifp->lock);
ifp->flags |= IFA_F_MANAGETEMPADDR;
ifp->cstamp = jiffies;
ifp->tokenized = tokenized;
spin_unlock_bh(&ifp->lock);
addrconf_dad_start(ifp);
}
if (ifp) {
u32 flags;
unsigned long now;
u32 stored_lft;
/* update lifetime (RFC2462 5.5.3 e) */
spin_lock_bh(&ifp->lock);
now = jiffies;
if (ifp->valid_lft > (now - ifp->tstamp) / HZ)
stored_lft = ifp->valid_lft - (now - ifp->tstamp) / HZ;
else
stored_lft = 0;
if (!update_lft && !create && stored_lft) {
const u32 minimum_lft = min_t(u32,
stored_lft, MIN_VALID_LIFETIME);
valid_lft = max(valid_lft, minimum_lft);
/* RFC4862 Section 5.5.3e:
* "Note that the preferred lifetime of the
* corresponding address is always reset to
* the Preferred Lifetime in the received
* Prefix Information option, regardless of
* whether the valid lifetime is also reset or
* ignored."
*
* So we should always update prefered_lft here.
*/
update_lft = 1;
}
if (update_lft) {
ifp->valid_lft = valid_lft;
ifp->prefered_lft = prefered_lft;
ifp->tstamp = now;
flags = ifp->flags;
ifp->flags &= ~IFA_F_DEPRECATED;
spin_unlock_bh(&ifp->lock);
if (!(flags&IFA_F_TENTATIVE))
ipv6_ifa_notify(0, ifp);
} else
spin_unlock_bh(&ifp->lock);
manage_tempaddrs(in6_dev, ifp, valid_lft, prefered_lft,
create, now);
in6_ifa_put(ifp);
addrconf_verify();
}
} }
inet6_prefix_notify(RTM_NEWPREFIX, in6_dev, pinfo); inet6_prefix_notify(RTM_NEWPREFIX, in6_dev, pinfo);
put:
in6_dev_put(in6_dev); in6_dev_put(in6_dev);
} }
...@@ -2947,8 +2972,8 @@ static void init_loopback(struct net_device *dev) ...@@ -2947,8 +2972,8 @@ static void init_loopback(struct net_device *dev)
} }
} }
static void addrconf_add_linklocal(struct inet6_dev *idev, void addrconf_add_linklocal(struct inet6_dev *idev,
const struct in6_addr *addr, u32 flags) const struct in6_addr *addr, u32 flags)
{ {
struct inet6_ifaddr *ifp; struct inet6_ifaddr *ifp;
u32 addr_flags = flags | IFA_F_PERMANENT; u32 addr_flags = flags | IFA_F_PERMANENT;
...@@ -2967,6 +2992,7 @@ static void addrconf_add_linklocal(struct inet6_dev *idev, ...@@ -2967,6 +2992,7 @@ static void addrconf_add_linklocal(struct inet6_dev *idev,
in6_ifa_put(ifp); in6_ifa_put(ifp);
} }
} }
EXPORT_SYMBOL_GPL(addrconf_add_linklocal);
static bool ipv6_reserved_interfaceid(struct in6_addr address) static bool ipv6_reserved_interfaceid(struct in6_addr address)
{ {
......
...@@ -73,15 +73,6 @@ ...@@ -73,15 +73,6 @@
#include <linux/netfilter.h> #include <linux/netfilter.h>
#include <linux/netfilter_ipv6.h> #include <linux/netfilter_ipv6.h>
/* Set to 3 to get tracing... */
#define ND_DEBUG 1
#define ND_PRINTK(val, level, fmt, ...) \
do { \
if (val <= ND_DEBUG) \
net_##level##_ratelimited(fmt, ##__VA_ARGS__); \
} while (0)
static u32 ndisc_hash(const void *pkey, static u32 ndisc_hash(const void *pkey,
const struct net_device *dev, const struct net_device *dev,
__u32 *hash_rnd); __u32 *hash_rnd);
...@@ -150,11 +141,10 @@ struct neigh_table nd_tbl = { ...@@ -150,11 +141,10 @@ struct neigh_table nd_tbl = {
}; };
EXPORT_SYMBOL_GPL(nd_tbl); EXPORT_SYMBOL_GPL(nd_tbl);
static void ndisc_fill_addr_option(struct sk_buff *skb, int type, void *data) void __ndisc_fill_addr_option(struct sk_buff *skb, int type, void *data,
int data_len, int pad)
{ {
int pad = ndisc_addr_option_pad(skb->dev->type); int space = __ndisc_opt_addr_space(data_len, pad);
int data_len = skb->dev->addr_len;
int space = ndisc_opt_addr_space(skb->dev);
u8 *opt = skb_put(skb, space); u8 *opt = skb_put(skb, space);
opt[0] = type; opt[0] = type;
...@@ -171,6 +161,23 @@ static void ndisc_fill_addr_option(struct sk_buff *skb, int type, void *data) ...@@ -171,6 +161,23 @@ static void ndisc_fill_addr_option(struct sk_buff *skb, int type, void *data)
if (space > 0) if (space > 0)
memset(opt, 0, space); memset(opt, 0, space);
} }
EXPORT_SYMBOL_GPL(__ndisc_fill_addr_option);
static inline void ndisc_fill_addr_option(struct sk_buff *skb, int type,
void *data, u8 icmp6_type)
{
__ndisc_fill_addr_option(skb, type, data, skb->dev->addr_len,
ndisc_addr_option_pad(skb->dev->type));
ndisc_ops_fill_addr_option(skb->dev, skb, icmp6_type);
}
static inline void ndisc_fill_redirect_addr_option(struct sk_buff *skb,
void *ha,
const u8 *ops_data)
{
ndisc_fill_addr_option(skb, ND_OPT_TARGET_LL_ADDR, ha, NDISC_REDIRECT);
ndisc_ops_fill_redirect_addr_option(skb->dev, skb, ops_data);
}
static struct nd_opt_hdr *ndisc_next_option(struct nd_opt_hdr *cur, static struct nd_opt_hdr *ndisc_next_option(struct nd_opt_hdr *cur,
struct nd_opt_hdr *end) struct nd_opt_hdr *end)
...@@ -185,24 +192,28 @@ static struct nd_opt_hdr *ndisc_next_option(struct nd_opt_hdr *cur, ...@@ -185,24 +192,28 @@ static struct nd_opt_hdr *ndisc_next_option(struct nd_opt_hdr *cur,
return cur <= end && cur->nd_opt_type == type ? cur : NULL; return cur <= end && cur->nd_opt_type == type ? cur : NULL;
} }
static inline int ndisc_is_useropt(struct nd_opt_hdr *opt) static inline int ndisc_is_useropt(const struct net_device *dev,
struct nd_opt_hdr *opt)
{ {
return opt->nd_opt_type == ND_OPT_RDNSS || return opt->nd_opt_type == ND_OPT_RDNSS ||
opt->nd_opt_type == ND_OPT_DNSSL; opt->nd_opt_type == ND_OPT_DNSSL ||
ndisc_ops_is_useropt(dev, opt->nd_opt_type);
} }
static struct nd_opt_hdr *ndisc_next_useropt(struct nd_opt_hdr *cur, static struct nd_opt_hdr *ndisc_next_useropt(const struct net_device *dev,
struct nd_opt_hdr *cur,
struct nd_opt_hdr *end) struct nd_opt_hdr *end)
{ {
if (!cur || !end || cur >= end) if (!cur || !end || cur >= end)
return NULL; return NULL;
do { do {
cur = ((void *)cur) + (cur->nd_opt_len << 3); cur = ((void *)cur) + (cur->nd_opt_len << 3);
} while (cur < end && !ndisc_is_useropt(cur)); } while (cur < end && !ndisc_is_useropt(dev, cur));
return cur <= end && ndisc_is_useropt(cur) ? cur : NULL; return cur <= end && ndisc_is_useropt(dev, cur) ? cur : NULL;
} }
struct ndisc_options *ndisc_parse_options(u8 *opt, int opt_len, struct ndisc_options *ndisc_parse_options(const struct net_device *dev,
u8 *opt, int opt_len,
struct ndisc_options *ndopts) struct ndisc_options *ndopts)
{ {
struct nd_opt_hdr *nd_opt = (struct nd_opt_hdr *)opt; struct nd_opt_hdr *nd_opt = (struct nd_opt_hdr *)opt;
...@@ -217,6 +228,8 @@ struct ndisc_options *ndisc_parse_options(u8 *opt, int opt_len, ...@@ -217,6 +228,8 @@ struct ndisc_options *ndisc_parse_options(u8 *opt, int opt_len,
l = nd_opt->nd_opt_len << 3; l = nd_opt->nd_opt_len << 3;
if (opt_len < l || l == 0) if (opt_len < l || l == 0)
return NULL; return NULL;
if (ndisc_ops_parse_options(dev, nd_opt, ndopts))
goto next_opt;
switch (nd_opt->nd_opt_type) { switch (nd_opt->nd_opt_type) {
case ND_OPT_SOURCE_LL_ADDR: case ND_OPT_SOURCE_LL_ADDR:
case ND_OPT_TARGET_LL_ADDR: case ND_OPT_TARGET_LL_ADDR:
...@@ -243,7 +256,7 @@ struct ndisc_options *ndisc_parse_options(u8 *opt, int opt_len, ...@@ -243,7 +256,7 @@ struct ndisc_options *ndisc_parse_options(u8 *opt, int opt_len,
break; break;
#endif #endif
default: default:
if (ndisc_is_useropt(nd_opt)) { if (ndisc_is_useropt(dev, nd_opt)) {
ndopts->nd_useropts_end = nd_opt; ndopts->nd_useropts_end = nd_opt;
if (!ndopts->nd_useropts) if (!ndopts->nd_useropts)
ndopts->nd_useropts = nd_opt; ndopts->nd_useropts = nd_opt;
...@@ -260,6 +273,7 @@ struct ndisc_options *ndisc_parse_options(u8 *opt, int opt_len, ...@@ -260,6 +273,7 @@ struct ndisc_options *ndisc_parse_options(u8 *opt, int opt_len,
nd_opt->nd_opt_len); nd_opt->nd_opt_len);
} }
} }
next_opt:
opt_len -= l; opt_len -= l;
nd_opt = ((void *)nd_opt) + l; nd_opt = ((void *)nd_opt) + l;
} }
...@@ -509,7 +523,8 @@ void ndisc_send_na(struct net_device *dev, const struct in6_addr *daddr, ...@@ -509,7 +523,8 @@ void ndisc_send_na(struct net_device *dev, const struct in6_addr *daddr,
if (!dev->addr_len) if (!dev->addr_len)
inc_opt = 0; inc_opt = 0;
if (inc_opt) if (inc_opt)
optlen += ndisc_opt_addr_space(dev); optlen += ndisc_opt_addr_space(dev,
NDISC_NEIGHBOUR_ADVERTISEMENT);
skb = ndisc_alloc_skb(dev, sizeof(*msg) + optlen); skb = ndisc_alloc_skb(dev, sizeof(*msg) + optlen);
if (!skb) if (!skb)
...@@ -528,8 +543,8 @@ void ndisc_send_na(struct net_device *dev, const struct in6_addr *daddr, ...@@ -528,8 +543,8 @@ void ndisc_send_na(struct net_device *dev, const struct in6_addr *daddr,
if (inc_opt) if (inc_opt)
ndisc_fill_addr_option(skb, ND_OPT_TARGET_LL_ADDR, ndisc_fill_addr_option(skb, ND_OPT_TARGET_LL_ADDR,
dev->dev_addr); dev->dev_addr,
NDISC_NEIGHBOUR_ADVERTISEMENT);
ndisc_send_skb(skb, daddr, src_addr); ndisc_send_skb(skb, daddr, src_addr);
} }
...@@ -574,7 +589,8 @@ void ndisc_send_ns(struct net_device *dev, const struct in6_addr *solicit, ...@@ -574,7 +589,8 @@ void ndisc_send_ns(struct net_device *dev, const struct in6_addr *solicit,
if (ipv6_addr_any(saddr)) if (ipv6_addr_any(saddr))
inc_opt = false; inc_opt = false;
if (inc_opt) if (inc_opt)
optlen += ndisc_opt_addr_space(dev); optlen += ndisc_opt_addr_space(dev,
NDISC_NEIGHBOUR_SOLICITATION);
skb = ndisc_alloc_skb(dev, sizeof(*msg) + optlen); skb = ndisc_alloc_skb(dev, sizeof(*msg) + optlen);
if (!skb) if (!skb)
...@@ -590,7 +606,8 @@ void ndisc_send_ns(struct net_device *dev, const struct in6_addr *solicit, ...@@ -590,7 +606,8 @@ void ndisc_send_ns(struct net_device *dev, const struct in6_addr *solicit,
if (inc_opt) if (inc_opt)
ndisc_fill_addr_option(skb, ND_OPT_SOURCE_LL_ADDR, ndisc_fill_addr_option(skb, ND_OPT_SOURCE_LL_ADDR,
dev->dev_addr); dev->dev_addr,
NDISC_NEIGHBOUR_SOLICITATION);
ndisc_send_skb(skb, daddr, saddr); ndisc_send_skb(skb, daddr, saddr);
} }
...@@ -626,7 +643,7 @@ void ndisc_send_rs(struct net_device *dev, const struct in6_addr *saddr, ...@@ -626,7 +643,7 @@ void ndisc_send_rs(struct net_device *dev, const struct in6_addr *saddr,
} }
#endif #endif
if (send_sllao) if (send_sllao)
optlen += ndisc_opt_addr_space(dev); optlen += ndisc_opt_addr_space(dev, NDISC_ROUTER_SOLICITATION);
skb = ndisc_alloc_skb(dev, sizeof(*msg) + optlen); skb = ndisc_alloc_skb(dev, sizeof(*msg) + optlen);
if (!skb) if (!skb)
...@@ -641,7 +658,8 @@ void ndisc_send_rs(struct net_device *dev, const struct in6_addr *saddr, ...@@ -641,7 +658,8 @@ void ndisc_send_rs(struct net_device *dev, const struct in6_addr *saddr,
if (send_sllao) if (send_sllao)
ndisc_fill_addr_option(skb, ND_OPT_SOURCE_LL_ADDR, ndisc_fill_addr_option(skb, ND_OPT_SOURCE_LL_ADDR,
dev->dev_addr); dev->dev_addr,
NDISC_ROUTER_SOLICITATION);
ndisc_send_skb(skb, daddr, saddr); ndisc_send_skb(skb, daddr, saddr);
} }
...@@ -702,6 +720,15 @@ static int pndisc_is_router(const void *pkey, ...@@ -702,6 +720,15 @@ static int pndisc_is_router(const void *pkey,
return ret; return ret;
} }
void ndisc_update(const struct net_device *dev, struct neighbour *neigh,
const u8 *lladdr, u8 new, u32 flags, u8 icmp6_type,
struct ndisc_options *ndopts)
{
neigh_update(neigh, lladdr, new, flags);
/* report ndisc ops about neighbour update */
ndisc_ops_update(dev, neigh, flags, icmp6_type, ndopts);
}
static void ndisc_recv_ns(struct sk_buff *skb) static void ndisc_recv_ns(struct sk_buff *skb)
{ {
struct nd_msg *msg = (struct nd_msg *)skb_transport_header(skb); struct nd_msg *msg = (struct nd_msg *)skb_transport_header(skb);
...@@ -738,7 +765,7 @@ static void ndisc_recv_ns(struct sk_buff *skb) ...@@ -738,7 +765,7 @@ static void ndisc_recv_ns(struct sk_buff *skb)
return; return;
} }
if (!ndisc_parse_options(msg->opt, ndoptlen, &ndopts)) { if (!ndisc_parse_options(dev, msg->opt, ndoptlen, &ndopts)) {
ND_PRINTK(2, warn, "NS: invalid ND options\n"); ND_PRINTK(2, warn, "NS: invalid ND options\n");
return; return;
} }
...@@ -856,9 +883,10 @@ static void ndisc_recv_ns(struct sk_buff *skb) ...@@ -856,9 +883,10 @@ static void ndisc_recv_ns(struct sk_buff *skb)
neigh = __neigh_lookup(&nd_tbl, saddr, dev, neigh = __neigh_lookup(&nd_tbl, saddr, dev,
!inc || lladdr || !dev->addr_len); !inc || lladdr || !dev->addr_len);
if (neigh) if (neigh)
neigh_update(neigh, lladdr, NUD_STALE, ndisc_update(dev, neigh, lladdr, NUD_STALE,
NEIGH_UPDATE_F_WEAK_OVERRIDE| NEIGH_UPDATE_F_WEAK_OVERRIDE|
NEIGH_UPDATE_F_OVERRIDE); NEIGH_UPDATE_F_OVERRIDE,
NDISC_NEIGHBOUR_SOLICITATION, &ndopts);
if (neigh || !dev->header_ops) { if (neigh || !dev->header_ops) {
ndisc_send_na(dev, saddr, &msg->target, !!is_router, ndisc_send_na(dev, saddr, &msg->target, !!is_router,
true, (ifp != NULL && inc), inc); true, (ifp != NULL && inc), inc);
...@@ -911,7 +939,7 @@ static void ndisc_recv_na(struct sk_buff *skb) ...@@ -911,7 +939,7 @@ static void ndisc_recv_na(struct sk_buff *skb)
idev->cnf.drop_unsolicited_na) idev->cnf.drop_unsolicited_na)
return; return;
if (!ndisc_parse_options(msg->opt, ndoptlen, &ndopts)) { if (!ndisc_parse_options(dev, msg->opt, ndoptlen, &ndopts)) {
ND_PRINTK(2, warn, "NS: invalid ND option\n"); ND_PRINTK(2, warn, "NS: invalid ND option\n");
return; return;
} }
...@@ -967,12 +995,13 @@ static void ndisc_recv_na(struct sk_buff *skb) ...@@ -967,12 +995,13 @@ static void ndisc_recv_na(struct sk_buff *skb)
goto out; goto out;
} }
neigh_update(neigh, lladdr, ndisc_update(dev, neigh, lladdr,
msg->icmph.icmp6_solicited ? NUD_REACHABLE : NUD_STALE, msg->icmph.icmp6_solicited ? NUD_REACHABLE : NUD_STALE,
NEIGH_UPDATE_F_WEAK_OVERRIDE| NEIGH_UPDATE_F_WEAK_OVERRIDE|
(msg->icmph.icmp6_override ? NEIGH_UPDATE_F_OVERRIDE : 0)| (msg->icmph.icmp6_override ? NEIGH_UPDATE_F_OVERRIDE : 0)|
NEIGH_UPDATE_F_OVERRIDE_ISROUTER| NEIGH_UPDATE_F_OVERRIDE_ISROUTER|
(msg->icmph.icmp6_router ? NEIGH_UPDATE_F_ISROUTER : 0)); (msg->icmph.icmp6_router ? NEIGH_UPDATE_F_ISROUTER : 0),
NDISC_NEIGHBOUR_ADVERTISEMENT, &ndopts);
if ((old_flags & ~neigh->flags) & NTF_ROUTER) { if ((old_flags & ~neigh->flags) & NTF_ROUTER) {
/* /*
...@@ -1017,7 +1046,7 @@ static void ndisc_recv_rs(struct sk_buff *skb) ...@@ -1017,7 +1046,7 @@ static void ndisc_recv_rs(struct sk_buff *skb)
goto out; goto out;
/* Parse ND options */ /* Parse ND options */
if (!ndisc_parse_options(rs_msg->opt, ndoptlen, &ndopts)) { if (!ndisc_parse_options(skb->dev, rs_msg->opt, ndoptlen, &ndopts)) {
ND_PRINTK(2, notice, "NS: invalid ND option, ignored\n"); ND_PRINTK(2, notice, "NS: invalid ND option, ignored\n");
goto out; goto out;
} }
...@@ -1031,10 +1060,11 @@ static void ndisc_recv_rs(struct sk_buff *skb) ...@@ -1031,10 +1060,11 @@ static void ndisc_recv_rs(struct sk_buff *skb)
neigh = __neigh_lookup(&nd_tbl, saddr, skb->dev, 1); neigh = __neigh_lookup(&nd_tbl, saddr, skb->dev, 1);
if (neigh) { if (neigh) {
neigh_update(neigh, lladdr, NUD_STALE, ndisc_update(skb->dev, neigh, lladdr, NUD_STALE,
NEIGH_UPDATE_F_WEAK_OVERRIDE| NEIGH_UPDATE_F_WEAK_OVERRIDE|
NEIGH_UPDATE_F_OVERRIDE| NEIGH_UPDATE_F_OVERRIDE|
NEIGH_UPDATE_F_OVERRIDE_ISROUTER); NEIGH_UPDATE_F_OVERRIDE_ISROUTER,
NDISC_ROUTER_SOLICITATION, &ndopts);
neigh_release(neigh); neigh_release(neigh);
} }
out: out:
...@@ -1135,7 +1165,7 @@ static void ndisc_router_discovery(struct sk_buff *skb) ...@@ -1135,7 +1165,7 @@ static void ndisc_router_discovery(struct sk_buff *skb)
return; return;
} }
if (!ndisc_parse_options(opt, optlen, &ndopts)) { if (!ndisc_parse_options(skb->dev, opt, optlen, &ndopts)) {
ND_PRINTK(2, warn, "RA: invalid ND options\n"); ND_PRINTK(2, warn, "RA: invalid ND options\n");
return; return;
} }
...@@ -1329,11 +1359,12 @@ static void ndisc_router_discovery(struct sk_buff *skb) ...@@ -1329,11 +1359,12 @@ static void ndisc_router_discovery(struct sk_buff *skb)
goto out; goto out;
} }
} }
neigh_update(neigh, lladdr, NUD_STALE, ndisc_update(skb->dev, neigh, lladdr, NUD_STALE,
NEIGH_UPDATE_F_WEAK_OVERRIDE| NEIGH_UPDATE_F_WEAK_OVERRIDE|
NEIGH_UPDATE_F_OVERRIDE| NEIGH_UPDATE_F_OVERRIDE|
NEIGH_UPDATE_F_OVERRIDE_ISROUTER| NEIGH_UPDATE_F_OVERRIDE_ISROUTER|
NEIGH_UPDATE_F_ISROUTER); NEIGH_UPDATE_F_ISROUTER,
NDISC_ROUTER_ADVERTISEMENT, &ndopts);
} }
if (!ipv6_accept_ra(in6_dev)) { if (!ipv6_accept_ra(in6_dev)) {
...@@ -1421,7 +1452,8 @@ static void ndisc_router_discovery(struct sk_buff *skb) ...@@ -1421,7 +1452,8 @@ static void ndisc_router_discovery(struct sk_buff *skb)
struct nd_opt_hdr *p; struct nd_opt_hdr *p;
for (p = ndopts.nd_useropts; for (p = ndopts.nd_useropts;
p; p;
p = ndisc_next_useropt(p, ndopts.nd_useropts_end)) { p = ndisc_next_useropt(skb->dev, p,
ndopts.nd_useropts_end)) {
ndisc_ra_useropt(skb, p); ndisc_ra_useropt(skb, p);
} }
} }
...@@ -1459,7 +1491,7 @@ static void ndisc_redirect_rcv(struct sk_buff *skb) ...@@ -1459,7 +1491,7 @@ static void ndisc_redirect_rcv(struct sk_buff *skb)
return; return;
} }
if (!ndisc_parse_options(msg->opt, ndoptlen, &ndopts)) if (!ndisc_parse_options(skb->dev, msg->opt, ndoptlen, &ndopts))
return; return;
if (!ndopts.nd_opts_rh) { if (!ndopts.nd_opts_rh) {
...@@ -1504,7 +1536,8 @@ void ndisc_send_redirect(struct sk_buff *skb, const struct in6_addr *target) ...@@ -1504,7 +1536,8 @@ void ndisc_send_redirect(struct sk_buff *skb, const struct in6_addr *target)
struct dst_entry *dst; struct dst_entry *dst;
struct flowi6 fl6; struct flowi6 fl6;
int rd_len; int rd_len;
u8 ha_buf[MAX_ADDR_LEN], *ha = NULL; u8 ha_buf[MAX_ADDR_LEN], *ha = NULL,
ops_data_buf[NDISC_OPS_REDIRECT_DATA_SPACE], *ops_data = NULL;
int oif = l3mdev_fib_oif(dev); int oif = l3mdev_fib_oif(dev);
bool ret; bool ret;
...@@ -1563,7 +1596,9 @@ void ndisc_send_redirect(struct sk_buff *skb, const struct in6_addr *target) ...@@ -1563,7 +1596,9 @@ void ndisc_send_redirect(struct sk_buff *skb, const struct in6_addr *target)
memcpy(ha_buf, neigh->ha, dev->addr_len); memcpy(ha_buf, neigh->ha, dev->addr_len);
read_unlock_bh(&neigh->lock); read_unlock_bh(&neigh->lock);
ha = ha_buf; ha = ha_buf;
optlen += ndisc_opt_addr_space(dev); optlen += ndisc_redirect_opt_addr_space(dev, neigh,
ops_data_buf,
&ops_data);
} else } else
read_unlock_bh(&neigh->lock); read_unlock_bh(&neigh->lock);
...@@ -1594,7 +1629,7 @@ void ndisc_send_redirect(struct sk_buff *skb, const struct in6_addr *target) ...@@ -1594,7 +1629,7 @@ void ndisc_send_redirect(struct sk_buff *skb, const struct in6_addr *target)
*/ */
if (ha) if (ha)
ndisc_fill_addr_option(buff, ND_OPT_TARGET_LL_ADDR, ha); ndisc_fill_redirect_addr_option(buff, ha, ops_data);
/* /*
* build redirect option and copy skb over to the new packet. * build redirect option and copy skb over to the new packet.
......
...@@ -2201,7 +2201,7 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu ...@@ -2201,7 +2201,7 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu
* first-hop router for the specified ICMP Destination Address. * first-hop router for the specified ICMP Destination Address.
*/ */
if (!ndisc_parse_options(msg->opt, optlen, &ndopts)) { if (!ndisc_parse_options(skb->dev, msg->opt, optlen, &ndopts)) {
net_dbg_ratelimited("rt6_redirect: invalid ND options\n"); net_dbg_ratelimited("rt6_redirect: invalid ND options\n");
return; return;
} }
...@@ -2236,12 +2236,12 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu ...@@ -2236,12 +2236,12 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu
* We have finally decided to accept it. * We have finally decided to accept it.
*/ */
neigh_update(neigh, lladdr, NUD_STALE, ndisc_update(skb->dev, neigh, lladdr, NUD_STALE,
NEIGH_UPDATE_F_WEAK_OVERRIDE| NEIGH_UPDATE_F_WEAK_OVERRIDE|
NEIGH_UPDATE_F_OVERRIDE| NEIGH_UPDATE_F_OVERRIDE|
(on_link ? 0 : (NEIGH_UPDATE_F_OVERRIDE_ISROUTER| (on_link ? 0 : (NEIGH_UPDATE_F_OVERRIDE_ISROUTER|
NEIGH_UPDATE_F_ISROUTER)) NEIGH_UPDATE_F_ISROUTER)),
); NDISC_REDIRECT, &ndopts);
nrt = ip6_rt_cache_alloc(rt, &msg->dest, NULL); nrt = ip6_rt_cache_alloc(rt, &msg->dest, NULL);
if (!nrt) if (!nrt)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment