linux/net/phonet/pn_netlink.c
<<
>>
Prefs
   1/*
   2 * File: pn_netlink.c
   3 *
   4 * Phonet netlink interface
   5 *
   6 * Copyright (C) 2008 Nokia Corporation.
   7 *
   8 * Contact: Remi Denis-Courmont <remi.denis-courmont@nokia.com>
   9 * Original author: Sakari Ailus <sakari.ailus@nokia.com>
  10 *
  11 * This program is free software; you can redistribute it and/or
  12 * modify it under the terms of the GNU General Public License
  13 * version 2 as published by the Free Software Foundation.
  14 *
  15 * This program is distributed in the hope that it will be useful, but
  16 * WITHOUT ANY WARRANTY; without even the implied warranty of
  17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  18 * General Public License for more details.
  19 *
  20 * You should have received a copy of the GNU General Public License
  21 * along with this program; if not, write to the Free Software
  22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
  23 * 02110-1301 USA
  24 */
  25
  26#include <linux/kernel.h>
  27#include <linux/netlink.h>
  28#include <linux/phonet.h>
  29#include <net/sock.h>
  30#include <net/phonet/pn_dev.h>
  31
  32static int fill_addr(struct sk_buff *skb, struct net_device *dev, u8 addr,
  33                     u32 pid, u32 seq, int event);
  34
  35void phonet_address_notify(int event, struct net_device *dev, u8 addr)
  36{
  37        struct sk_buff *skb;
  38        int err = -ENOBUFS;
  39
  40        skb = nlmsg_new(NLMSG_ALIGN(sizeof(struct ifaddrmsg)) +
  41                        nla_total_size(1), GFP_KERNEL);
  42        if (skb == NULL)
  43                goto errout;
  44        err = fill_addr(skb, dev, addr, 0, 0, event);
  45        if (err < 0) {
  46                WARN_ON(err == -EMSGSIZE);
  47                kfree_skb(skb);
  48                goto errout;
  49        }
  50        rtnl_notify(skb, dev_net(dev), 0,
  51                    RTNLGRP_PHONET_IFADDR, NULL, GFP_KERNEL);
  52        return;
  53errout:
  54        if (err < 0)
  55                rtnl_set_sk_err(dev_net(dev), RTNLGRP_PHONET_IFADDR, err);
  56}
  57
  58static const struct nla_policy ifa_phonet_policy[IFA_MAX+1] = {
  59        [IFA_LOCAL] = { .type = NLA_U8 },
  60};
  61
  62static int addr_doit(struct sk_buff *skb, struct nlmsghdr *nlh, void *attr)
  63{
  64        struct net *net = sock_net(skb->sk);
  65        struct nlattr *tb[IFA_MAX+1];
  66        struct net_device *dev;
  67        struct ifaddrmsg *ifm;
  68        int err;
  69        u8 pnaddr;
  70
  71        if (!capable(CAP_SYS_ADMIN))
  72                return -EPERM;
  73
  74        ASSERT_RTNL();
  75
  76        err = nlmsg_parse(nlh, sizeof(*ifm), tb, IFA_MAX, ifa_phonet_policy);
  77        if (err < 0)
  78                return err;
  79
  80        ifm = nlmsg_data(nlh);
  81        if (tb[IFA_LOCAL] == NULL)
  82                return -EINVAL;
  83        pnaddr = nla_get_u8(tb[IFA_LOCAL]);
  84        if (pnaddr & 3)
  85                /* Phonet addresses only have 6 high-order bits */
  86                return -EINVAL;
  87
  88        dev = __dev_get_by_index(net, ifm->ifa_index);
  89        if (dev == NULL)
  90                return -ENODEV;
  91
  92        if (nlh->nlmsg_type == RTM_NEWADDR)
  93                err = phonet_address_add(dev, pnaddr);
  94        else
  95                err = phonet_address_del(dev, pnaddr);
  96        if (!err)
  97                phonet_address_notify(nlh->nlmsg_type, dev, pnaddr);
  98        return err;
  99}
 100
 101static int fill_addr(struct sk_buff *skb, struct net_device *dev, u8 addr,
 102                        u32 pid, u32 seq, int event)
 103{
 104        struct ifaddrmsg *ifm;
 105        struct nlmsghdr *nlh;
 106
 107        nlh = nlmsg_put(skb, pid, seq, event, sizeof(*ifm), 0);
 108        if (nlh == NULL)
 109                return -EMSGSIZE;
 110
 111        ifm = nlmsg_data(nlh);
 112        ifm->ifa_family = AF_PHONET;
 113        ifm->ifa_prefixlen = 0;
 114        ifm->ifa_flags = IFA_F_PERMANENT;
 115        ifm->ifa_scope = RT_SCOPE_LINK;
 116        ifm->ifa_index = dev->ifindex;
 117        NLA_PUT_U8(skb, IFA_LOCAL, addr);
 118        return nlmsg_end(skb, nlh);
 119
 120nla_put_failure:
 121        nlmsg_cancel(skb, nlh);
 122        return -EMSGSIZE;
 123}
 124
 125static int getaddr_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
 126{
 127        struct phonet_device_list *pndevs;
 128        struct phonet_device *pnd;
 129        int dev_idx = 0, dev_start_idx = cb->args[0];
 130        int addr_idx = 0, addr_start_idx = cb->args[1];
 131
 132        pndevs = phonet_device_list(sock_net(skb->sk));
 133        spin_lock_bh(&pndevs->lock);
 134        list_for_each_entry(pnd, &pndevs->list, list) {
 135                u8 addr;
 136
 137                if (dev_idx > dev_start_idx)
 138                        addr_start_idx = 0;
 139                if (dev_idx++ < dev_start_idx)
 140                        continue;
 141
 142                addr_idx = 0;
 143                for (addr = find_first_bit(pnd->addrs, 64); addr < 64;
 144                        addr = find_next_bit(pnd->addrs, 64, 1+addr)) {
 145                        if (addr_idx++ < addr_start_idx)
 146                                continue;
 147
 148                        if (fill_addr(skb, pnd->netdev, addr << 2,
 149                                         NETLINK_CB(cb->skb).pid,
 150                                        cb->nlh->nlmsg_seq, RTM_NEWADDR) < 0)
 151                                goto out;
 152                }
 153        }
 154
 155out:
 156        spin_unlock_bh(&pndevs->lock);
 157        cb->args[0] = dev_idx;
 158        cb->args[1] = addr_idx;
 159
 160        return skb->len;
 161}
 162
 163int __init phonet_netlink_register(void)
 164{
 165        int err = __rtnl_register(PF_PHONET, RTM_NEWADDR, addr_doit, NULL);
 166        if (err)
 167                return err;
 168
 169        /* Further __rtnl_register() cannot fail */
 170        __rtnl_register(PF_PHONET, RTM_DELADDR, addr_doit, NULL);
 171        __rtnl_register(PF_PHONET, RTM_GETADDR, NULL, getaddr_dumpit);
 172        return 0;
 173}
 174