18fb39740SRemi Denis-Courmont /* 28fb39740SRemi Denis-Courmont * File: pn_netlink.c 38fb39740SRemi Denis-Courmont * 48fb39740SRemi Denis-Courmont * Phonet netlink interface 58fb39740SRemi Denis-Courmont * 68fb39740SRemi Denis-Courmont * Copyright (C) 2008 Nokia Corporation. 78fb39740SRemi Denis-Courmont * 831fdc555SRémi Denis-Courmont * Authors: Sakari Ailus <sakari.ailus@nokia.com> 931fdc555SRémi Denis-Courmont * Remi Denis-Courmont 108fb39740SRemi Denis-Courmont * 118fb39740SRemi Denis-Courmont * This program is free software; you can redistribute it and/or 128fb39740SRemi Denis-Courmont * modify it under the terms of the GNU General Public License 138fb39740SRemi Denis-Courmont * version 2 as published by the Free Software Foundation. 148fb39740SRemi Denis-Courmont * 158fb39740SRemi Denis-Courmont * This program is distributed in the hope that it will be useful, but 168fb39740SRemi Denis-Courmont * WITHOUT ANY WARRANTY; without even the implied warranty of 178fb39740SRemi Denis-Courmont * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 188fb39740SRemi Denis-Courmont * General Public License for more details. 198fb39740SRemi Denis-Courmont * 208fb39740SRemi Denis-Courmont * You should have received a copy of the GNU General Public License 218fb39740SRemi Denis-Courmont * along with this program; if not, write to the Free Software 228fb39740SRemi Denis-Courmont * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 238fb39740SRemi Denis-Courmont * 02110-1301 USA 248fb39740SRemi Denis-Courmont */ 258fb39740SRemi Denis-Courmont 268fb39740SRemi Denis-Courmont #include <linux/kernel.h> 278fb39740SRemi Denis-Courmont #include <linux/netlink.h> 288fb39740SRemi Denis-Courmont #include <linux/phonet.h> 295a0e3ad6STejun Heo #include <linux/slab.h> 308fb39740SRemi Denis-Courmont #include <net/sock.h> 318fb39740SRemi Denis-Courmont #include <net/phonet/pn_dev.h> 328fb39740SRemi Denis-Courmont 33f062f41dSRémi Denis-Courmont /* Device address handling */ 34f062f41dSRémi Denis-Courmont 358fb39740SRemi Denis-Courmont static int fill_addr(struct sk_buff *skb, struct net_device *dev, u8 addr, 3615e47304SEric W. Biederman u32 portid, u32 seq, int event); 378fb39740SRemi Denis-Courmont 38c7a1a4c8SRémi Denis-Courmont void phonet_address_notify(int event, struct net_device *dev, u8 addr) 398fb39740SRemi Denis-Courmont { 408fb39740SRemi Denis-Courmont struct sk_buff *skb; 418fb39740SRemi Denis-Courmont int err = -ENOBUFS; 428fb39740SRemi Denis-Courmont 438fb39740SRemi Denis-Courmont skb = nlmsg_new(NLMSG_ALIGN(sizeof(struct ifaddrmsg)) + 448fb39740SRemi Denis-Courmont nla_total_size(1), GFP_KERNEL); 458fb39740SRemi Denis-Courmont if (skb == NULL) 468fb39740SRemi Denis-Courmont goto errout; 478fb39740SRemi Denis-Courmont err = fill_addr(skb, dev, addr, 0, 0, event); 488fb39740SRemi Denis-Courmont if (err < 0) { 498fb39740SRemi Denis-Courmont WARN_ON(err == -EMSGSIZE); 508fb39740SRemi Denis-Courmont kfree_skb(skb); 518fb39740SRemi Denis-Courmont goto errout; 528fb39740SRemi Denis-Courmont } 531ce85fe4SPablo Neira Ayuso rtnl_notify(skb, dev_net(dev), 0, 548fb39740SRemi Denis-Courmont RTNLGRP_PHONET_IFADDR, NULL, GFP_KERNEL); 551ce85fe4SPablo Neira Ayuso return; 568fb39740SRemi Denis-Courmont errout: 578fb39740SRemi Denis-Courmont rtnl_set_sk_err(dev_net(dev), RTNLGRP_PHONET_IFADDR, err); 588fb39740SRemi Denis-Courmont } 598fb39740SRemi Denis-Courmont 608980713bSRémi Denis-Courmont static const struct nla_policy ifa_phonet_policy[IFA_MAX+1] = { 618980713bSRémi Denis-Courmont [IFA_LOCAL] = { .type = NLA_U8 }, 628980713bSRémi Denis-Courmont }; 638980713bSRémi Denis-Courmont 64661d2967SThomas Graf static int addr_doit(struct sk_buff *skb, struct nlmsghdr *nlh) 658fb39740SRemi Denis-Courmont { 668980713bSRémi Denis-Courmont struct net *net = sock_net(skb->sk); 678980713bSRémi Denis-Courmont struct nlattr *tb[IFA_MAX+1]; 688fb39740SRemi Denis-Courmont struct net_device *dev; 698980713bSRémi Denis-Courmont struct ifaddrmsg *ifm; 708fb39740SRemi Denis-Courmont int err; 718fb39740SRemi Denis-Courmont u8 pnaddr; 728fb39740SRemi Denis-Courmont 7390f62cf3SEric W. Biederman if (!netlink_capable(skb, CAP_NET_ADMIN)) 74dfc47ef8SEric W. Biederman return -EPERM; 75dfc47ef8SEric W. Biederman 7690f62cf3SEric W. Biederman if (!netlink_capable(skb, CAP_SYS_ADMIN)) 778fb39740SRemi Denis-Courmont return -EPERM; 788fb39740SRemi Denis-Courmont 798fb39740SRemi Denis-Courmont ASSERT_RTNL(); 808fb39740SRemi Denis-Courmont 818980713bSRémi Denis-Courmont err = nlmsg_parse(nlh, sizeof(*ifm), tb, IFA_MAX, ifa_phonet_policy); 828980713bSRémi Denis-Courmont if (err < 0) 838fb39740SRemi Denis-Courmont return err; 848fb39740SRemi Denis-Courmont 858980713bSRémi Denis-Courmont ifm = nlmsg_data(nlh); 868980713bSRémi Denis-Courmont if (tb[IFA_LOCAL] == NULL) 878980713bSRémi Denis-Courmont return -EINVAL; 888980713bSRémi Denis-Courmont pnaddr = nla_get_u8(tb[IFA_LOCAL]); 898980713bSRémi Denis-Courmont if (pnaddr & 3) 908980713bSRémi Denis-Courmont /* Phonet addresses only have 6 high-order bits */ 918fb39740SRemi Denis-Courmont return -EINVAL; 928fb39740SRemi Denis-Courmont 938980713bSRémi Denis-Courmont dev = __dev_get_by_index(net, ifm->ifa_index); 948fb39740SRemi Denis-Courmont if (dev == NULL) 958fb39740SRemi Denis-Courmont return -ENODEV; 968fb39740SRemi Denis-Courmont 978980713bSRémi Denis-Courmont if (nlh->nlmsg_type == RTM_NEWADDR) 988980713bSRémi Denis-Courmont err = phonet_address_add(dev, pnaddr); 998980713bSRémi Denis-Courmont else 1008fb39740SRemi Denis-Courmont err = phonet_address_del(dev, pnaddr); 1018fb39740SRemi Denis-Courmont if (!err) 102c7a1a4c8SRémi Denis-Courmont phonet_address_notify(nlh->nlmsg_type, dev, pnaddr); 1038fb39740SRemi Denis-Courmont return err; 1048fb39740SRemi Denis-Courmont } 1058fb39740SRemi Denis-Courmont 1068fb39740SRemi Denis-Courmont static int fill_addr(struct sk_buff *skb, struct net_device *dev, u8 addr, 10715e47304SEric W. Biederman u32 portid, u32 seq, int event) 1088fb39740SRemi Denis-Courmont { 1098fb39740SRemi Denis-Courmont struct ifaddrmsg *ifm; 1108fb39740SRemi Denis-Courmont struct nlmsghdr *nlh; 1118fb39740SRemi Denis-Courmont 11215e47304SEric W. Biederman nlh = nlmsg_put(skb, portid, seq, event, sizeof(*ifm), 0); 1138980713bSRémi Denis-Courmont if (nlh == NULL) 1148980713bSRémi Denis-Courmont return -EMSGSIZE; 1158980713bSRémi Denis-Courmont 1168980713bSRémi Denis-Courmont ifm = nlmsg_data(nlh); 1178fb39740SRemi Denis-Courmont ifm->ifa_family = AF_PHONET; 1188fb39740SRemi Denis-Courmont ifm->ifa_prefixlen = 0; 1198fb39740SRemi Denis-Courmont ifm->ifa_flags = IFA_F_PERMANENT; 1208980713bSRémi Denis-Courmont ifm->ifa_scope = RT_SCOPE_LINK; 1218fb39740SRemi Denis-Courmont ifm->ifa_index = dev->ifindex; 1227f116b5bSDavid S. Miller if (nla_put_u8(skb, IFA_LOCAL, addr)) 1237f116b5bSDavid S. Miller goto nla_put_failure; 124053c095aSJohannes Berg nlmsg_end(skb, nlh); 125053c095aSJohannes Berg return 0; 1268fb39740SRemi Denis-Courmont 1278980713bSRémi Denis-Courmont nla_put_failure: 1288980713bSRémi Denis-Courmont nlmsg_cancel(skb, nlh); 1298980713bSRémi Denis-Courmont return -EMSGSIZE; 1308fb39740SRemi Denis-Courmont } 1318fb39740SRemi Denis-Courmont 1328fb39740SRemi Denis-Courmont static int getaddr_dumpit(struct sk_buff *skb, struct netlink_callback *cb) 1338fb39740SRemi Denis-Courmont { 1349a3b7a42Sremi.denis-courmont@nokia struct phonet_device_list *pndevs; 1358fb39740SRemi Denis-Courmont struct phonet_device *pnd; 1368fb39740SRemi Denis-Courmont int dev_idx = 0, dev_start_idx = cb->args[0]; 1378fb39740SRemi Denis-Courmont int addr_idx = 0, addr_start_idx = cb->args[1]; 1388fb39740SRemi Denis-Courmont 1399a3b7a42Sremi.denis-courmont@nokia pndevs = phonet_device_list(sock_net(skb->sk)); 140eeb74a9dSRémi Denis-Courmont rcu_read_lock(); 141eeb74a9dSRémi Denis-Courmont list_for_each_entry_rcu(pnd, &pndevs->list, list) { 1428fb39740SRemi Denis-Courmont u8 addr; 1438fb39740SRemi Denis-Courmont 1448fb39740SRemi Denis-Courmont if (dev_idx > dev_start_idx) 1458fb39740SRemi Denis-Courmont addr_start_idx = 0; 1468fb39740SRemi Denis-Courmont if (dev_idx++ < dev_start_idx) 1478fb39740SRemi Denis-Courmont continue; 1488fb39740SRemi Denis-Courmont 1498fb39740SRemi Denis-Courmont addr_idx = 0; 150a1ca14acSAkinobu Mita for_each_set_bit(addr, pnd->addrs, 64) { 1518fb39740SRemi Denis-Courmont if (addr_idx++ < addr_start_idx) 1528fb39740SRemi Denis-Courmont continue; 1538fb39740SRemi Denis-Courmont 1548fb39740SRemi Denis-Courmont if (fill_addr(skb, pnd->netdev, addr << 2, 15515e47304SEric W. Biederman NETLINK_CB(cb->skb).portid, 156998ec759SRémi Denis-Courmont cb->nlh->nlmsg_seq, RTM_NEWADDR) < 0) 1578fb39740SRemi Denis-Courmont goto out; 1588fb39740SRemi Denis-Courmont } 1598fb39740SRemi Denis-Courmont } 1608fb39740SRemi Denis-Courmont 1618fb39740SRemi Denis-Courmont out: 162eeb74a9dSRémi Denis-Courmont rcu_read_unlock(); 1638fb39740SRemi Denis-Courmont cb->args[0] = dev_idx; 1648fb39740SRemi Denis-Courmont cb->args[1] = addr_idx; 1658fb39740SRemi Denis-Courmont 1668fb39740SRemi Denis-Courmont return skb->len; 1678fb39740SRemi Denis-Courmont } 1688fb39740SRemi Denis-Courmont 169f062f41dSRémi Denis-Courmont /* Routes handling */ 170f062f41dSRémi Denis-Courmont 171f062f41dSRémi Denis-Courmont static int fill_route(struct sk_buff *skb, struct net_device *dev, u8 dst, 17215e47304SEric W. Biederman u32 portid, u32 seq, int event) 173f062f41dSRémi Denis-Courmont { 174f062f41dSRémi Denis-Courmont struct rtmsg *rtm; 175f062f41dSRémi Denis-Courmont struct nlmsghdr *nlh; 176f062f41dSRémi Denis-Courmont 17715e47304SEric W. Biederman nlh = nlmsg_put(skb, portid, seq, event, sizeof(*rtm), 0); 178f062f41dSRémi Denis-Courmont if (nlh == NULL) 179f062f41dSRémi Denis-Courmont return -EMSGSIZE; 180f062f41dSRémi Denis-Courmont 181f062f41dSRémi Denis-Courmont rtm = nlmsg_data(nlh); 182f062f41dSRémi Denis-Courmont rtm->rtm_family = AF_PHONET; 183f062f41dSRémi Denis-Courmont rtm->rtm_dst_len = 6; 184f062f41dSRémi Denis-Courmont rtm->rtm_src_len = 0; 185f062f41dSRémi Denis-Courmont rtm->rtm_tos = 0; 186f062f41dSRémi Denis-Courmont rtm->rtm_table = RT_TABLE_MAIN; 187f062f41dSRémi Denis-Courmont rtm->rtm_protocol = RTPROT_STATIC; 188f062f41dSRémi Denis-Courmont rtm->rtm_scope = RT_SCOPE_UNIVERSE; 189f062f41dSRémi Denis-Courmont rtm->rtm_type = RTN_UNICAST; 190f062f41dSRémi Denis-Courmont rtm->rtm_flags = 0; 1917f116b5bSDavid S. Miller if (nla_put_u8(skb, RTA_DST, dst) || 1927f116b5bSDavid S. Miller nla_put_u32(skb, RTA_OIF, dev->ifindex)) 1937f116b5bSDavid S. Miller goto nla_put_failure; 194053c095aSJohannes Berg nlmsg_end(skb, nlh); 195053c095aSJohannes Berg return 0; 196f062f41dSRémi Denis-Courmont 197f062f41dSRémi Denis-Courmont nla_put_failure: 198f062f41dSRémi Denis-Courmont nlmsg_cancel(skb, nlh); 199f062f41dSRémi Denis-Courmont return -EMSGSIZE; 200f062f41dSRémi Denis-Courmont } 201f062f41dSRémi Denis-Courmont 202f062f41dSRémi Denis-Courmont void rtm_phonet_notify(int event, struct net_device *dev, u8 dst) 203f062f41dSRémi Denis-Courmont { 204f062f41dSRémi Denis-Courmont struct sk_buff *skb; 205f062f41dSRémi Denis-Courmont int err = -ENOBUFS; 206f062f41dSRémi Denis-Courmont 207f062f41dSRémi Denis-Courmont skb = nlmsg_new(NLMSG_ALIGN(sizeof(struct ifaddrmsg)) + 208f062f41dSRémi Denis-Courmont nla_total_size(1) + nla_total_size(4), GFP_KERNEL); 209f062f41dSRémi Denis-Courmont if (skb == NULL) 210f062f41dSRémi Denis-Courmont goto errout; 211f062f41dSRémi Denis-Courmont err = fill_route(skb, dev, dst, 0, 0, event); 212f062f41dSRémi Denis-Courmont if (err < 0) { 213f062f41dSRémi Denis-Courmont WARN_ON(err == -EMSGSIZE); 214f062f41dSRémi Denis-Courmont kfree_skb(skb); 215f062f41dSRémi Denis-Courmont goto errout; 216f062f41dSRémi Denis-Courmont } 217f062f41dSRémi Denis-Courmont rtnl_notify(skb, dev_net(dev), 0, 218f062f41dSRémi Denis-Courmont RTNLGRP_PHONET_ROUTE, NULL, GFP_KERNEL); 219f062f41dSRémi Denis-Courmont return; 220f062f41dSRémi Denis-Courmont errout: 221f062f41dSRémi Denis-Courmont rtnl_set_sk_err(dev_net(dev), RTNLGRP_PHONET_ROUTE, err); 222f062f41dSRémi Denis-Courmont } 223f062f41dSRémi Denis-Courmont 224f062f41dSRémi Denis-Courmont static const struct nla_policy rtm_phonet_policy[RTA_MAX+1] = { 225f062f41dSRémi Denis-Courmont [RTA_DST] = { .type = NLA_U8 }, 226f062f41dSRémi Denis-Courmont [RTA_OIF] = { .type = NLA_U32 }, 227f062f41dSRémi Denis-Courmont }; 228f062f41dSRémi Denis-Courmont 229661d2967SThomas Graf static int route_doit(struct sk_buff *skb, struct nlmsghdr *nlh) 230f062f41dSRémi Denis-Courmont { 231f062f41dSRémi Denis-Courmont struct net *net = sock_net(skb->sk); 232f062f41dSRémi Denis-Courmont struct nlattr *tb[RTA_MAX+1]; 233f062f41dSRémi Denis-Courmont struct net_device *dev; 234f062f41dSRémi Denis-Courmont struct rtmsg *rtm; 235f062f41dSRémi Denis-Courmont int err; 236f062f41dSRémi Denis-Courmont u8 dst; 237f062f41dSRémi Denis-Courmont 23890f62cf3SEric W. Biederman if (!netlink_capable(skb, CAP_NET_ADMIN)) 239dfc47ef8SEric W. Biederman return -EPERM; 240dfc47ef8SEric W. Biederman 24190f62cf3SEric W. Biederman if (!netlink_capable(skb, CAP_SYS_ADMIN)) 242f062f41dSRémi Denis-Courmont return -EPERM; 243f062f41dSRémi Denis-Courmont 244f062f41dSRémi Denis-Courmont ASSERT_RTNL(); 245f062f41dSRémi Denis-Courmont 246f062f41dSRémi Denis-Courmont err = nlmsg_parse(nlh, sizeof(*rtm), tb, RTA_MAX, rtm_phonet_policy); 247f062f41dSRémi Denis-Courmont if (err < 0) 248f062f41dSRémi Denis-Courmont return err; 249f062f41dSRémi Denis-Courmont 250f062f41dSRémi Denis-Courmont rtm = nlmsg_data(nlh); 251f062f41dSRémi Denis-Courmont if (rtm->rtm_table != RT_TABLE_MAIN || rtm->rtm_type != RTN_UNICAST) 252f062f41dSRémi Denis-Courmont return -EINVAL; 253f062f41dSRémi Denis-Courmont if (tb[RTA_DST] == NULL || tb[RTA_OIF] == NULL) 254f062f41dSRémi Denis-Courmont return -EINVAL; 255f062f41dSRémi Denis-Courmont dst = nla_get_u8(tb[RTA_DST]); 256f062f41dSRémi Denis-Courmont if (dst & 3) /* Phonet addresses only have 6 high-order bits */ 257f062f41dSRémi Denis-Courmont return -EINVAL; 258f062f41dSRémi Denis-Courmont 259f062f41dSRémi Denis-Courmont dev = __dev_get_by_index(net, nla_get_u32(tb[RTA_OIF])); 260f062f41dSRémi Denis-Courmont if (dev == NULL) 261f062f41dSRémi Denis-Courmont return -ENODEV; 262f062f41dSRémi Denis-Courmont 263f062f41dSRémi Denis-Courmont if (nlh->nlmsg_type == RTM_NEWROUTE) 264f062f41dSRémi Denis-Courmont err = phonet_route_add(dev, dst); 265f062f41dSRémi Denis-Courmont else 266f062f41dSRémi Denis-Courmont err = phonet_route_del(dev, dst); 267f062f41dSRémi Denis-Courmont if (!err) 268f062f41dSRémi Denis-Courmont rtm_phonet_notify(nlh->nlmsg_type, dev, dst); 269f062f41dSRémi Denis-Courmont return err; 270f062f41dSRémi Denis-Courmont } 271f062f41dSRémi Denis-Courmont 272f062f41dSRémi Denis-Courmont static int route_dumpit(struct sk_buff *skb, struct netlink_callback *cb) 273f062f41dSRémi Denis-Courmont { 274f062f41dSRémi Denis-Courmont struct net *net = sock_net(skb->sk); 275926e9878SJohannes Berg u8 addr; 276f062f41dSRémi Denis-Courmont 277e67f88ddSEric Dumazet rcu_read_lock(); 278926e9878SJohannes Berg for (addr = cb->args[0]; addr < 64; addr++) { 279926e9878SJohannes Berg struct net_device *dev = phonet_route_get_rcu(net, addr << 2); 280f062f41dSRémi Denis-Courmont 281f062f41dSRémi Denis-Courmont if (!dev) 282f062f41dSRémi Denis-Courmont continue; 283f062f41dSRémi Denis-Courmont 284926e9878SJohannes Berg if (fill_route(skb, dev, addr << 2, NETLINK_CB(cb->skb).portid, 285926e9878SJohannes Berg cb->nlh->nlmsg_seq, RTM_NEWROUTE) < 0) 286f062f41dSRémi Denis-Courmont goto out; 287f062f41dSRémi Denis-Courmont } 288f062f41dSRémi Denis-Courmont 289f062f41dSRémi Denis-Courmont out: 290e67f88ddSEric Dumazet rcu_read_unlock(); 291926e9878SJohannes Berg cb->args[0] = addr; 292f062f41dSRémi Denis-Courmont 293f062f41dSRémi Denis-Courmont return skb->len; 294f062f41dSRémi Denis-Courmont } 295f062f41dSRémi Denis-Courmont 296660f706dSremi.denis-courmont@nokia int __init phonet_netlink_register(void) 2978fb39740SRemi Denis-Courmont { 298c7ac8679SGreg Rose int err = __rtnl_register(PF_PHONET, RTM_NEWADDR, addr_doit, 299c7ac8679SGreg Rose NULL, NULL); 300660f706dSremi.denis-courmont@nokia if (err) 301660f706dSremi.denis-courmont@nokia return err; 302660f706dSremi.denis-courmont@nokia 303660f706dSremi.denis-courmont@nokia /* Further __rtnl_register() cannot fail */ 304c7ac8679SGreg Rose __rtnl_register(PF_PHONET, RTM_DELADDR, addr_doit, NULL, NULL); 305c7ac8679SGreg Rose __rtnl_register(PF_PHONET, RTM_GETADDR, NULL, getaddr_dumpit, NULL); 306c7ac8679SGreg Rose __rtnl_register(PF_PHONET, RTM_NEWROUTE, route_doit, NULL, NULL); 307c7ac8679SGreg Rose __rtnl_register(PF_PHONET, RTM_DELROUTE, route_doit, NULL, NULL); 308c7ac8679SGreg Rose __rtnl_register(PF_PHONET, RTM_GETROUTE, NULL, route_dumpit, NULL); 309660f706dSremi.denis-courmont@nokia return 0; 3108fb39740SRemi Denis-Courmont } 311