| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1 | /* | 
|  | 2 | * INET		An implementation of the TCP/IP protocol suite for the LINUX | 
|  | 3 | *		operating system.  INET is implemented using the  BSD Socket | 
|  | 4 | *		interface as the means of communication with the user level. | 
|  | 5 | * | 
|  | 6 | *		Routing netlink socket interface: protocol independent part. | 
|  | 7 | * | 
|  | 8 | * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> | 
|  | 9 | * | 
|  | 10 | *		This program is free software; you can redistribute it and/or | 
|  | 11 | *		modify it under the terms of the GNU General Public License | 
|  | 12 | *		as published by the Free Software Foundation; either version | 
|  | 13 | *		2 of the License, or (at your option) any later version. | 
|  | 14 | * | 
|  | 15 | *	Fixes: | 
|  | 16 | *	Vitaly E. Lavrov		RTA_OK arithmetics was wrong. | 
|  | 17 | */ | 
|  | 18 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 19 | #include <linux/errno.h> | 
|  | 20 | #include <linux/module.h> | 
|  | 21 | #include <linux/types.h> | 
|  | 22 | #include <linux/socket.h> | 
|  | 23 | #include <linux/kernel.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 24 | #include <linux/timer.h> | 
|  | 25 | #include <linux/string.h> | 
|  | 26 | #include <linux/sockios.h> | 
|  | 27 | #include <linux/net.h> | 
|  | 28 | #include <linux/fcntl.h> | 
|  | 29 | #include <linux/mm.h> | 
|  | 30 | #include <linux/slab.h> | 
|  | 31 | #include <linux/interrupt.h> | 
|  | 32 | #include <linux/capability.h> | 
|  | 33 | #include <linux/skbuff.h> | 
|  | 34 | #include <linux/init.h> | 
|  | 35 | #include <linux/security.h> | 
| Stephen Hemminger | 6756ae4 | 2006-03-20 22:23:58 -0800 | [diff] [blame] | 36 | #include <linux/mutex.h> | 
| Thomas Graf | 1823730 | 2006-08-04 23:04:54 -0700 | [diff] [blame] | 37 | #include <linux/if_addr.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 38 |  | 
|  | 39 | #include <asm/uaccess.h> | 
|  | 40 | #include <asm/system.h> | 
|  | 41 | #include <asm/string.h> | 
|  | 42 |  | 
|  | 43 | #include <linux/inet.h> | 
|  | 44 | #include <linux/netdevice.h> | 
|  | 45 | #include <net/ip.h> | 
|  | 46 | #include <net/protocol.h> | 
|  | 47 | #include <net/arp.h> | 
|  | 48 | #include <net/route.h> | 
|  | 49 | #include <net/udp.h> | 
|  | 50 | #include <net/sock.h> | 
|  | 51 | #include <net/pkt_sched.h> | 
| Thomas Graf | 14c0b97 | 2006-08-04 03:38:38 -0700 | [diff] [blame] | 52 | #include <net/fib_rules.h> | 
| Thomas Graf | 9ac4a16 | 2005-11-10 02:25:55 +0100 | [diff] [blame] | 53 | #include <net/netlink.h> | 
| Jean Tourrilhes | 711e2c3 | 2006-02-22 15:10:56 -0800 | [diff] [blame] | 54 | #ifdef CONFIG_NET_WIRELESS_RTNETLINK | 
|  | 55 | #include <linux/wireless.h> | 
|  | 56 | #include <net/iw_handler.h> | 
|  | 57 | #endif	/* CONFIG_NET_WIRELESS_RTNETLINK */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 58 |  | 
| Stephen Hemminger | 6756ae4 | 2006-03-20 22:23:58 -0800 | [diff] [blame] | 59 | static DEFINE_MUTEX(rtnl_mutex); | 
| Thomas Graf | 56fc85a | 2006-08-15 00:37:29 -0700 | [diff] [blame] | 60 | static struct sock *rtnl; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 61 |  | 
|  | 62 | void rtnl_lock(void) | 
|  | 63 | { | 
| Stephen Hemminger | 6756ae4 | 2006-03-20 22:23:58 -0800 | [diff] [blame] | 64 | mutex_lock(&rtnl_mutex); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 65 | } | 
|  | 66 |  | 
| Stephen Hemminger | 6756ae4 | 2006-03-20 22:23:58 -0800 | [diff] [blame] | 67 | void __rtnl_unlock(void) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 68 | { | 
| Stephen Hemminger | 6756ae4 | 2006-03-20 22:23:58 -0800 | [diff] [blame] | 69 | mutex_unlock(&rtnl_mutex); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 70 | } | 
| Stephen Hemminger | 6756ae4 | 2006-03-20 22:23:58 -0800 | [diff] [blame] | 71 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 72 | void rtnl_unlock(void) | 
|  | 73 | { | 
| Stephen Hemminger | 6756ae4 | 2006-03-20 22:23:58 -0800 | [diff] [blame] | 74 | mutex_unlock(&rtnl_mutex); | 
|  | 75 | if (rtnl && rtnl->sk_receive_queue.qlen) | 
|  | 76 | rtnl->sk_data_ready(rtnl, 0); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 77 | netdev_run_todo(); | 
|  | 78 | } | 
|  | 79 |  | 
| Stephen Hemminger | 6756ae4 | 2006-03-20 22:23:58 -0800 | [diff] [blame] | 80 | int rtnl_trylock(void) | 
|  | 81 | { | 
|  | 82 | return mutex_trylock(&rtnl_mutex); | 
|  | 83 | } | 
|  | 84 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 85 | int rtattr_parse(struct rtattr *tb[], int maxattr, struct rtattr *rta, int len) | 
|  | 86 | { | 
|  | 87 | memset(tb, 0, sizeof(struct rtattr*)*maxattr); | 
|  | 88 |  | 
|  | 89 | while (RTA_OK(rta, len)) { | 
|  | 90 | unsigned flavor = rta->rta_type; | 
|  | 91 | if (flavor && flavor <= maxattr) | 
|  | 92 | tb[flavor-1] = rta; | 
|  | 93 | rta = RTA_NEXT(rta, len); | 
|  | 94 | } | 
|  | 95 | return 0; | 
|  | 96 | } | 
|  | 97 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 98 | struct rtnetlink_link * rtnetlink_links[NPROTO]; | 
|  | 99 |  | 
| Thomas Graf | db46edc | 2005-05-03 14:29:39 -0700 | [diff] [blame] | 100 | static const int rtm_min[RTM_NR_FAMILIES] = | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 101 | { | 
| Thomas Graf | f90a0a7 | 2005-05-03 14:29:00 -0700 | [diff] [blame] | 102 | [RTM_FAM(RTM_NEWLINK)]      = NLMSG_LENGTH(sizeof(struct ifinfomsg)), | 
|  | 103 | [RTM_FAM(RTM_NEWADDR)]      = NLMSG_LENGTH(sizeof(struct ifaddrmsg)), | 
|  | 104 | [RTM_FAM(RTM_NEWROUTE)]     = NLMSG_LENGTH(sizeof(struct rtmsg)), | 
| Thomas Graf | 14c0b97 | 2006-08-04 03:38:38 -0700 | [diff] [blame] | 105 | [RTM_FAM(RTM_NEWRULE)]      = NLMSG_LENGTH(sizeof(struct fib_rule_hdr)), | 
| Thomas Graf | f90a0a7 | 2005-05-03 14:29:00 -0700 | [diff] [blame] | 106 | [RTM_FAM(RTM_NEWQDISC)]     = NLMSG_LENGTH(sizeof(struct tcmsg)), | 
|  | 107 | [RTM_FAM(RTM_NEWTCLASS)]    = NLMSG_LENGTH(sizeof(struct tcmsg)), | 
|  | 108 | [RTM_FAM(RTM_NEWTFILTER)]   = NLMSG_LENGTH(sizeof(struct tcmsg)), | 
|  | 109 | [RTM_FAM(RTM_NEWACTION)]    = NLMSG_LENGTH(sizeof(struct tcamsg)), | 
| Thomas Graf | f90a0a7 | 2005-05-03 14:29:00 -0700 | [diff] [blame] | 110 | [RTM_FAM(RTM_GETMULTICAST)] = NLMSG_LENGTH(sizeof(struct rtgenmsg)), | 
|  | 111 | [RTM_FAM(RTM_GETANYCAST)]   = NLMSG_LENGTH(sizeof(struct rtgenmsg)), | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 112 | }; | 
|  | 113 |  | 
| Thomas Graf | db46edc | 2005-05-03 14:29:39 -0700 | [diff] [blame] | 114 | static const int rta_max[RTM_NR_FAMILIES] = | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 115 | { | 
| Thomas Graf | f90a0a7 | 2005-05-03 14:29:00 -0700 | [diff] [blame] | 116 | [RTM_FAM(RTM_NEWLINK)]      = IFLA_MAX, | 
|  | 117 | [RTM_FAM(RTM_NEWADDR)]      = IFA_MAX, | 
|  | 118 | [RTM_FAM(RTM_NEWROUTE)]     = RTA_MAX, | 
| Thomas Graf | 14c0b97 | 2006-08-04 03:38:38 -0700 | [diff] [blame] | 119 | [RTM_FAM(RTM_NEWRULE)]      = FRA_MAX, | 
| Thomas Graf | f90a0a7 | 2005-05-03 14:29:00 -0700 | [diff] [blame] | 120 | [RTM_FAM(RTM_NEWQDISC)]     = TCA_MAX, | 
|  | 121 | [RTM_FAM(RTM_NEWTCLASS)]    = TCA_MAX, | 
|  | 122 | [RTM_FAM(RTM_NEWTFILTER)]   = TCA_MAX, | 
|  | 123 | [RTM_FAM(RTM_NEWACTION)]    = TCAA_MAX, | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 124 | }; | 
|  | 125 |  | 
|  | 126 | void __rta_fill(struct sk_buff *skb, int attrtype, int attrlen, const void *data) | 
|  | 127 | { | 
|  | 128 | struct rtattr *rta; | 
|  | 129 | int size = RTA_LENGTH(attrlen); | 
|  | 130 |  | 
|  | 131 | rta = (struct rtattr*)skb_put(skb, RTA_ALIGN(size)); | 
|  | 132 | rta->rta_type = attrtype; | 
|  | 133 | rta->rta_len = size; | 
|  | 134 | memcpy(RTA_DATA(rta), data, attrlen); | 
| Patrick McHardy | b3563c4 | 2005-06-28 12:54:43 -0700 | [diff] [blame] | 135 | memset(RTA_DATA(rta) + attrlen, 0, RTA_ALIGN(size) - size); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 136 | } | 
|  | 137 |  | 
|  | 138 | size_t rtattr_strlcpy(char *dest, const struct rtattr *rta, size_t size) | 
|  | 139 | { | 
|  | 140 | size_t ret = RTA_PAYLOAD(rta); | 
|  | 141 | char *src = RTA_DATA(rta); | 
|  | 142 |  | 
|  | 143 | if (ret > 0 && src[ret - 1] == '\0') | 
|  | 144 | ret--; | 
|  | 145 | if (size > 0) { | 
|  | 146 | size_t len = (ret >= size) ? size - 1 : ret; | 
|  | 147 | memset(dest, 0, size); | 
|  | 148 | memcpy(dest, src, len); | 
|  | 149 | } | 
|  | 150 | return ret; | 
|  | 151 | } | 
|  | 152 |  | 
|  | 153 | int rtnetlink_send(struct sk_buff *skb, u32 pid, unsigned group, int echo) | 
|  | 154 | { | 
|  | 155 | int err = 0; | 
|  | 156 |  | 
| Patrick McHardy | ac6d439 | 2005-08-14 19:29:52 -0700 | [diff] [blame] | 157 | NETLINK_CB(skb).dst_group = group; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 158 | if (echo) | 
|  | 159 | atomic_inc(&skb->users); | 
|  | 160 | netlink_broadcast(rtnl, skb, pid, group, GFP_KERNEL); | 
|  | 161 | if (echo) | 
|  | 162 | err = netlink_unicast(rtnl, skb, pid, MSG_DONTWAIT); | 
|  | 163 | return err; | 
|  | 164 | } | 
|  | 165 |  | 
| Thomas Graf | 2942e90 | 2006-08-15 00:30:25 -0700 | [diff] [blame] | 166 | int rtnl_unicast(struct sk_buff *skb, u32 pid) | 
|  | 167 | { | 
|  | 168 | return nlmsg_unicast(rtnl, skb, pid); | 
|  | 169 | } | 
|  | 170 |  | 
| Thomas Graf | 97676b6 | 2006-08-15 00:31:41 -0700 | [diff] [blame] | 171 | int rtnl_notify(struct sk_buff *skb, u32 pid, u32 group, | 
|  | 172 | struct nlmsghdr *nlh, gfp_t flags) | 
|  | 173 | { | 
|  | 174 | int report = 0; | 
|  | 175 |  | 
|  | 176 | if (nlh) | 
|  | 177 | report = nlmsg_report(nlh); | 
|  | 178 |  | 
|  | 179 | return nlmsg_notify(rtnl, skb, pid, group, report, flags); | 
|  | 180 | } | 
|  | 181 |  | 
|  | 182 | void rtnl_set_sk_err(u32 group, int error) | 
|  | 183 | { | 
|  | 184 | netlink_set_err(rtnl, 0, group, error); | 
|  | 185 | } | 
|  | 186 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 187 | int rtnetlink_put_metrics(struct sk_buff *skb, u32 *metrics) | 
|  | 188 | { | 
| Thomas Graf | 2d7202b | 2006-08-22 00:01:27 -0700 | [diff] [blame] | 189 | struct nlattr *mx; | 
|  | 190 | int i, valid = 0; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 191 |  | 
| Thomas Graf | 2d7202b | 2006-08-22 00:01:27 -0700 | [diff] [blame] | 192 | mx = nla_nest_start(skb, RTA_METRICS); | 
|  | 193 | if (mx == NULL) | 
|  | 194 | return -ENOBUFS; | 
|  | 195 |  | 
|  | 196 | for (i = 0; i < RTAX_MAX; i++) { | 
|  | 197 | if (metrics[i]) { | 
|  | 198 | valid++; | 
|  | 199 | NLA_PUT_U32(skb, i+1, metrics[i]); | 
|  | 200 | } | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 201 | } | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 202 |  | 
| David S. Miller | a57d27f | 2006-08-22 22:20:14 -0700 | [diff] [blame] | 203 | if (!valid) { | 
|  | 204 | nla_nest_cancel(skb, mx); | 
|  | 205 | return 0; | 
|  | 206 | } | 
| Thomas Graf | 2d7202b | 2006-08-22 00:01:27 -0700 | [diff] [blame] | 207 |  | 
|  | 208 | return nla_nest_end(skb, mx); | 
|  | 209 |  | 
|  | 210 | nla_put_failure: | 
|  | 211 | return nla_nest_cancel(skb, mx); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 212 | } | 
|  | 213 |  | 
| Thomas Graf | e3703b3 | 2006-11-27 09:27:07 -0800 | [diff] [blame] | 214 | int rtnl_put_cacheinfo(struct sk_buff *skb, struct dst_entry *dst, u32 id, | 
|  | 215 | u32 ts, u32 tsage, long expires, u32 error) | 
|  | 216 | { | 
|  | 217 | struct rta_cacheinfo ci = { | 
|  | 218 | .rta_lastuse = jiffies_to_clock_t(jiffies - dst->lastuse), | 
|  | 219 | .rta_used = dst->__use, | 
|  | 220 | .rta_clntref = atomic_read(&(dst->__refcnt)), | 
|  | 221 | .rta_error = error, | 
|  | 222 | .rta_id =  id, | 
|  | 223 | .rta_ts = ts, | 
|  | 224 | .rta_tsage = tsage, | 
|  | 225 | }; | 
|  | 226 |  | 
|  | 227 | if (expires) | 
|  | 228 | ci.rta_expires = jiffies_to_clock_t(expires); | 
|  | 229 |  | 
|  | 230 | return nla_put(skb, RTA_CACHEINFO, sizeof(ci), &ci); | 
|  | 231 | } | 
|  | 232 |  | 
|  | 233 | EXPORT_SYMBOL_GPL(rtnl_put_cacheinfo); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 234 |  | 
| Stefan Rompf | b00055a | 2006-03-20 17:09:11 -0800 | [diff] [blame] | 235 | static void set_operstate(struct net_device *dev, unsigned char transition) | 
|  | 236 | { | 
|  | 237 | unsigned char operstate = dev->operstate; | 
|  | 238 |  | 
|  | 239 | switch(transition) { | 
|  | 240 | case IF_OPER_UP: | 
|  | 241 | if ((operstate == IF_OPER_DORMANT || | 
|  | 242 | operstate == IF_OPER_UNKNOWN) && | 
|  | 243 | !netif_dormant(dev)) | 
|  | 244 | operstate = IF_OPER_UP; | 
|  | 245 | break; | 
|  | 246 |  | 
|  | 247 | case IF_OPER_DORMANT: | 
|  | 248 | if (operstate == IF_OPER_UP || | 
|  | 249 | operstate == IF_OPER_UNKNOWN) | 
|  | 250 | operstate = IF_OPER_DORMANT; | 
|  | 251 | break; | 
|  | 252 | }; | 
|  | 253 |  | 
|  | 254 | if (dev->operstate != operstate) { | 
|  | 255 | write_lock_bh(&dev_base_lock); | 
|  | 256 | dev->operstate = operstate; | 
|  | 257 | write_unlock_bh(&dev_base_lock); | 
|  | 258 | netdev_state_change(dev); | 
|  | 259 | } | 
|  | 260 | } | 
|  | 261 |  | 
| Thomas Graf | b60c511 | 2006-08-04 23:05:34 -0700 | [diff] [blame] | 262 | static void copy_rtnl_link_stats(struct rtnl_link_stats *a, | 
|  | 263 | struct net_device_stats *b) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 264 | { | 
| Thomas Graf | b60c511 | 2006-08-04 23:05:34 -0700 | [diff] [blame] | 265 | a->rx_packets = b->rx_packets; | 
|  | 266 | a->tx_packets = b->tx_packets; | 
|  | 267 | a->rx_bytes = b->rx_bytes; | 
|  | 268 | a->tx_bytes = b->tx_bytes; | 
|  | 269 | a->rx_errors = b->rx_errors; | 
|  | 270 | a->tx_errors = b->tx_errors; | 
|  | 271 | a->rx_dropped = b->rx_dropped; | 
|  | 272 | a->tx_dropped = b->tx_dropped; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 273 |  | 
| Thomas Graf | b60c511 | 2006-08-04 23:05:34 -0700 | [diff] [blame] | 274 | a->multicast = b->multicast; | 
|  | 275 | a->collisions = b->collisions; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 276 |  | 
| Thomas Graf | b60c511 | 2006-08-04 23:05:34 -0700 | [diff] [blame] | 277 | a->rx_length_errors = b->rx_length_errors; | 
|  | 278 | a->rx_over_errors = b->rx_over_errors; | 
|  | 279 | a->rx_crc_errors = b->rx_crc_errors; | 
|  | 280 | a->rx_frame_errors = b->rx_frame_errors; | 
|  | 281 | a->rx_fifo_errors = b->rx_fifo_errors; | 
|  | 282 | a->rx_missed_errors = b->rx_missed_errors; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 283 |  | 
| Thomas Graf | b60c511 | 2006-08-04 23:05:34 -0700 | [diff] [blame] | 284 | a->tx_aborted_errors = b->tx_aborted_errors; | 
|  | 285 | a->tx_carrier_errors = b->tx_carrier_errors; | 
|  | 286 | a->tx_fifo_errors = b->tx_fifo_errors; | 
|  | 287 | a->tx_heartbeat_errors = b->tx_heartbeat_errors; | 
|  | 288 | a->tx_window_errors = b->tx_window_errors; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 289 |  | 
| Thomas Graf | b60c511 | 2006-08-04 23:05:34 -0700 | [diff] [blame] | 290 | a->rx_compressed = b->rx_compressed; | 
|  | 291 | a->tx_compressed = b->tx_compressed; | 
|  | 292 | }; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 293 |  | 
| Thomas Graf | 339bf98 | 2006-11-10 14:10:15 -0800 | [diff] [blame] | 294 | static inline size_t if_nlmsg_size(int iwbuflen) | 
|  | 295 | { | 
|  | 296 | return NLMSG_ALIGN(sizeof(struct ifinfomsg)) | 
|  | 297 | + nla_total_size(IFNAMSIZ) /* IFLA_IFNAME */ | 
|  | 298 | + nla_total_size(IFNAMSIZ) /* IFLA_QDISC */ | 
|  | 299 | + nla_total_size(sizeof(struct rtnl_link_ifmap)) | 
|  | 300 | + nla_total_size(sizeof(struct rtnl_link_stats)) | 
|  | 301 | + nla_total_size(MAX_ADDR_LEN) /* IFLA_ADDRESS */ | 
|  | 302 | + nla_total_size(MAX_ADDR_LEN) /* IFLA_BROADCAST */ | 
|  | 303 | + nla_total_size(4) /* IFLA_TXQLEN */ | 
|  | 304 | + nla_total_size(4) /* IFLA_WEIGHT */ | 
|  | 305 | + nla_total_size(4) /* IFLA_MTU */ | 
|  | 306 | + nla_total_size(4) /* IFLA_LINK */ | 
|  | 307 | + nla_total_size(4) /* IFLA_MASTER */ | 
|  | 308 | + nla_total_size(1) /* IFLA_OPERSTATE */ | 
|  | 309 | + nla_total_size(1) /* IFLA_LINKMODE */ | 
|  | 310 | + nla_total_size(iwbuflen); | 
|  | 311 | } | 
|  | 312 |  | 
| Thomas Graf | b60c511 | 2006-08-04 23:05:34 -0700 | [diff] [blame] | 313 | static int rtnl_fill_ifinfo(struct sk_buff *skb, struct net_device *dev, | 
|  | 314 | void *iwbuf, int iwbuflen, int type, u32 pid, | 
|  | 315 | u32 seq, u32 change, unsigned int flags) | 
|  | 316 | { | 
|  | 317 | struct ifinfomsg *ifm; | 
|  | 318 | struct nlmsghdr *nlh; | 
|  | 319 |  | 
|  | 320 | nlh = nlmsg_put(skb, pid, seq, type, sizeof(*ifm), flags); | 
|  | 321 | if (nlh == NULL) | 
| Patrick McHardy | 2693256 | 2007-01-31 23:16:40 -0800 | [diff] [blame] | 322 | return -EMSGSIZE; | 
| Thomas Graf | b60c511 | 2006-08-04 23:05:34 -0700 | [diff] [blame] | 323 |  | 
|  | 324 | ifm = nlmsg_data(nlh); | 
|  | 325 | ifm->ifi_family = AF_UNSPEC; | 
|  | 326 | ifm->__ifi_pad = 0; | 
|  | 327 | ifm->ifi_type = dev->type; | 
|  | 328 | ifm->ifi_index = dev->ifindex; | 
|  | 329 | ifm->ifi_flags = dev_get_flags(dev); | 
|  | 330 | ifm->ifi_change = change; | 
|  | 331 |  | 
|  | 332 | NLA_PUT_STRING(skb, IFLA_IFNAME, dev->name); | 
|  | 333 | NLA_PUT_U32(skb, IFLA_TXQLEN, dev->tx_queue_len); | 
|  | 334 | NLA_PUT_U32(skb, IFLA_WEIGHT, dev->weight); | 
|  | 335 | NLA_PUT_U8(skb, IFLA_OPERSTATE, | 
|  | 336 | netif_running(dev) ? dev->operstate : IF_OPER_DOWN); | 
|  | 337 | NLA_PUT_U8(skb, IFLA_LINKMODE, dev->link_mode); | 
|  | 338 | NLA_PUT_U32(skb, IFLA_MTU, dev->mtu); | 
|  | 339 |  | 
|  | 340 | if (dev->ifindex != dev->iflink) | 
|  | 341 | NLA_PUT_U32(skb, IFLA_LINK, dev->iflink); | 
|  | 342 |  | 
|  | 343 | if (dev->master) | 
|  | 344 | NLA_PUT_U32(skb, IFLA_MASTER, dev->master->ifindex); | 
|  | 345 |  | 
|  | 346 | if (dev->qdisc_sleeping) | 
|  | 347 | NLA_PUT_STRING(skb, IFLA_QDISC, dev->qdisc_sleeping->ops->id); | 
| Stefan Rompf | b00055a | 2006-03-20 17:09:11 -0800 | [diff] [blame] | 348 |  | 
|  | 349 | if (1) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 350 | struct rtnl_link_ifmap map = { | 
|  | 351 | .mem_start   = dev->mem_start, | 
|  | 352 | .mem_end     = dev->mem_end, | 
|  | 353 | .base_addr   = dev->base_addr, | 
|  | 354 | .irq         = dev->irq, | 
|  | 355 | .dma         = dev->dma, | 
|  | 356 | .port        = dev->if_port, | 
|  | 357 | }; | 
| Thomas Graf | b60c511 | 2006-08-04 23:05:34 -0700 | [diff] [blame] | 358 | NLA_PUT(skb, IFLA_MAP, sizeof(map), &map); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 359 | } | 
|  | 360 |  | 
|  | 361 | if (dev->addr_len) { | 
| Thomas Graf | b60c511 | 2006-08-04 23:05:34 -0700 | [diff] [blame] | 362 | NLA_PUT(skb, IFLA_ADDRESS, dev->addr_len, dev->dev_addr); | 
|  | 363 | NLA_PUT(skb, IFLA_BROADCAST, dev->addr_len, dev->broadcast); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 364 | } | 
|  | 365 |  | 
|  | 366 | if (dev->get_stats) { | 
| Thomas Graf | b60c511 | 2006-08-04 23:05:34 -0700 | [diff] [blame] | 367 | struct net_device_stats *stats = dev->get_stats(dev); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 368 | if (stats) { | 
| Thomas Graf | b60c511 | 2006-08-04 23:05:34 -0700 | [diff] [blame] | 369 | struct nlattr *attr; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 370 |  | 
| Thomas Graf | b60c511 | 2006-08-04 23:05:34 -0700 | [diff] [blame] | 371 | attr = nla_reserve(skb, IFLA_STATS, | 
|  | 372 | sizeof(struct rtnl_link_stats)); | 
|  | 373 | if (attr == NULL) | 
|  | 374 | goto nla_put_failure; | 
|  | 375 |  | 
|  | 376 | copy_rtnl_link_stats(nla_data(attr), stats); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 377 | } | 
|  | 378 | } | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 379 |  | 
| Thomas Graf | b60c511 | 2006-08-04 23:05:34 -0700 | [diff] [blame] | 380 | if (iwbuf) | 
|  | 381 | NLA_PUT(skb, IFLA_WIRELESS, iwbuflen, iwbuf); | 
|  | 382 |  | 
|  | 383 | return nlmsg_end(skb, nlh); | 
|  | 384 |  | 
|  | 385 | nla_put_failure: | 
| Patrick McHardy | 2693256 | 2007-01-31 23:16:40 -0800 | [diff] [blame] | 386 | nlmsg_cancel(skb, nlh); | 
|  | 387 | return -EMSGSIZE; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 388 | } | 
|  | 389 |  | 
| Thomas Graf | b60c511 | 2006-08-04 23:05:34 -0700 | [diff] [blame] | 390 | static int rtnl_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 391 | { | 
|  | 392 | int idx; | 
|  | 393 | int s_idx = cb->args[0]; | 
|  | 394 | struct net_device *dev; | 
|  | 395 |  | 
|  | 396 | read_lock(&dev_base_lock); | 
|  | 397 | for (dev=dev_base, idx=0; dev; dev = dev->next, idx++) { | 
|  | 398 | if (idx < s_idx) | 
|  | 399 | continue; | 
| Thomas Graf | b60c511 | 2006-08-04 23:05:34 -0700 | [diff] [blame] | 400 | if (rtnl_fill_ifinfo(skb, dev, NULL, 0, RTM_NEWLINK, | 
|  | 401 | NETLINK_CB(cb->skb).pid, | 
|  | 402 | cb->nlh->nlmsg_seq, 0, NLM_F_MULTI) <= 0) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 403 | break; | 
|  | 404 | } | 
|  | 405 | read_unlock(&dev_base_lock); | 
|  | 406 | cb->args[0] = idx; | 
|  | 407 |  | 
|  | 408 | return skb->len; | 
|  | 409 | } | 
|  | 410 |  | 
| Thomas Graf | da5e049 | 2006-08-10 21:17:37 -0700 | [diff] [blame] | 411 | static struct nla_policy ifla_policy[IFLA_MAX+1] __read_mostly = { | 
| Thomas Graf | 5176f91 | 2006-08-26 20:13:18 -0700 | [diff] [blame] | 412 | [IFLA_IFNAME]		= { .type = NLA_STRING, .len = IFNAMSIZ-1 }, | 
|  | 413 | [IFLA_MAP]		= { .len = sizeof(struct rtnl_link_ifmap) }, | 
| Thomas Graf | da5e049 | 2006-08-10 21:17:37 -0700 | [diff] [blame] | 414 | [IFLA_MTU]		= { .type = NLA_U32 }, | 
|  | 415 | [IFLA_TXQLEN]		= { .type = NLA_U32 }, | 
|  | 416 | [IFLA_WEIGHT]		= { .type = NLA_U32 }, | 
|  | 417 | [IFLA_OPERSTATE]	= { .type = NLA_U8 }, | 
|  | 418 | [IFLA_LINKMODE]		= { .type = NLA_U8 }, | 
|  | 419 | }; | 
|  | 420 |  | 
|  | 421 | static int rtnl_setlink(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 422 | { | 
| Thomas Graf | da5e049 | 2006-08-10 21:17:37 -0700 | [diff] [blame] | 423 | struct ifinfomsg *ifm; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 424 | struct net_device *dev; | 
| Thomas Graf | da5e049 | 2006-08-10 21:17:37 -0700 | [diff] [blame] | 425 | int err, send_addr_notify = 0, modified = 0; | 
|  | 426 | struct nlattr *tb[IFLA_MAX+1]; | 
|  | 427 | char ifname[IFNAMSIZ]; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 428 |  | 
| Thomas Graf | da5e049 | 2006-08-10 21:17:37 -0700 | [diff] [blame] | 429 | err = nlmsg_parse(nlh, sizeof(*ifm), tb, IFLA_MAX, ifla_policy); | 
|  | 430 | if (err < 0) | 
|  | 431 | goto errout; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 432 |  | 
| Thomas Graf | 5176f91 | 2006-08-26 20:13:18 -0700 | [diff] [blame] | 433 | if (tb[IFLA_IFNAME]) | 
|  | 434 | nla_strlcpy(ifname, tb[IFLA_IFNAME], IFNAMSIZ); | 
| Patrick McHardy | 78e5b891 | 2006-09-13 20:35:36 -0700 | [diff] [blame] | 435 | else | 
|  | 436 | ifname[0] = '\0'; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 437 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 438 | err = -EINVAL; | 
| Thomas Graf | da5e049 | 2006-08-10 21:17:37 -0700 | [diff] [blame] | 439 | ifm = nlmsg_data(nlh); | 
|  | 440 | if (ifm->ifi_index >= 0) | 
|  | 441 | dev = dev_get_by_index(ifm->ifi_index); | 
|  | 442 | else if (tb[IFLA_IFNAME]) | 
|  | 443 | dev = dev_get_by_name(ifname); | 
|  | 444 | else | 
|  | 445 | goto errout; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 446 |  | 
| Thomas Graf | da5e049 | 2006-08-10 21:17:37 -0700 | [diff] [blame] | 447 | if (dev == NULL) { | 
|  | 448 | err = -ENODEV; | 
|  | 449 | goto errout; | 
|  | 450 | } | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 451 |  | 
| Thomas Graf | da5e049 | 2006-08-10 21:17:37 -0700 | [diff] [blame] | 452 | if (tb[IFLA_ADDRESS] && | 
|  | 453 | nla_len(tb[IFLA_ADDRESS]) < dev->addr_len) | 
|  | 454 | goto errout_dev; | 
|  | 455 |  | 
|  | 456 | if (tb[IFLA_BROADCAST] && | 
|  | 457 | nla_len(tb[IFLA_BROADCAST]) < dev->addr_len) | 
|  | 458 | goto errout_dev; | 
|  | 459 |  | 
|  | 460 | if (tb[IFLA_MAP]) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 461 | struct rtnl_link_ifmap *u_map; | 
|  | 462 | struct ifmap k_map; | 
|  | 463 |  | 
|  | 464 | if (!dev->set_config) { | 
|  | 465 | err = -EOPNOTSUPP; | 
| Thomas Graf | da5e049 | 2006-08-10 21:17:37 -0700 | [diff] [blame] | 466 | goto errout_dev; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 467 | } | 
|  | 468 |  | 
|  | 469 | if (!netif_device_present(dev)) { | 
|  | 470 | err = -ENODEV; | 
| Thomas Graf | da5e049 | 2006-08-10 21:17:37 -0700 | [diff] [blame] | 471 | goto errout_dev; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 472 | } | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 473 |  | 
| Thomas Graf | da5e049 | 2006-08-10 21:17:37 -0700 | [diff] [blame] | 474 | u_map = nla_data(tb[IFLA_MAP]); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 475 | k_map.mem_start = (unsigned long) u_map->mem_start; | 
|  | 476 | k_map.mem_end = (unsigned long) u_map->mem_end; | 
|  | 477 | k_map.base_addr = (unsigned short) u_map->base_addr; | 
|  | 478 | k_map.irq = (unsigned char) u_map->irq; | 
|  | 479 | k_map.dma = (unsigned char) u_map->dma; | 
|  | 480 | k_map.port = (unsigned char) u_map->port; | 
|  | 481 |  | 
|  | 482 | err = dev->set_config(dev, &k_map); | 
| Thomas Graf | da5e049 | 2006-08-10 21:17:37 -0700 | [diff] [blame] | 483 | if (err < 0) | 
|  | 484 | goto errout_dev; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 485 |  | 
| Thomas Graf | da5e049 | 2006-08-10 21:17:37 -0700 | [diff] [blame] | 486 | modified = 1; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 487 | } | 
|  | 488 |  | 
| Thomas Graf | da5e049 | 2006-08-10 21:17:37 -0700 | [diff] [blame] | 489 | if (tb[IFLA_ADDRESS]) { | 
| David S. Miller | 70f8e78 | 2006-08-08 16:47:37 -0700 | [diff] [blame] | 490 | struct sockaddr *sa; | 
|  | 491 | int len; | 
|  | 492 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 493 | if (!dev->set_mac_address) { | 
|  | 494 | err = -EOPNOTSUPP; | 
| Thomas Graf | da5e049 | 2006-08-10 21:17:37 -0700 | [diff] [blame] | 495 | goto errout_dev; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 496 | } | 
| Thomas Graf | da5e049 | 2006-08-10 21:17:37 -0700 | [diff] [blame] | 497 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 498 | if (!netif_device_present(dev)) { | 
|  | 499 | err = -ENODEV; | 
| Thomas Graf | da5e049 | 2006-08-10 21:17:37 -0700 | [diff] [blame] | 500 | goto errout_dev; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 501 | } | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 502 |  | 
| David S. Miller | 70f8e78 | 2006-08-08 16:47:37 -0700 | [diff] [blame] | 503 | len = sizeof(sa_family_t) + dev->addr_len; | 
|  | 504 | sa = kmalloc(len, GFP_KERNEL); | 
|  | 505 | if (!sa) { | 
|  | 506 | err = -ENOMEM; | 
| Thomas Graf | da5e049 | 2006-08-10 21:17:37 -0700 | [diff] [blame] | 507 | goto errout_dev; | 
| David S. Miller | 70f8e78 | 2006-08-08 16:47:37 -0700 | [diff] [blame] | 508 | } | 
|  | 509 | sa->sa_family = dev->type; | 
| Thomas Graf | da5e049 | 2006-08-10 21:17:37 -0700 | [diff] [blame] | 510 | memcpy(sa->sa_data, nla_data(tb[IFLA_ADDRESS]), | 
| David S. Miller | 70f8e78 | 2006-08-08 16:47:37 -0700 | [diff] [blame] | 511 | dev->addr_len); | 
|  | 512 | err = dev->set_mac_address(dev, sa); | 
|  | 513 | kfree(sa); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 514 | if (err) | 
| Thomas Graf | da5e049 | 2006-08-10 21:17:37 -0700 | [diff] [blame] | 515 | goto errout_dev; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 516 | send_addr_notify = 1; | 
| Thomas Graf | da5e049 | 2006-08-10 21:17:37 -0700 | [diff] [blame] | 517 | modified = 1; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 518 | } | 
|  | 519 |  | 
| Thomas Graf | da5e049 | 2006-08-10 21:17:37 -0700 | [diff] [blame] | 520 | if (tb[IFLA_MTU]) { | 
|  | 521 | err = dev_set_mtu(dev, nla_get_u32(tb[IFLA_MTU])); | 
|  | 522 | if (err < 0) | 
|  | 523 | goto errout_dev; | 
|  | 524 | modified = 1; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 525 | } | 
|  | 526 |  | 
| Thomas Graf | da5e049 | 2006-08-10 21:17:37 -0700 | [diff] [blame] | 527 | /* | 
|  | 528 | * Interface selected by interface index but interface | 
|  | 529 | * name provided implies that a name change has been | 
|  | 530 | * requested. | 
|  | 531 | */ | 
|  | 532 | if (ifm->ifi_index >= 0 && ifname[0]) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 533 | err = dev_change_name(dev, ifname); | 
| Thomas Graf | da5e049 | 2006-08-10 21:17:37 -0700 | [diff] [blame] | 534 | if (err < 0) | 
|  | 535 | goto errout_dev; | 
|  | 536 | modified = 1; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 537 | } | 
|  | 538 |  | 
| Jean Tourrilhes | 711e2c3 | 2006-02-22 15:10:56 -0800 | [diff] [blame] | 539 | #ifdef CONFIG_NET_WIRELESS_RTNETLINK | 
| Thomas Graf | da5e049 | 2006-08-10 21:17:37 -0700 | [diff] [blame] | 540 | if (tb[IFLA_WIRELESS]) { | 
| Jean Tourrilhes | 711e2c3 | 2006-02-22 15:10:56 -0800 | [diff] [blame] | 541 | /* Call Wireless Extensions. | 
|  | 542 | * Various stuff checked in there... */ | 
| Thomas Graf | da5e049 | 2006-08-10 21:17:37 -0700 | [diff] [blame] | 543 | err = wireless_rtnetlink_set(dev, nla_data(tb[IFLA_WIRELESS]), | 
|  | 544 | nla_len(tb[IFLA_WIRELESS])); | 
|  | 545 | if (err < 0) | 
|  | 546 | goto errout_dev; | 
| Jean Tourrilhes | 711e2c3 | 2006-02-22 15:10:56 -0800 | [diff] [blame] | 547 | } | 
|  | 548 | #endif	/* CONFIG_NET_WIRELESS_RTNETLINK */ | 
|  | 549 |  | 
| Thomas Graf | da5e049 | 2006-08-10 21:17:37 -0700 | [diff] [blame] | 550 | if (tb[IFLA_BROADCAST]) { | 
|  | 551 | nla_memcpy(dev->broadcast, tb[IFLA_BROADCAST], dev->addr_len); | 
|  | 552 | send_addr_notify = 1; | 
|  | 553 | } | 
|  | 554 |  | 
|  | 555 |  | 
|  | 556 | if (ifm->ifi_flags) | 
|  | 557 | dev_change_flags(dev, ifm->ifi_flags); | 
|  | 558 |  | 
|  | 559 | if (tb[IFLA_TXQLEN]) | 
|  | 560 | dev->tx_queue_len = nla_get_u32(tb[IFLA_TXQLEN]); | 
|  | 561 |  | 
|  | 562 | if (tb[IFLA_WEIGHT]) | 
|  | 563 | dev->weight = nla_get_u32(tb[IFLA_WEIGHT]); | 
|  | 564 |  | 
|  | 565 | if (tb[IFLA_OPERSTATE]) | 
|  | 566 | set_operstate(dev, nla_get_u8(tb[IFLA_OPERSTATE])); | 
|  | 567 |  | 
|  | 568 | if (tb[IFLA_LINKMODE]) { | 
|  | 569 | write_lock_bh(&dev_base_lock); | 
|  | 570 | dev->link_mode = nla_get_u8(tb[IFLA_LINKMODE]); | 
|  | 571 | write_unlock_bh(&dev_base_lock); | 
|  | 572 | } | 
|  | 573 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 574 | err = 0; | 
|  | 575 |  | 
| Thomas Graf | da5e049 | 2006-08-10 21:17:37 -0700 | [diff] [blame] | 576 | errout_dev: | 
|  | 577 | if (err < 0 && modified && net_ratelimit()) | 
|  | 578 | printk(KERN_WARNING "A link change request failed with " | 
|  | 579 | "some changes comitted already. Interface %s may " | 
|  | 580 | "have been left with an inconsistent configuration, " | 
|  | 581 | "please check.\n", dev->name); | 
|  | 582 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 583 | if (send_addr_notify) | 
|  | 584 | call_netdevice_notifiers(NETDEV_CHANGEADDR, dev); | 
|  | 585 |  | 
|  | 586 | dev_put(dev); | 
| Thomas Graf | da5e049 | 2006-08-10 21:17:37 -0700 | [diff] [blame] | 587 | errout: | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 588 | return err; | 
|  | 589 | } | 
|  | 590 |  | 
| Thomas Graf | b60c511 | 2006-08-04 23:05:34 -0700 | [diff] [blame] | 591 | static int rtnl_getlink(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg) | 
| Jean Tourrilhes | 711e2c3 | 2006-02-22 15:10:56 -0800 | [diff] [blame] | 592 | { | 
| Thomas Graf | b60c511 | 2006-08-04 23:05:34 -0700 | [diff] [blame] | 593 | struct ifinfomsg *ifm; | 
|  | 594 | struct nlattr *tb[IFLA_MAX+1]; | 
|  | 595 | struct net_device *dev = NULL; | 
|  | 596 | struct sk_buff *nskb; | 
|  | 597 | char *iw_buf = NULL, *iw = NULL; | 
| Jean Tourrilhes | 711e2c3 | 2006-02-22 15:10:56 -0800 | [diff] [blame] | 598 | int iw_buf_len = 0; | 
| Thomas Graf | 339bf98 | 2006-11-10 14:10:15 -0800 | [diff] [blame] | 599 | int err; | 
| Jean Tourrilhes | 711e2c3 | 2006-02-22 15:10:56 -0800 | [diff] [blame] | 600 |  | 
| Thomas Graf | b60c511 | 2006-08-04 23:05:34 -0700 | [diff] [blame] | 601 | err = nlmsg_parse(nlh, sizeof(*ifm), tb, IFLA_MAX, ifla_policy); | 
|  | 602 | if (err < 0) | 
| Eric Sesterhenn | 9918f23 | 2006-09-26 23:26:38 -0700 | [diff] [blame] | 603 | return err; | 
| Thomas Graf | b60c511 | 2006-08-04 23:05:34 -0700 | [diff] [blame] | 604 |  | 
|  | 605 | ifm = nlmsg_data(nlh); | 
|  | 606 | if (ifm->ifi_index >= 0) { | 
| Jean Tourrilhes | 711e2c3 | 2006-02-22 15:10:56 -0800 | [diff] [blame] | 607 | dev = dev_get_by_index(ifm->ifi_index); | 
| Thomas Graf | b60c511 | 2006-08-04 23:05:34 -0700 | [diff] [blame] | 608 | if (dev == NULL) | 
|  | 609 | return -ENODEV; | 
|  | 610 | } else | 
| Jean Tourrilhes | 711e2c3 | 2006-02-22 15:10:56 -0800 | [diff] [blame] | 611 | return -EINVAL; | 
| Thomas Graf | b60c511 | 2006-08-04 23:05:34 -0700 | [diff] [blame] | 612 |  | 
| Jean Tourrilhes | 711e2c3 | 2006-02-22 15:10:56 -0800 | [diff] [blame] | 613 |  | 
|  | 614 | #ifdef CONFIG_NET_WIRELESS_RTNETLINK | 
| Thomas Graf | b60c511 | 2006-08-04 23:05:34 -0700 | [diff] [blame] | 615 | if (tb[IFLA_WIRELESS]) { | 
| Jean Tourrilhes | 711e2c3 | 2006-02-22 15:10:56 -0800 | [diff] [blame] | 616 | /* Call Wireless Extensions. We need to know the size before | 
|  | 617 | * we can alloc. Various stuff checked in there... */ | 
| Thomas Graf | b60c511 | 2006-08-04 23:05:34 -0700 | [diff] [blame] | 618 | err = wireless_rtnetlink_get(dev, nla_data(tb[IFLA_WIRELESS]), | 
|  | 619 | nla_len(tb[IFLA_WIRELESS]), | 
|  | 620 | &iw_buf, &iw_buf_len); | 
|  | 621 | if (err < 0) | 
|  | 622 | goto errout; | 
|  | 623 |  | 
|  | 624 | iw += IW_EV_POINT_OFF; | 
| Jean Tourrilhes | 711e2c3 | 2006-02-22 15:10:56 -0800 | [diff] [blame] | 625 | } | 
|  | 626 | #endif	/* CONFIG_NET_WIRELESS_RTNETLINK */ | 
|  | 627 |  | 
| Thomas Graf | 339bf98 | 2006-11-10 14:10:15 -0800 | [diff] [blame] | 628 | nskb = nlmsg_new(if_nlmsg_size(iw_buf_len), GFP_KERNEL); | 
| Thomas Graf | b60c511 | 2006-08-04 23:05:34 -0700 | [diff] [blame] | 629 | if (nskb == NULL) { | 
|  | 630 | err = -ENOBUFS; | 
|  | 631 | goto errout; | 
|  | 632 | } | 
| Jean Tourrilhes | 711e2c3 | 2006-02-22 15:10:56 -0800 | [diff] [blame] | 633 |  | 
| Thomas Graf | b60c511 | 2006-08-04 23:05:34 -0700 | [diff] [blame] | 634 | err = rtnl_fill_ifinfo(nskb, dev, iw, iw_buf_len, RTM_NEWLINK, | 
|  | 635 | NETLINK_CB(skb).pid, nlh->nlmsg_seq, 0, 0); | 
| Patrick McHardy | 2693256 | 2007-01-31 23:16:40 -0800 | [diff] [blame] | 636 | if (err < 0) { | 
|  | 637 | /* -EMSGSIZE implies BUG in if_nlmsg_size */ | 
|  | 638 | WARN_ON(err == -EMSGSIZE); | 
|  | 639 | kfree_skb(nskb); | 
|  | 640 | goto errout; | 
|  | 641 | } | 
| Patrick McHardy | b974179 | 2006-10-12 01:50:30 -0700 | [diff] [blame] | 642 | err = rtnl_unicast(nskb, NETLINK_CB(skb).pid); | 
| Thomas Graf | b60c511 | 2006-08-04 23:05:34 -0700 | [diff] [blame] | 643 | errout: | 
|  | 644 | kfree(iw_buf); | 
| Jean Tourrilhes | 711e2c3 | 2006-02-22 15:10:56 -0800 | [diff] [blame] | 645 | dev_put(dev); | 
| Thomas Graf | b60c511 | 2006-08-04 23:05:34 -0700 | [diff] [blame] | 646 |  | 
| Jean Tourrilhes | 711e2c3 | 2006-02-22 15:10:56 -0800 | [diff] [blame] | 647 | return err; | 
| Jean Tourrilhes | 711e2c3 | 2006-02-22 15:10:56 -0800 | [diff] [blame] | 648 | } | 
| Jean Tourrilhes | 711e2c3 | 2006-02-22 15:10:56 -0800 | [diff] [blame] | 649 |  | 
| Thomas Graf | b60c511 | 2006-08-04 23:05:34 -0700 | [diff] [blame] | 650 | static int rtnl_dump_all(struct sk_buff *skb, struct netlink_callback *cb) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 651 | { | 
|  | 652 | int idx; | 
|  | 653 | int s_idx = cb->family; | 
|  | 654 |  | 
|  | 655 | if (s_idx == 0) | 
|  | 656 | s_idx = 1; | 
|  | 657 | for (idx=1; idx<NPROTO; idx++) { | 
|  | 658 | int type = cb->nlh->nlmsg_type-RTM_BASE; | 
|  | 659 | if (idx < s_idx || idx == PF_PACKET) | 
|  | 660 | continue; | 
|  | 661 | if (rtnetlink_links[idx] == NULL || | 
|  | 662 | rtnetlink_links[idx][type].dumpit == NULL) | 
|  | 663 | continue; | 
|  | 664 | if (idx > s_idx) | 
|  | 665 | memset(&cb->args[0], 0, sizeof(cb->args)); | 
|  | 666 | if (rtnetlink_links[idx][type].dumpit(skb, cb)) | 
|  | 667 | break; | 
|  | 668 | } | 
|  | 669 | cb->family = idx; | 
|  | 670 |  | 
|  | 671 | return skb->len; | 
|  | 672 | } | 
|  | 673 |  | 
|  | 674 | void rtmsg_ifinfo(int type, struct net_device *dev, unsigned change) | 
|  | 675 | { | 
|  | 676 | struct sk_buff *skb; | 
| Thomas Graf | 0ec6d3f | 2006-08-15 00:37:09 -0700 | [diff] [blame] | 677 | int err = -ENOBUFS; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 678 |  | 
| Thomas Graf | 339bf98 | 2006-11-10 14:10:15 -0800 | [diff] [blame] | 679 | skb = nlmsg_new(if_nlmsg_size(0), GFP_KERNEL); | 
| Thomas Graf | 0ec6d3f | 2006-08-15 00:37:09 -0700 | [diff] [blame] | 680 | if (skb == NULL) | 
|  | 681 | goto errout; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 682 |  | 
| Thomas Graf | 0ec6d3f | 2006-08-15 00:37:09 -0700 | [diff] [blame] | 683 | err = rtnl_fill_ifinfo(skb, dev, NULL, 0, type, 0, 0, change, 0); | 
| Patrick McHardy | 2693256 | 2007-01-31 23:16:40 -0800 | [diff] [blame] | 684 | if (err < 0) { | 
|  | 685 | /* -EMSGSIZE implies BUG in if_nlmsg_size() */ | 
|  | 686 | WARN_ON(err == -EMSGSIZE); | 
|  | 687 | kfree_skb(skb); | 
|  | 688 | goto errout; | 
|  | 689 | } | 
| Thomas Graf | 0ec6d3f | 2006-08-15 00:37:09 -0700 | [diff] [blame] | 690 | err = rtnl_notify(skb, 0, RTNLGRP_LINK, NULL, GFP_KERNEL); | 
|  | 691 | errout: | 
|  | 692 | if (err < 0) | 
|  | 693 | rtnl_set_sk_err(RTNLGRP_LINK, err); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 694 | } | 
|  | 695 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 696 | /* Protected by RTNL sempahore.  */ | 
|  | 697 | static struct rtattr **rta_buf; | 
|  | 698 | static int rtattr_max; | 
|  | 699 |  | 
|  | 700 | /* Process one rtnetlink message. */ | 
|  | 701 |  | 
|  | 702 | static __inline__ int | 
|  | 703 | rtnetlink_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, int *errp) | 
|  | 704 | { | 
|  | 705 | struct rtnetlink_link *link; | 
|  | 706 | struct rtnetlink_link *link_tab; | 
|  | 707 | int sz_idx, kind; | 
|  | 708 | int min_len; | 
|  | 709 | int family; | 
|  | 710 | int type; | 
|  | 711 | int err; | 
|  | 712 |  | 
|  | 713 | /* Only requests are handled by kernel now */ | 
|  | 714 | if (!(nlh->nlmsg_flags&NLM_F_REQUEST)) | 
|  | 715 | return 0; | 
|  | 716 |  | 
|  | 717 | type = nlh->nlmsg_type; | 
|  | 718 |  | 
|  | 719 | /* A control message: ignore them */ | 
|  | 720 | if (type < RTM_BASE) | 
|  | 721 | return 0; | 
|  | 722 |  | 
|  | 723 | /* Unknown message: reply with EINVAL */ | 
|  | 724 | if (type > RTM_MAX) | 
|  | 725 | goto err_inval; | 
|  | 726 |  | 
|  | 727 | type -= RTM_BASE; | 
|  | 728 |  | 
|  | 729 | /* All the messages must have at least 1 byte length */ | 
|  | 730 | if (nlh->nlmsg_len < NLMSG_LENGTH(sizeof(struct rtgenmsg))) | 
|  | 731 | return 0; | 
|  | 732 |  | 
|  | 733 | family = ((struct rtgenmsg*)NLMSG_DATA(nlh))->rtgen_family; | 
|  | 734 | if (family >= NPROTO) { | 
|  | 735 | *errp = -EAFNOSUPPORT; | 
|  | 736 | return -1; | 
|  | 737 | } | 
|  | 738 |  | 
|  | 739 | link_tab = rtnetlink_links[family]; | 
|  | 740 | if (link_tab == NULL) | 
|  | 741 | link_tab = rtnetlink_links[PF_UNSPEC]; | 
|  | 742 | link = &link_tab[type]; | 
|  | 743 |  | 
|  | 744 | sz_idx = type>>2; | 
|  | 745 | kind = type&3; | 
|  | 746 |  | 
| Darrel Goeddel | c7bdb54 | 2006-06-27 13:26:11 -0700 | [diff] [blame] | 747 | if (kind != 2 && security_netlink_recv(skb, CAP_NET_ADMIN)) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 748 | *errp = -EPERM; | 
|  | 749 | return -1; | 
|  | 750 | } | 
|  | 751 |  | 
|  | 752 | if (kind == 2 && nlh->nlmsg_flags&NLM_F_DUMP) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 753 | if (link->dumpit == NULL) | 
|  | 754 | link = &(rtnetlink_links[PF_UNSPEC][type]); | 
|  | 755 |  | 
|  | 756 | if (link->dumpit == NULL) | 
|  | 757 | goto err_inval; | 
|  | 758 |  | 
|  | 759 | if ((*errp = netlink_dump_start(rtnl, skb, nlh, | 
| Thomas Graf | a8f74b2 | 2005-11-10 02:25:52 +0100 | [diff] [blame] | 760 | link->dumpit, NULL)) != 0) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 761 | return -1; | 
|  | 762 | } | 
| Thomas Graf | 9ac4a16 | 2005-11-10 02:25:55 +0100 | [diff] [blame] | 763 |  | 
|  | 764 | netlink_queue_skip(nlh, skb); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 765 | return -1; | 
|  | 766 | } | 
|  | 767 |  | 
|  | 768 | memset(rta_buf, 0, (rtattr_max * sizeof(struct rtattr *))); | 
|  | 769 |  | 
|  | 770 | min_len = rtm_min[sz_idx]; | 
|  | 771 | if (nlh->nlmsg_len < min_len) | 
|  | 772 | goto err_inval; | 
|  | 773 |  | 
|  | 774 | if (nlh->nlmsg_len > min_len) { | 
|  | 775 | int attrlen = nlh->nlmsg_len - NLMSG_ALIGN(min_len); | 
|  | 776 | struct rtattr *attr = (void*)nlh + NLMSG_ALIGN(min_len); | 
|  | 777 |  | 
|  | 778 | while (RTA_OK(attr, attrlen)) { | 
|  | 779 | unsigned flavor = attr->rta_type; | 
|  | 780 | if (flavor) { | 
|  | 781 | if (flavor > rta_max[sz_idx]) | 
|  | 782 | goto err_inval; | 
|  | 783 | rta_buf[flavor-1] = attr; | 
|  | 784 | } | 
|  | 785 | attr = RTA_NEXT(attr, attrlen); | 
|  | 786 | } | 
|  | 787 | } | 
|  | 788 |  | 
|  | 789 | if (link->doit == NULL) | 
|  | 790 | link = &(rtnetlink_links[PF_UNSPEC][type]); | 
|  | 791 | if (link->doit == NULL) | 
|  | 792 | goto err_inval; | 
|  | 793 | err = link->doit(skb, nlh, (void *)&rta_buf[0]); | 
|  | 794 |  | 
|  | 795 | *errp = err; | 
|  | 796 | return err; | 
|  | 797 |  | 
|  | 798 | err_inval: | 
|  | 799 | *errp = -EINVAL; | 
|  | 800 | return -1; | 
|  | 801 | } | 
|  | 802 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 803 | static void rtnetlink_rcv(struct sock *sk, int len) | 
|  | 804 | { | 
| Thomas Graf | 9ac4a16 | 2005-11-10 02:25:55 +0100 | [diff] [blame] | 805 | unsigned int qlen = 0; | 
| Herbert Xu | 2a0a6eb | 2005-05-03 14:55:09 -0700 | [diff] [blame] | 806 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 807 | do { | 
| Stephen Hemminger | 6756ae4 | 2006-03-20 22:23:58 -0800 | [diff] [blame] | 808 | mutex_lock(&rtnl_mutex); | 
| Thomas Graf | 9ac4a16 | 2005-11-10 02:25:55 +0100 | [diff] [blame] | 809 | netlink_run_queue(sk, &qlen, &rtnetlink_rcv_msg); | 
| Stephen Hemminger | 6756ae4 | 2006-03-20 22:23:58 -0800 | [diff] [blame] | 810 | mutex_unlock(&rtnl_mutex); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 811 |  | 
|  | 812 | netdev_run_todo(); | 
| Herbert Xu | 2a0a6eb | 2005-05-03 14:55:09 -0700 | [diff] [blame] | 813 | } while (qlen); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 814 | } | 
|  | 815 |  | 
| Thomas Graf | db46edc | 2005-05-03 14:29:39 -0700 | [diff] [blame] | 816 | static struct rtnetlink_link link_rtnetlink_table[RTM_NR_MSGTYPES] = | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 817 | { | 
| Thomas Graf | b60c511 | 2006-08-04 23:05:34 -0700 | [diff] [blame] | 818 | [RTM_GETLINK     - RTM_BASE] = { .doit   = rtnl_getlink, | 
|  | 819 | .dumpit = rtnl_dump_ifinfo	 }, | 
| Thomas Graf | da5e049 | 2006-08-10 21:17:37 -0700 | [diff] [blame] | 820 | [RTM_SETLINK     - RTM_BASE] = { .doit   = rtnl_setlink		 }, | 
| Thomas Graf | b60c511 | 2006-08-04 23:05:34 -0700 | [diff] [blame] | 821 | [RTM_GETADDR     - RTM_BASE] = { .dumpit = rtnl_dump_all	 }, | 
|  | 822 | [RTM_GETROUTE    - RTM_BASE] = { .dumpit = rtnl_dump_all	 }, | 
| Thomas Graf | c7fb64d | 2005-06-18 22:50:55 -0700 | [diff] [blame] | 823 | [RTM_NEWNEIGH    - RTM_BASE] = { .doit   = neigh_add		 }, | 
|  | 824 | [RTM_DELNEIGH    - RTM_BASE] = { .doit   = neigh_delete		 }, | 
|  | 825 | [RTM_GETNEIGH    - RTM_BASE] = { .dumpit = neigh_dump_info	 }, | 
| Thomas Graf | 14c0b97 | 2006-08-04 03:38:38 -0700 | [diff] [blame] | 826 | #ifdef CONFIG_FIB_RULES | 
|  | 827 | [RTM_NEWRULE     - RTM_BASE] = { .doit   = fib_nl_newrule	 }, | 
|  | 828 | [RTM_DELRULE     - RTM_BASE] = { .doit   = fib_nl_delrule	 }, | 
|  | 829 | #endif | 
| Thomas Graf | b60c511 | 2006-08-04 23:05:34 -0700 | [diff] [blame] | 830 | [RTM_GETRULE     - RTM_BASE] = { .dumpit = rtnl_dump_all	 }, | 
| Thomas Graf | c7fb64d | 2005-06-18 22:50:55 -0700 | [diff] [blame] | 831 | [RTM_GETNEIGHTBL - RTM_BASE] = { .dumpit = neightbl_dump_info	 }, | 
|  | 832 | [RTM_SETNEIGHTBL - RTM_BASE] = { .doit   = neightbl_set		 }, | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 833 | }; | 
|  | 834 |  | 
|  | 835 | static int rtnetlink_event(struct notifier_block *this, unsigned long event, void *ptr) | 
|  | 836 | { | 
|  | 837 | struct net_device *dev = ptr; | 
|  | 838 | switch (event) { | 
|  | 839 | case NETDEV_UNREGISTER: | 
|  | 840 | rtmsg_ifinfo(RTM_DELLINK, dev, ~0U); | 
|  | 841 | break; | 
|  | 842 | case NETDEV_REGISTER: | 
|  | 843 | rtmsg_ifinfo(RTM_NEWLINK, dev, ~0U); | 
|  | 844 | break; | 
|  | 845 | case NETDEV_UP: | 
|  | 846 | case NETDEV_DOWN: | 
|  | 847 | rtmsg_ifinfo(RTM_NEWLINK, dev, IFF_UP|IFF_RUNNING); | 
|  | 848 | break; | 
|  | 849 | case NETDEV_CHANGE: | 
|  | 850 | case NETDEV_GOING_DOWN: | 
|  | 851 | break; | 
|  | 852 | default: | 
|  | 853 | rtmsg_ifinfo(RTM_NEWLINK, dev, 0); | 
|  | 854 | break; | 
|  | 855 | } | 
|  | 856 | return NOTIFY_DONE; | 
|  | 857 | } | 
|  | 858 |  | 
|  | 859 | static struct notifier_block rtnetlink_dev_notifier = { | 
|  | 860 | .notifier_call	= rtnetlink_event, | 
|  | 861 | }; | 
|  | 862 |  | 
|  | 863 | void __init rtnetlink_init(void) | 
|  | 864 | { | 
|  | 865 | int i; | 
|  | 866 |  | 
|  | 867 | rtattr_max = 0; | 
|  | 868 | for (i = 0; i < ARRAY_SIZE(rta_max); i++) | 
|  | 869 | if (rta_max[i] > rtattr_max) | 
|  | 870 | rtattr_max = rta_max[i]; | 
|  | 871 | rta_buf = kmalloc(rtattr_max * sizeof(struct rtattr *), GFP_KERNEL); | 
|  | 872 | if (!rta_buf) | 
|  | 873 | panic("rtnetlink_init: cannot allocate rta_buf\n"); | 
|  | 874 |  | 
| Patrick McHardy | 0662860 | 2005-08-15 12:33:26 -0700 | [diff] [blame] | 875 | rtnl = netlink_kernel_create(NETLINK_ROUTE, RTNLGRP_MAX, rtnetlink_rcv, | 
| YOSHIFUJI Hideaki | 4ec93ed | 2007-02-09 23:24:36 +0900 | [diff] [blame] | 876 | THIS_MODULE); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 877 | if (rtnl == NULL) | 
|  | 878 | panic("rtnetlink_init: cannot initialize rtnetlink\n"); | 
|  | 879 | netlink_set_nonroot(NETLINK_ROUTE, NL_NONROOT_RECV); | 
|  | 880 | register_netdevice_notifier(&rtnetlink_dev_notifier); | 
|  | 881 | rtnetlink_links[PF_UNSPEC] = link_rtnetlink_table; | 
|  | 882 | rtnetlink_links[PF_PACKET] = link_rtnetlink_table; | 
|  | 883 | } | 
|  | 884 |  | 
|  | 885 | EXPORT_SYMBOL(__rta_fill); | 
|  | 886 | EXPORT_SYMBOL(rtattr_strlcpy); | 
|  | 887 | EXPORT_SYMBOL(rtattr_parse); | 
|  | 888 | EXPORT_SYMBOL(rtnetlink_links); | 
|  | 889 | EXPORT_SYMBOL(rtnetlink_put_metrics); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 890 | EXPORT_SYMBOL(rtnl_lock); | 
| Stephen Hemminger | 6756ae4 | 2006-03-20 22:23:58 -0800 | [diff] [blame] | 891 | EXPORT_SYMBOL(rtnl_trylock); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 892 | EXPORT_SYMBOL(rtnl_unlock); | 
| Thomas Graf | 2942e90 | 2006-08-15 00:30:25 -0700 | [diff] [blame] | 893 | EXPORT_SYMBOL(rtnl_unicast); | 
| Thomas Graf | 97676b6 | 2006-08-15 00:31:41 -0700 | [diff] [blame] | 894 | EXPORT_SYMBOL(rtnl_notify); | 
|  | 895 | EXPORT_SYMBOL(rtnl_set_sk_err); |