diff options
author | Jiri Pirko <jiri@mellanox.com> | 2018-07-23 03:23:06 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2018-07-23 23:44:12 -0400 |
commit | 32a4f5ecd7381f30ae3bb36dea77a150ba68af2e (patch) | |
tree | b6d291d08192b09e78cd72791b53b53e3852af65 /net/sched/cls_api.c | |
parent | f71e0ca4db187af7c44987e9d21e9042c3046070 (diff) |
net: sched: introduce chain object to uapi
Allow user to create, destroy, get and dump chain objects. Do that by
extending rtnl commands by the chain-specific ones. User will now be
able to explicitly create or destroy chains (so far this was done only
automatically according the filter/act needs and refcounting). Also, the
user will receive notification about any chain creation or destuction.
Signed-off-by: Jiri Pirko <jiri@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/sched/cls_api.c')
-rw-r--r-- | net/sched/cls_api.c | 308 |
1 files changed, 300 insertions, 8 deletions
diff --git a/net/sched/cls_api.c b/net/sched/cls_api.c index eb0bf9037ef9..e65b390336aa 100644 --- a/net/sched/cls_api.c +++ b/net/sched/cls_api.c | |||
@@ -262,29 +262,57 @@ static void tcf_chain_hold(struct tcf_chain *chain) | |||
262 | ++chain->refcnt; | 262 | ++chain->refcnt; |
263 | } | 263 | } |
264 | 264 | ||
265 | struct tcf_chain *tcf_chain_get(struct tcf_block *block, u32 chain_index, | 265 | static struct tcf_chain *tcf_chain_lookup(struct tcf_block *block, |
266 | bool create) | 266 | u32 chain_index) |
267 | { | 267 | { |
268 | struct tcf_chain *chain; | 268 | struct tcf_chain *chain; |
269 | 269 | ||
270 | list_for_each_entry(chain, &block->chain_list, list) { | 270 | list_for_each_entry(chain, &block->chain_list, list) { |
271 | if (chain->index == chain_index) { | 271 | if (chain->index == chain_index) |
272 | tcf_chain_hold(chain); | ||
273 | return chain; | 272 | return chain; |
274 | } | 273 | } |
274 | return NULL; | ||
275 | } | ||
276 | |||
277 | static int tc_chain_notify(struct tcf_chain *chain, struct sk_buff *oskb, | ||
278 | u32 seq, u16 flags, int event, bool unicast); | ||
279 | |||
280 | struct tcf_chain *tcf_chain_get(struct tcf_block *block, u32 chain_index, | ||
281 | bool create) | ||
282 | { | ||
283 | struct tcf_chain *chain = tcf_chain_lookup(block, chain_index); | ||
284 | |||
285 | if (chain) { | ||
286 | tcf_chain_hold(chain); | ||
287 | return chain; | ||
275 | } | 288 | } |
276 | 289 | ||
277 | return create ? tcf_chain_create(block, chain_index) : NULL; | 290 | if (!create) |
291 | return NULL; | ||
292 | chain = tcf_chain_create(block, chain_index); | ||
293 | if (!chain) | ||
294 | return NULL; | ||
295 | tc_chain_notify(chain, NULL, 0, NLM_F_CREATE | NLM_F_EXCL, | ||
296 | RTM_NEWCHAIN, false); | ||
297 | return chain; | ||
278 | } | 298 | } |
279 | EXPORT_SYMBOL(tcf_chain_get); | 299 | EXPORT_SYMBOL(tcf_chain_get); |
280 | 300 | ||
281 | void tcf_chain_put(struct tcf_chain *chain) | 301 | void tcf_chain_put(struct tcf_chain *chain) |
282 | { | 302 | { |
283 | if (--chain->refcnt == 0) | 303 | if (--chain->refcnt == 0) { |
304 | tc_chain_notify(chain, NULL, 0, 0, RTM_DELCHAIN, false); | ||
284 | tcf_chain_destroy(chain); | 305 | tcf_chain_destroy(chain); |
306 | } | ||
285 | } | 307 | } |
286 | EXPORT_SYMBOL(tcf_chain_put); | 308 | EXPORT_SYMBOL(tcf_chain_put); |
287 | 309 | ||
310 | static void tcf_chain_put_explicitly_created(struct tcf_chain *chain) | ||
311 | { | ||
312 | if (chain->explicitly_created) | ||
313 | tcf_chain_put(chain); | ||
314 | } | ||
315 | |||
288 | static bool tcf_block_offload_in_use(struct tcf_block *block) | 316 | static bool tcf_block_offload_in_use(struct tcf_block *block) |
289 | { | 317 | { |
290 | return block->offloadcnt; | 318 | return block->offloadcnt; |
@@ -694,8 +722,10 @@ void tcf_block_put_ext(struct tcf_block *block, struct Qdisc *q, | |||
694 | 722 | ||
695 | if (block->refcnt == 1) { | 723 | if (block->refcnt == 1) { |
696 | /* At this point, all the chains should have refcnt >= 1. */ | 724 | /* At this point, all the chains should have refcnt >= 1. */ |
697 | list_for_each_entry_safe(chain, tmp, &block->chain_list, list) | 725 | list_for_each_entry_safe(chain, tmp, &block->chain_list, list) { |
726 | tcf_chain_put_explicitly_created(chain); | ||
698 | tcf_chain_put(chain); | 727 | tcf_chain_put(chain); |
728 | } | ||
699 | 729 | ||
700 | block->refcnt--; | 730 | block->refcnt--; |
701 | if (list_empty(&block->chain_list)) | 731 | if (list_empty(&block->chain_list)) |
@@ -1609,6 +1639,264 @@ out: | |||
1609 | return skb->len; | 1639 | return skb->len; |
1610 | } | 1640 | } |
1611 | 1641 | ||
1642 | static int tc_chain_fill_node(struct tcf_chain *chain, struct net *net, | ||
1643 | struct sk_buff *skb, struct tcf_block *block, | ||
1644 | u32 portid, u32 seq, u16 flags, int event) | ||
1645 | { | ||
1646 | unsigned char *b = skb_tail_pointer(skb); | ||
1647 | struct nlmsghdr *nlh; | ||
1648 | struct tcmsg *tcm; | ||
1649 | |||
1650 | nlh = nlmsg_put(skb, portid, seq, event, sizeof(*tcm), flags); | ||
1651 | if (!nlh) | ||
1652 | goto out_nlmsg_trim; | ||
1653 | tcm = nlmsg_data(nlh); | ||
1654 | tcm->tcm_family = AF_UNSPEC; | ||
1655 | tcm->tcm__pad1 = 0; | ||
1656 | tcm->tcm__pad2 = 0; | ||
1657 | tcm->tcm_handle = 0; | ||
1658 | if (block->q) { | ||
1659 | tcm->tcm_ifindex = qdisc_dev(block->q)->ifindex; | ||
1660 | tcm->tcm_parent = block->q->handle; | ||
1661 | } else { | ||
1662 | tcm->tcm_ifindex = TCM_IFINDEX_MAGIC_BLOCK; | ||
1663 | tcm->tcm_block_index = block->index; | ||
1664 | } | ||
1665 | |||
1666 | if (nla_put_u32(skb, TCA_CHAIN, chain->index)) | ||
1667 | goto nla_put_failure; | ||
1668 | |||
1669 | nlh->nlmsg_len = skb_tail_pointer(skb) - b; | ||
1670 | return skb->len; | ||
1671 | |||
1672 | out_nlmsg_trim: | ||
1673 | nla_put_failure: | ||
1674 | nlmsg_trim(skb, b); | ||
1675 | return -EMSGSIZE; | ||
1676 | } | ||
1677 | |||
1678 | static int tc_chain_notify(struct tcf_chain *chain, struct sk_buff *oskb, | ||
1679 | u32 seq, u16 flags, int event, bool unicast) | ||
1680 | { | ||
1681 | u32 portid = oskb ? NETLINK_CB(oskb).portid : 0; | ||
1682 | struct tcf_block *block = chain->block; | ||
1683 | struct net *net = block->net; | ||
1684 | struct sk_buff *skb; | ||
1685 | |||
1686 | skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL); | ||
1687 | if (!skb) | ||
1688 | return -ENOBUFS; | ||
1689 | |||
1690 | if (tc_chain_fill_node(chain, net, skb, block, portid, | ||
1691 | seq, flags, event) <= 0) { | ||
1692 | kfree_skb(skb); | ||
1693 | return -EINVAL; | ||
1694 | } | ||
1695 | |||
1696 | if (unicast) | ||
1697 | return netlink_unicast(net->rtnl, skb, portid, MSG_DONTWAIT); | ||
1698 | |||
1699 | return rtnetlink_send(skb, net, portid, RTNLGRP_TC, flags & NLM_F_ECHO); | ||
1700 | } | ||
1701 | |||
1702 | /* Add/delete/get a chain */ | ||
1703 | |||
1704 | static int tc_ctl_chain(struct sk_buff *skb, struct nlmsghdr *n, | ||
1705 | struct netlink_ext_ack *extack) | ||
1706 | { | ||
1707 | struct net *net = sock_net(skb->sk); | ||
1708 | struct nlattr *tca[TCA_MAX + 1]; | ||
1709 | struct tcmsg *t; | ||
1710 | u32 parent; | ||
1711 | u32 chain_index; | ||
1712 | struct Qdisc *q = NULL; | ||
1713 | struct tcf_chain *chain = NULL; | ||
1714 | struct tcf_block *block; | ||
1715 | unsigned long cl; | ||
1716 | int err; | ||
1717 | |||
1718 | if (n->nlmsg_type != RTM_GETCHAIN && | ||
1719 | !netlink_ns_capable(skb, net->user_ns, CAP_NET_ADMIN)) | ||
1720 | return -EPERM; | ||
1721 | |||
1722 | replay: | ||
1723 | err = nlmsg_parse(n, sizeof(*t), tca, TCA_MAX, NULL, extack); | ||
1724 | if (err < 0) | ||
1725 | return err; | ||
1726 | |||
1727 | t = nlmsg_data(n); | ||
1728 | parent = t->tcm_parent; | ||
1729 | cl = 0; | ||
1730 | |||
1731 | block = tcf_block_find(net, &q, &parent, &cl, | ||
1732 | t->tcm_ifindex, t->tcm_block_index, extack); | ||
1733 | if (IS_ERR(block)) | ||
1734 | return PTR_ERR(block); | ||
1735 | |||
1736 | chain_index = tca[TCA_CHAIN] ? nla_get_u32(tca[TCA_CHAIN]) : 0; | ||
1737 | if (chain_index > TC_ACT_EXT_VAL_MASK) { | ||
1738 | NL_SET_ERR_MSG(extack, "Specified chain index exceeds upper limit"); | ||
1739 | return -EINVAL; | ||
1740 | } | ||
1741 | chain = tcf_chain_lookup(block, chain_index); | ||
1742 | if (n->nlmsg_type == RTM_NEWCHAIN) { | ||
1743 | if (chain) { | ||
1744 | NL_SET_ERR_MSG(extack, "Filter chain already exists"); | ||
1745 | return -EEXIST; | ||
1746 | } | ||
1747 | if (!(n->nlmsg_flags & NLM_F_CREATE)) { | ||
1748 | NL_SET_ERR_MSG(extack, "Need both RTM_NEWCHAIN and NLM_F_CREATE to create a new chain"); | ||
1749 | return -ENOENT; | ||
1750 | } | ||
1751 | chain = tcf_chain_create(block, chain_index); | ||
1752 | if (!chain) { | ||
1753 | NL_SET_ERR_MSG(extack, "Failed to create filter chain"); | ||
1754 | return -ENOMEM; | ||
1755 | } | ||
1756 | } else { | ||
1757 | if (!chain) { | ||
1758 | NL_SET_ERR_MSG(extack, "Cannot find specified filter chain"); | ||
1759 | return -EINVAL; | ||
1760 | } | ||
1761 | tcf_chain_hold(chain); | ||
1762 | } | ||
1763 | |||
1764 | switch (n->nlmsg_type) { | ||
1765 | case RTM_NEWCHAIN: | ||
1766 | /* In case the chain was successfully added, take a reference | ||
1767 | * to the chain. This ensures that an empty chain | ||
1768 | * does not disappear at the end of this function. | ||
1769 | */ | ||
1770 | tcf_chain_hold(chain); | ||
1771 | chain->explicitly_created = true; | ||
1772 | tc_chain_notify(chain, NULL, 0, NLM_F_CREATE | NLM_F_EXCL, | ||
1773 | RTM_NEWCHAIN, false); | ||
1774 | break; | ||
1775 | case RTM_DELCHAIN: | ||
1776 | /* Flush the chain first as the user requested chain removal. */ | ||
1777 | tcf_chain_flush(chain); | ||
1778 | /* In case the chain was successfully deleted, put a reference | ||
1779 | * to the chain previously taken during addition. | ||
1780 | */ | ||
1781 | tcf_chain_put_explicitly_created(chain); | ||
1782 | break; | ||
1783 | case RTM_GETCHAIN: | ||
1784 | break; | ||
1785 | err = tc_chain_notify(chain, skb, n->nlmsg_seq, | ||
1786 | n->nlmsg_seq, n->nlmsg_type, true); | ||
1787 | if (err < 0) | ||
1788 | NL_SET_ERR_MSG(extack, "Failed to send chain notify message"); | ||
1789 | break; | ||
1790 | default: | ||
1791 | err = -EOPNOTSUPP; | ||
1792 | NL_SET_ERR_MSG(extack, "Unsupported message type"); | ||
1793 | goto errout; | ||
1794 | } | ||
1795 | |||
1796 | errout: | ||
1797 | tcf_chain_put(chain); | ||
1798 | if (err == -EAGAIN) | ||
1799 | /* Replay the request. */ | ||
1800 | goto replay; | ||
1801 | return err; | ||
1802 | } | ||
1803 | |||
1804 | /* called with RTNL */ | ||
1805 | static int tc_dump_chain(struct sk_buff *skb, struct netlink_callback *cb) | ||
1806 | { | ||
1807 | struct net *net = sock_net(skb->sk); | ||
1808 | struct nlattr *tca[TCA_MAX + 1]; | ||
1809 | struct Qdisc *q = NULL; | ||
1810 | struct tcf_block *block; | ||
1811 | struct tcf_chain *chain; | ||
1812 | struct tcmsg *tcm = nlmsg_data(cb->nlh); | ||
1813 | long index_start; | ||
1814 | long index; | ||
1815 | u32 parent; | ||
1816 | int err; | ||
1817 | |||
1818 | if (nlmsg_len(cb->nlh) < sizeof(*tcm)) | ||
1819 | return skb->len; | ||
1820 | |||
1821 | err = nlmsg_parse(cb->nlh, sizeof(*tcm), tca, TCA_MAX, NULL, NULL); | ||
1822 | if (err) | ||
1823 | return err; | ||
1824 | |||
1825 | if (tcm->tcm_ifindex == TCM_IFINDEX_MAGIC_BLOCK) { | ||
1826 | block = tcf_block_lookup(net, tcm->tcm_block_index); | ||
1827 | if (!block) | ||
1828 | goto out; | ||
1829 | /* If we work with block index, q is NULL and parent value | ||
1830 | * will never be used in the following code. The check | ||
1831 | * in tcf_fill_node prevents it. However, compiler does not | ||
1832 | * see that far, so set parent to zero to silence the warning | ||
1833 | * about parent being uninitialized. | ||
1834 | */ | ||
1835 | parent = 0; | ||
1836 | } else { | ||
1837 | const struct Qdisc_class_ops *cops; | ||
1838 | struct net_device *dev; | ||
1839 | unsigned long cl = 0; | ||
1840 | |||
1841 | dev = __dev_get_by_index(net, tcm->tcm_ifindex); | ||
1842 | if (!dev) | ||
1843 | return skb->len; | ||
1844 | |||
1845 | parent = tcm->tcm_parent; | ||
1846 | if (!parent) { | ||
1847 | q = dev->qdisc; | ||
1848 | parent = q->handle; | ||
1849 | } else { | ||
1850 | q = qdisc_lookup(dev, TC_H_MAJ(tcm->tcm_parent)); | ||
1851 | } | ||
1852 | if (!q) | ||
1853 | goto out; | ||
1854 | cops = q->ops->cl_ops; | ||
1855 | if (!cops) | ||
1856 | goto out; | ||
1857 | if (!cops->tcf_block) | ||
1858 | goto out; | ||
1859 | if (TC_H_MIN(tcm->tcm_parent)) { | ||
1860 | cl = cops->find(q, tcm->tcm_parent); | ||
1861 | if (cl == 0) | ||
1862 | goto out; | ||
1863 | } | ||
1864 | block = cops->tcf_block(q, cl, NULL); | ||
1865 | if (!block) | ||
1866 | goto out; | ||
1867 | if (tcf_block_shared(block)) | ||
1868 | q = NULL; | ||
1869 | } | ||
1870 | |||
1871 | index_start = cb->args[0]; | ||
1872 | index = 0; | ||
1873 | |||
1874 | list_for_each_entry(chain, &block->chain_list, list) { | ||
1875 | if ((tca[TCA_CHAIN] && | ||
1876 | nla_get_u32(tca[TCA_CHAIN]) != chain->index)) | ||
1877 | continue; | ||
1878 | if (index < index_start) { | ||
1879 | index++; | ||
1880 | continue; | ||
1881 | } | ||
1882 | err = tc_chain_fill_node(chain, net, skb, block, | ||
1883 | NETLINK_CB(cb->skb).portid, | ||
1884 | cb->nlh->nlmsg_seq, NLM_F_MULTI, | ||
1885 | RTM_NEWCHAIN); | ||
1886 | if (err <= 0) | ||
1887 | break; | ||
1888 | index++; | ||
1889 | } | ||
1890 | |||
1891 | cb->args[0] = index; | ||
1892 | |||
1893 | out: | ||
1894 | /* If we did no progress, the error (EMSGSIZE) is real */ | ||
1895 | if (skb->len == 0 && err) | ||
1896 | return err; | ||
1897 | return skb->len; | ||
1898 | } | ||
1899 | |||
1612 | void tcf_exts_destroy(struct tcf_exts *exts) | 1900 | void tcf_exts_destroy(struct tcf_exts *exts) |
1613 | { | 1901 | { |
1614 | #ifdef CONFIG_NET_CLS_ACT | 1902 | #ifdef CONFIG_NET_CLS_ACT |
@@ -1825,6 +2113,10 @@ static int __init tc_filter_init(void) | |||
1825 | rtnl_register(PF_UNSPEC, RTM_DELTFILTER, tc_del_tfilter, NULL, 0); | 2113 | rtnl_register(PF_UNSPEC, RTM_DELTFILTER, tc_del_tfilter, NULL, 0); |
1826 | rtnl_register(PF_UNSPEC, RTM_GETTFILTER, tc_get_tfilter, | 2114 | rtnl_register(PF_UNSPEC, RTM_GETTFILTER, tc_get_tfilter, |
1827 | tc_dump_tfilter, 0); | 2115 | tc_dump_tfilter, 0); |
2116 | rtnl_register(PF_UNSPEC, RTM_NEWCHAIN, tc_ctl_chain, NULL, 0); | ||
2117 | rtnl_register(PF_UNSPEC, RTM_DELCHAIN, tc_ctl_chain, NULL, 0); | ||
2118 | rtnl_register(PF_UNSPEC, RTM_GETCHAIN, tc_ctl_chain, | ||
2119 | tc_dump_chain, 0); | ||
1828 | 2120 | ||
1829 | return 0; | 2121 | return 0; |
1830 | 2122 | ||