diff options
| author | Florian Westphal <fw@strlen.de> | 2018-11-07 17:00:38 -0500 |
|---|---|---|
| committer | Steffen Klassert <steffen.klassert@secunet.com> | 2018-11-09 05:58:07 -0500 |
| commit | 9cf545ebd591da673bb6b6c88150212ad83567a9 (patch) | |
| tree | d394bcd44723236e3ef7b7bc3edb14f4d2aa2977 /net/xfrm | |
| parent | 6be3b0db6db82cf056a72cc18042048edd27f8ee (diff) | |
xfrm: policy: store inexact policies in a tree ordered by destination address
This adds inexact lists per destination network, stored in a search tree.
Inexact lookups now return two 'candidate lists', the 'any' policies
('any' destionations), and a list of policies that share same
daddr/prefix.
Next patch will add a second search tree for 'saddr:any' policies
so we can avoid placing those on the 'any:any' list too.
Signed-off-by: Florian Westphal <fw@strlen.de>
Acked-by: David S. Miller <davem@davemloft.net>
Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
Diffstat (limited to 'net/xfrm')
| -rw-r--r-- | net/xfrm/xfrm_policy.c | 333 |
1 files changed, 327 insertions, 6 deletions
diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c index 4eb12e9b40c2..81447d5d02e6 100644 --- a/net/xfrm/xfrm_policy.c +++ b/net/xfrm/xfrm_policy.c | |||
| @@ -49,6 +49,38 @@ struct xfrm_flo { | |||
| 49 | /* prefixes smaller than this are stored in lists, not trees. */ | 49 | /* prefixes smaller than this are stored in lists, not trees. */ |
| 50 | #define INEXACT_PREFIXLEN_IPV4 16 | 50 | #define INEXACT_PREFIXLEN_IPV4 16 |
| 51 | #define INEXACT_PREFIXLEN_IPV6 48 | 51 | #define INEXACT_PREFIXLEN_IPV6 48 |
| 52 | |||
| 53 | struct xfrm_pol_inexact_node { | ||
| 54 | struct rb_node node; | ||
| 55 | union { | ||
| 56 | xfrm_address_t addr; | ||
| 57 | struct rcu_head rcu; | ||
| 58 | }; | ||
| 59 | u8 prefixlen; | ||
| 60 | |||
| 61 | /* the policies matching this node, can be empty list */ | ||
| 62 | struct hlist_head hhead; | ||
| 63 | }; | ||
| 64 | |||
| 65 | /* xfrm inexact policy search tree: | ||
| 66 | * xfrm_pol_inexact_bin = hash(dir,type,family,if_id); | ||
| 67 | * | | ||
| 68 | * +---- root_d: sorted by daddr:prefix | ||
| 69 | * | | | ||
| 70 | * | xfrm_pol_inexact_node | ||
| 71 | * | | | ||
| 72 | * | +- coarse policies and all any:daddr policies | ||
| 73 | * | | ||
| 74 | * +---- coarse policies and all any:any policies | ||
| 75 | * | ||
| 76 | * Lookups return two candidate lists: | ||
| 77 | * 1. any:any list from top-level xfrm_pol_inexact_bin | ||
| 78 | * 2. any:daddr list from daddr tree | ||
| 79 | * | ||
| 80 | * This result set then needs to be searched for the policy with | ||
| 81 | * the lowest priority. If two results have same prio, youngest one wins. | ||
| 82 | */ | ||
| 83 | |||
| 52 | struct xfrm_pol_inexact_key { | 84 | struct xfrm_pol_inexact_key { |
| 53 | possible_net_t net; | 85 | possible_net_t net; |
| 54 | u32 if_id; | 86 | u32 if_id; |
| @@ -62,12 +94,17 @@ struct xfrm_pol_inexact_bin { | |||
| 62 | /* list containing '*:*' policies */ | 94 | /* list containing '*:*' policies */ |
| 63 | struct hlist_head hhead; | 95 | struct hlist_head hhead; |
| 64 | 96 | ||
| 97 | seqcount_t count; | ||
| 98 | /* tree sorted by daddr/prefix */ | ||
| 99 | struct rb_root root_d; | ||
| 100 | |||
| 65 | /* slow path below */ | 101 | /* slow path below */ |
| 66 | struct list_head inexact_bins; | 102 | struct list_head inexact_bins; |
| 67 | struct rcu_head rcu; | 103 | struct rcu_head rcu; |
| 68 | }; | 104 | }; |
| 69 | 105 | ||
| 70 | enum xfrm_pol_inexact_candidate_type { | 106 | enum xfrm_pol_inexact_candidate_type { |
| 107 | XFRM_POL_CAND_DADDR, | ||
| 71 | XFRM_POL_CAND_ANY, | 108 | XFRM_POL_CAND_ANY, |
| 72 | 109 | ||
| 73 | XFRM_POL_CAND_MAX, | 110 | XFRM_POL_CAND_MAX, |
| @@ -658,6 +695,8 @@ xfrm_policy_inexact_alloc_bin(const struct xfrm_policy *pol, u8 dir) | |||
| 658 | 695 | ||
| 659 | bin->k = k; | 696 | bin->k = k; |
| 660 | INIT_HLIST_HEAD(&bin->hhead); | 697 | INIT_HLIST_HEAD(&bin->hhead); |
| 698 | bin->root_d = RB_ROOT; | ||
| 699 | seqcount_init(&bin->count); | ||
| 661 | 700 | ||
| 662 | prev = rhashtable_lookup_get_insert_key(&xfrm_policy_inexact_table, | 701 | prev = rhashtable_lookup_get_insert_key(&xfrm_policy_inexact_table, |
| 663 | &bin->k, &bin->head, | 702 | &bin->k, &bin->head, |
| @@ -708,9 +747,211 @@ xfrm_policy_inexact_insert_use_any_list(const struct xfrm_policy *policy) | |||
| 708 | return saddr_any && daddr_any; | 747 | return saddr_any && daddr_any; |
| 709 | } | 748 | } |
| 710 | 749 | ||
| 750 | static void xfrm_pol_inexact_node_init(struct xfrm_pol_inexact_node *node, | ||
| 751 | const xfrm_address_t *addr, u8 prefixlen) | ||
| 752 | { | ||
| 753 | node->addr = *addr; | ||
| 754 | node->prefixlen = prefixlen; | ||
| 755 | } | ||
| 756 | |||
| 757 | static struct xfrm_pol_inexact_node * | ||
| 758 | xfrm_pol_inexact_node_alloc(const xfrm_address_t *addr, u8 prefixlen) | ||
| 759 | { | ||
| 760 | struct xfrm_pol_inexact_node *node; | ||
| 761 | |||
| 762 | node = kzalloc(sizeof(*node), GFP_ATOMIC); | ||
| 763 | if (node) | ||
| 764 | xfrm_pol_inexact_node_init(node, addr, prefixlen); | ||
| 765 | |||
| 766 | return node; | ||
| 767 | } | ||
| 768 | |||
| 769 | static int xfrm_policy_addr_delta(const xfrm_address_t *a, | ||
| 770 | const xfrm_address_t *b, | ||
| 771 | u8 prefixlen, u16 family) | ||
| 772 | { | ||
| 773 | unsigned int pdw, pbi; | ||
| 774 | int delta = 0; | ||
| 775 | |||
| 776 | switch (family) { | ||
| 777 | case AF_INET: | ||
| 778 | if (sizeof(long) == 4 && prefixlen == 0) | ||
| 779 | return ntohl(a->a4) - ntohl(b->a4); | ||
| 780 | return (ntohl(a->a4) & ((~0UL << (32 - prefixlen)))) - | ||
| 781 | (ntohl(b->a4) & ((~0UL << (32 - prefixlen)))); | ||
| 782 | case AF_INET6: | ||
| 783 | pdw = prefixlen >> 5; | ||
| 784 | pbi = prefixlen & 0x1f; | ||
| 785 | |||
| 786 | if (pdw) { | ||
| 787 | delta = memcmp(a->a6, b->a6, pdw << 2); | ||
| 788 | if (delta) | ||
| 789 | return delta; | ||
| 790 | } | ||
| 791 | if (pbi) { | ||
| 792 | u32 mask = ~0u << (32 - pbi); | ||
| 793 | |||
| 794 | delta = (ntohl(a->a6[pdw]) & mask) - | ||
| 795 | (ntohl(b->a6[pdw]) & mask); | ||
| 796 | } | ||
| 797 | break; | ||
| 798 | default: | ||
| 799 | break; | ||
| 800 | } | ||
| 801 | |||
| 802 | return delta; | ||
| 803 | } | ||
| 804 | |||
| 805 | static void xfrm_policy_inexact_list_reinsert(struct net *net, | ||
| 806 | struct xfrm_pol_inexact_node *n, | ||
| 807 | u16 family) | ||
| 808 | { | ||
| 809 | struct hlist_node *newpos = NULL; | ||
| 810 | struct xfrm_policy *policy, *p; | ||
| 811 | |||
| 812 | list_for_each_entry_reverse(policy, &net->xfrm.policy_all, walk.all) { | ||
| 813 | if (!policy->bydst_reinsert) | ||
| 814 | continue; | ||
| 815 | |||
| 816 | WARN_ON_ONCE(policy->family != family); | ||
| 817 | |||
| 818 | policy->bydst_reinsert = false; | ||
| 819 | hlist_for_each_entry(p, &n->hhead, bydst) { | ||
| 820 | if (policy->priority >= p->priority) | ||
| 821 | newpos = &p->bydst; | ||
| 822 | else | ||
| 823 | break; | ||
| 824 | } | ||
| 825 | |||
| 826 | if (newpos) | ||
| 827 | hlist_add_behind(&policy->bydst, newpos); | ||
| 828 | else | ||
| 829 | hlist_add_head(&policy->bydst, &n->hhead); | ||
| 830 | } | ||
| 831 | } | ||
| 832 | |||
| 833 | /* merge nodes v and n */ | ||
| 834 | static void xfrm_policy_inexact_node_merge(struct net *net, | ||
| 835 | struct xfrm_pol_inexact_node *v, | ||
| 836 | struct xfrm_pol_inexact_node *n, | ||
| 837 | u16 family) | ||
| 838 | { | ||
| 839 | struct xfrm_policy *tmp; | ||
| 840 | |||
| 841 | hlist_for_each_entry(tmp, &v->hhead, bydst) | ||
| 842 | tmp->bydst_reinsert = true; | ||
| 843 | hlist_for_each_entry(tmp, &n->hhead, bydst) | ||
| 844 | tmp->bydst_reinsert = true; | ||
| 845 | |||
| 846 | INIT_HLIST_HEAD(&n->hhead); | ||
| 847 | xfrm_policy_inexact_list_reinsert(net, n, family); | ||
| 848 | } | ||
| 849 | |||
| 850 | static struct xfrm_pol_inexact_node * | ||
| 851 | xfrm_policy_inexact_insert_node(struct net *net, | ||
| 852 | struct rb_root *root, | ||
| 853 | xfrm_address_t *addr, | ||
| 854 | u16 family, u8 prefixlen, u8 dir) | ||
| 855 | { | ||
| 856 | struct xfrm_pol_inexact_node *cached = NULL; | ||
| 857 | struct rb_node **p, *parent = NULL; | ||
| 858 | struct xfrm_pol_inexact_node *node; | ||
| 859 | |||
| 860 | p = &root->rb_node; | ||
| 861 | while (*p) { | ||
| 862 | int delta; | ||
| 863 | |||
| 864 | parent = *p; | ||
| 865 | node = rb_entry(*p, struct xfrm_pol_inexact_node, node); | ||
| 866 | |||
| 867 | delta = xfrm_policy_addr_delta(addr, &node->addr, | ||
| 868 | node->prefixlen, | ||
| 869 | family); | ||
| 870 | if (delta == 0 && prefixlen >= node->prefixlen) { | ||
| 871 | WARN_ON_ONCE(cached); /* ipsec policies got lost */ | ||
| 872 | return node; | ||
| 873 | } | ||
| 874 | |||
| 875 | if (delta < 0) | ||
| 876 | p = &parent->rb_left; | ||
| 877 | else | ||
| 878 | p = &parent->rb_right; | ||
| 879 | |||
| 880 | if (prefixlen < node->prefixlen) { | ||
| 881 | delta = xfrm_policy_addr_delta(addr, &node->addr, | ||
| 882 | prefixlen, | ||
| 883 | family); | ||
| 884 | if (delta) | ||
| 885 | continue; | ||
| 886 | |||
| 887 | /* This node is a subnet of the new prefix. It needs | ||
| 888 | * to be removed and re-inserted with the smaller | ||
| 889 | * prefix and all nodes that are now also covered | ||
| 890 | * by the reduced prefixlen. | ||
| 891 | */ | ||
| 892 | rb_erase(&node->node, root); | ||
| 893 | |||
| 894 | if (!cached) { | ||
| 895 | xfrm_pol_inexact_node_init(node, addr, | ||
| 896 | prefixlen); | ||
| 897 | cached = node; | ||
| 898 | } else { | ||
| 899 | /* This node also falls within the new | ||
| 900 | * prefixlen. Merge the to-be-reinserted | ||
| 901 | * node and this one. | ||
| 902 | */ | ||
| 903 | xfrm_policy_inexact_node_merge(net, node, | ||
| 904 | cached, family); | ||
| 905 | kfree_rcu(node, rcu); | ||
| 906 | } | ||
| 907 | |||
| 908 | /* restart */ | ||
| 909 | p = &root->rb_node; | ||
| 910 | parent = NULL; | ||
| 911 | } | ||
| 912 | } | ||
| 913 | |||
| 914 | node = cached; | ||
| 915 | if (!node) { | ||
| 916 | node = xfrm_pol_inexact_node_alloc(addr, prefixlen); | ||
| 917 | if (!node) | ||
| 918 | return NULL; | ||
| 919 | } | ||
| 920 | |||
| 921 | rb_link_node_rcu(&node->node, parent, p); | ||
| 922 | rb_insert_color(&node->node, root); | ||
| 923 | |||
| 924 | return node; | ||
| 925 | } | ||
| 926 | |||
| 927 | static void xfrm_policy_inexact_gc_tree(struct rb_root *r, bool rm) | ||
| 928 | { | ||
| 929 | struct xfrm_pol_inexact_node *node; | ||
| 930 | struct rb_node *rn = rb_first(r); | ||
| 931 | |||
| 932 | while (rn) { | ||
| 933 | node = rb_entry(rn, struct xfrm_pol_inexact_node, node); | ||
| 934 | |||
| 935 | rn = rb_next(rn); | ||
| 936 | |||
| 937 | if (!hlist_empty(&node->hhead)) { | ||
| 938 | WARN_ON_ONCE(rm); | ||
| 939 | continue; | ||
| 940 | } | ||
| 941 | |||
| 942 | rb_erase(&node->node, r); | ||
| 943 | kfree_rcu(node, rcu); | ||
| 944 | } | ||
| 945 | } | ||
| 946 | |||
| 711 | static void __xfrm_policy_inexact_prune_bin(struct xfrm_pol_inexact_bin *b, bool net_exit) | 947 | static void __xfrm_policy_inexact_prune_bin(struct xfrm_pol_inexact_bin *b, bool net_exit) |
| 712 | { | 948 | { |
| 713 | if (!hlist_empty(&b->hhead)) { | 949 | write_seqcount_begin(&b->count); |
| 950 | xfrm_policy_inexact_gc_tree(&b->root_d, net_exit); | ||
| 951 | write_seqcount_end(&b->count); | ||
| 952 | |||
| 953 | if (!RB_EMPTY_ROOT(&b->root_d) || | ||
| 954 | !hlist_empty(&b->hhead)) { | ||
| 714 | WARN_ON_ONCE(net_exit); | 955 | WARN_ON_ONCE(net_exit); |
| 715 | return; | 956 | return; |
| 716 | } | 957 | } |
| @@ -741,6 +982,37 @@ static void __xfrm_policy_inexact_flush(struct net *net) | |||
| 741 | __xfrm_policy_inexact_prune_bin(bin, false); | 982 | __xfrm_policy_inexact_prune_bin(bin, false); |
| 742 | } | 983 | } |
| 743 | 984 | ||
| 985 | static struct hlist_head * | ||
| 986 | xfrm_policy_inexact_alloc_chain(struct xfrm_pol_inexact_bin *bin, | ||
| 987 | struct xfrm_policy *policy, u8 dir) | ||
| 988 | { | ||
| 989 | struct xfrm_pol_inexact_node *n; | ||
| 990 | struct net *net; | ||
| 991 | |||
| 992 | net = xp_net(policy); | ||
| 993 | lockdep_assert_held(&net->xfrm.xfrm_policy_lock); | ||
| 994 | |||
| 995 | if (xfrm_policy_inexact_insert_use_any_list(policy)) | ||
| 996 | return &bin->hhead; | ||
| 997 | |||
| 998 | if (xfrm_pol_inexact_addr_use_any_list(&policy->selector.daddr, | ||
| 999 | policy->family, | ||
| 1000 | policy->selector.prefixlen_d)) | ||
| 1001 | return &bin->hhead; | ||
| 1002 | |||
| 1003 | /* daddr is fixed */ | ||
| 1004 | write_seqcount_begin(&bin->count); | ||
| 1005 | n = xfrm_policy_inexact_insert_node(net, | ||
| 1006 | &bin->root_d, | ||
| 1007 | &policy->selector.daddr, | ||
| 1008 | policy->family, | ||
| 1009 | policy->selector.prefixlen_d, dir); | ||
| 1010 | write_seqcount_end(&bin->count); | ||
| 1011 | if (!n) | ||
| 1012 | return NULL; | ||
| 1013 | return &n->hhead; | ||
| 1014 | } | ||
| 1015 | |||
| 744 | static struct xfrm_policy * | 1016 | static struct xfrm_policy * |
| 745 | xfrm_policy_inexact_insert(struct xfrm_policy *policy, u8 dir, int excl) | 1017 | xfrm_policy_inexact_insert(struct xfrm_policy *policy, u8 dir, int excl) |
| 746 | { | 1018 | { |
| @@ -756,13 +1028,12 @@ xfrm_policy_inexact_insert(struct xfrm_policy *policy, u8 dir, int excl) | |||
| 756 | net = xp_net(policy); | 1028 | net = xp_net(policy); |
| 757 | lockdep_assert_held(&net->xfrm.xfrm_policy_lock); | 1029 | lockdep_assert_held(&net->xfrm.xfrm_policy_lock); |
| 758 | 1030 | ||
| 759 | if (xfrm_policy_inexact_insert_use_any_list(policy)) { | 1031 | chain = xfrm_policy_inexact_alloc_chain(bin, policy, dir); |
| 760 | chain = &bin->hhead; | 1032 | if (!chain) { |
| 761 | goto insert_to_list; | 1033 | __xfrm_policy_inexact_prune_bin(bin, false); |
| 1034 | return ERR_PTR(-ENOMEM); | ||
| 762 | } | 1035 | } |
| 763 | 1036 | ||
| 764 | chain = &bin->hhead; | ||
| 765 | insert_to_list: | ||
| 766 | delpol = xfrm_policy_insert_list(chain, policy, excl); | 1037 | delpol = xfrm_policy_insert_list(chain, policy, excl); |
| 767 | if (delpol && excl) { | 1038 | if (delpol && excl) { |
| 768 | __xfrm_policy_inexact_prune_bin(bin, false); | 1039 | __xfrm_policy_inexact_prune_bin(bin, false); |
| @@ -843,6 +1114,9 @@ static void xfrm_hash_rebuild(struct work_struct *work) | |||
| 843 | bin = xfrm_policy_inexact_alloc_bin(policy, dir); | 1114 | bin = xfrm_policy_inexact_alloc_bin(policy, dir); |
| 844 | if (!bin) | 1115 | if (!bin) |
| 845 | goto out_unlock; | 1116 | goto out_unlock; |
| 1117 | |||
| 1118 | if (!xfrm_policy_inexact_alloc_chain(bin, policy, dir)) | ||
| 1119 | goto out_unlock; | ||
| 846 | } | 1120 | } |
| 847 | 1121 | ||
| 848 | /* reset the bydst and inexact table in all directions */ | 1122 | /* reset the bydst and inexact table in all directions */ |
| @@ -1462,17 +1736,64 @@ static int xfrm_policy_match(const struct xfrm_policy *pol, | |||
| 1462 | return ret; | 1736 | return ret; |
| 1463 | } | 1737 | } |
| 1464 | 1738 | ||
| 1739 | static struct xfrm_pol_inexact_node * | ||
| 1740 | xfrm_policy_lookup_inexact_addr(const struct rb_root *r, | ||
| 1741 | seqcount_t *count, | ||
| 1742 | const xfrm_address_t *addr, u16 family) | ||
| 1743 | { | ||
| 1744 | const struct rb_node *parent; | ||
| 1745 | int seq; | ||
| 1746 | |||
| 1747 | again: | ||
| 1748 | seq = read_seqcount_begin(count); | ||
| 1749 | |||
| 1750 | parent = rcu_dereference_raw(r->rb_node); | ||
| 1751 | while (parent) { | ||
| 1752 | struct xfrm_pol_inexact_node *node; | ||
| 1753 | int delta; | ||
| 1754 | |||
| 1755 | node = rb_entry(parent, struct xfrm_pol_inexact_node, node); | ||
| 1756 | |||
| 1757 | delta = xfrm_policy_addr_delta(addr, &node->addr, | ||
| 1758 | node->prefixlen, family); | ||
| 1759 | if (delta < 0) { | ||
| 1760 | parent = rcu_dereference_raw(parent->rb_left); | ||
| 1761 | continue; | ||
| 1762 | } else if (delta > 0) { | ||
| 1763 | parent = rcu_dereference_raw(parent->rb_right); | ||
| 1764 | continue; | ||
| 1765 | } | ||
| 1766 | |||
| 1767 | return node; | ||
| 1768 | } | ||
| 1769 | |||
| 1770 | if (read_seqcount_retry(count, seq)) | ||
| 1771 | goto again; | ||
| 1772 | |||
| 1773 | return NULL; | ||
| 1774 | } | ||
| 1775 | |||
| 1465 | static bool | 1776 | static bool |
| 1466 | xfrm_policy_find_inexact_candidates(struct xfrm_pol_inexact_candidates *cand, | 1777 | xfrm_policy_find_inexact_candidates(struct xfrm_pol_inexact_candidates *cand, |
| 1467 | struct xfrm_pol_inexact_bin *b, | 1778 | struct xfrm_pol_inexact_bin *b, |
| 1468 | const xfrm_address_t *saddr, | 1779 | const xfrm_address_t *saddr, |
| 1469 | const xfrm_address_t *daddr) | 1780 | const xfrm_address_t *daddr) |
| 1470 | { | 1781 | { |
| 1782 | struct xfrm_pol_inexact_node *n; | ||
| 1783 | u16 family; | ||
| 1784 | |||
| 1471 | if (!b) | 1785 | if (!b) |
| 1472 | return false; | 1786 | return false; |
| 1473 | 1787 | ||
| 1788 | family = b->k.family; | ||
| 1474 | memset(cand, 0, sizeof(*cand)); | 1789 | memset(cand, 0, sizeof(*cand)); |
| 1475 | cand->res[XFRM_POL_CAND_ANY] = &b->hhead; | 1790 | cand->res[XFRM_POL_CAND_ANY] = &b->hhead; |
| 1791 | |||
| 1792 | n = xfrm_policy_lookup_inexact_addr(&b->root_d, &b->count, daddr, | ||
| 1793 | family); | ||
| 1794 | if (n) | ||
| 1795 | cand->res[XFRM_POL_CAND_DADDR] = &n->hhead; | ||
| 1796 | |||
| 1476 | return true; | 1797 | return true; |
| 1477 | } | 1798 | } |
| 1478 | 1799 | ||
