| @@ -262,29 +262,57 @@ static void tcf_chain_hold(struct tcf_chain *chain) · 261 unmodified lines |
| 262 | 262 | ++chain->refcnt; |
| 263 | 263 | } |
| 264 | 264 | |
| 265 | | −struct tcf_chain *tcf_chain_get(struct tcf_block *block, u32 chain_index, |
| 266 | | − bool create) |
| 265 | +static struct tcf_chain *tcf_chain_lookup(struct tcf_block *block, |
| 266 | + u32 chain_index) |
| 267 | 267 | { |
| 268 | 268 | struct tcf_chain *chain; |
| 269 | 269 | |
| 270 | 270 | list_for_each_entry(chain, &block->chain_list, list) { |
| 271 | | − if (chain->index == chain_index) { |
| 272 | | − tcf_chain_hold(chain); |
| 271 | + if (chain->index == chain_index) |
| 273 | 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 | 299 | EXPORT_SYMBOL(tcf_chain_get); |
| 280 | 300 | |
| 281 | 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 | 305 | tcf_chain_destroy(chain); |
| 306 | + } |
| 285 | 307 | } |
| 286 | 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 | 316 | static bool tcf_block_offload_in_use(struct tcf_block *block) |
| 289 | 317 | { |
| 290 | 318 | return block->offloadcnt; |
| @@ -694,8 +722,10 @@ void tcf_block_put_ext(struct tcf_block *block, struct Qdisc *q, · 403 unmodified lines |
| 694 | 722 | |
| 695 | 723 | if (block->refcnt == 1) { |
| 696 | 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 | 727 | tcf_chain_put(chain); |
| 728 | + } |
| 699 | 729 | |
| 700 | 730 | block->refcnt--; |
| 701 | 731 | if (list_empty(&block->chain_list)) |
| @@ -1609,6 +1639,264 @@ out: · 907 unmodified lines |
| 1609 | 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 | 1900 | void tcf_exts_destroy(struct tcf_exts *exts) |
| 1613 | 1901 | { |
| 1614 | 1902 | #ifdef CONFIG_NET_CLS_ACT |
| @@ -1825,6 +2113,10 @@ static int __init tc_filter_init(void) · 210 unmodified lines |
| 1825 | 2113 | rtnl_register(PF_UNSPEC, RTM_DELTFILTER, tc_del_tfilter, NULL, 0); |
| 1826 | 2114 | rtnl_register(PF_UNSPEC, RTM_GETTFILTER, tc_get_tfilter, |
| 1827 | 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 | 2121 | return 0; |
| 1830 | 2122 | |