📄 xfrm_policy.c
字号:
/* * xfrm_policy.c * * Changes: * Mitsuru KANDA @USAGI * Kazunori MIYAZAWA @USAGI * Kunihiro Ishiguro <kunihiro@ipinfusion.com> * IPv6 support * Kazunori MIYAZAWA @USAGI * YOSHIFUJI Hideaki * Split up af-specific portion * Derek Atkins <derek@ihtfp.com> Add the post_input processor * */#include <linux/slab.h>#include <linux/kmod.h>#include <linux/list.h>#include <linux/spinlock.h>#include <linux/workqueue.h>#include <linux/notifier.h>#include <linux/netdevice.h>#include <linux/netfilter.h>#include <linux/module.h>#include <linux/cache.h>#include <net/xfrm.h>#include <net/ip.h>#include "xfrm_hash.h"int sysctl_xfrm_larval_drop __read_mostly;DEFINE_MUTEX(xfrm_cfg_mutex);EXPORT_SYMBOL(xfrm_cfg_mutex);static DEFINE_RWLOCK(xfrm_policy_lock);unsigned int xfrm_policy_count[XFRM_POLICY_MAX*2];EXPORT_SYMBOL(xfrm_policy_count);static DEFINE_RWLOCK(xfrm_policy_afinfo_lock);static struct xfrm_policy_afinfo *xfrm_policy_afinfo[NPROTO];static struct kmem_cache *xfrm_dst_cache __read_mostly;static struct work_struct xfrm_policy_gc_work;static HLIST_HEAD(xfrm_policy_gc_list);static DEFINE_SPINLOCK(xfrm_policy_gc_lock);static struct xfrm_policy_afinfo *xfrm_policy_get_afinfo(unsigned short family);static void xfrm_policy_put_afinfo(struct xfrm_policy_afinfo *afinfo);static inline int__xfrm4_selector_match(struct xfrm_selector *sel, struct flowi *fl){ return addr_match(&fl->fl4_dst, &sel->daddr, sel->prefixlen_d) && addr_match(&fl->fl4_src, &sel->saddr, sel->prefixlen_s) && !((xfrm_flowi_dport(fl) ^ sel->dport) & sel->dport_mask) && !((xfrm_flowi_sport(fl) ^ sel->sport) & sel->sport_mask) && (fl->proto == sel->proto || !sel->proto) && (fl->oif == sel->ifindex || !sel->ifindex);}static inline int__xfrm6_selector_match(struct xfrm_selector *sel, struct flowi *fl){ return addr_match(&fl->fl6_dst, &sel->daddr, sel->prefixlen_d) && addr_match(&fl->fl6_src, &sel->saddr, sel->prefixlen_s) && !((xfrm_flowi_dport(fl) ^ sel->dport) & sel->dport_mask) && !((xfrm_flowi_sport(fl) ^ sel->sport) & sel->sport_mask) && (fl->proto == sel->proto || !sel->proto) && (fl->oif == sel->ifindex || !sel->ifindex);}int xfrm_selector_match(struct xfrm_selector *sel, struct flowi *fl, unsigned short family){ switch (family) { case AF_INET: return __xfrm4_selector_match(sel, fl); case AF_INET6: return __xfrm6_selector_match(sel, fl); } return 0;}int xfrm_dst_lookup(struct xfrm_dst **dst, struct flowi *fl, unsigned short family){ struct xfrm_policy_afinfo *afinfo = xfrm_policy_get_afinfo(family); int err = 0; if (unlikely(afinfo == NULL)) return -EAFNOSUPPORT; if (likely(afinfo->dst_lookup != NULL)) err = afinfo->dst_lookup(dst, fl); else err = -EINVAL; xfrm_policy_put_afinfo(afinfo); return err;}EXPORT_SYMBOL(xfrm_dst_lookup);static inline unsigned long make_jiffies(long secs){ if (secs >= (MAX_SCHEDULE_TIMEOUT-1)/HZ) return MAX_SCHEDULE_TIMEOUT-1; else return secs*HZ;}static void xfrm_policy_timer(unsigned long data){ struct xfrm_policy *xp = (struct xfrm_policy*)data; unsigned long now = get_seconds(); long next = LONG_MAX; int warn = 0; int dir; read_lock(&xp->lock); if (xp->dead) goto out; dir = xfrm_policy_id2dir(xp->index); if (xp->lft.hard_add_expires_seconds) { long tmo = xp->lft.hard_add_expires_seconds + xp->curlft.add_time - now; if (tmo <= 0) goto expired; if (tmo < next) next = tmo; } if (xp->lft.hard_use_expires_seconds) { long tmo = xp->lft.hard_use_expires_seconds + (xp->curlft.use_time ? : xp->curlft.add_time) - now; if (tmo <= 0) goto expired; if (tmo < next) next = tmo; } if (xp->lft.soft_add_expires_seconds) { long tmo = xp->lft.soft_add_expires_seconds + xp->curlft.add_time - now; if (tmo <= 0) { warn = 1; tmo = XFRM_KM_TIMEOUT; } if (tmo < next) next = tmo; } if (xp->lft.soft_use_expires_seconds) { long tmo = xp->lft.soft_use_expires_seconds + (xp->curlft.use_time ? : xp->curlft.add_time) - now; if (tmo <= 0) { warn = 1; tmo = XFRM_KM_TIMEOUT; } if (tmo < next) next = tmo; } if (warn) km_policy_expired(xp, dir, 0, 0); if (next != LONG_MAX && !mod_timer(&xp->timer, jiffies + make_jiffies(next))) xfrm_pol_hold(xp);out: read_unlock(&xp->lock); xfrm_pol_put(xp); return;expired: read_unlock(&xp->lock); if (!xfrm_policy_delete(xp, dir)) km_policy_expired(xp, dir, 1, 0); xfrm_pol_put(xp);}/* Allocate xfrm_policy. Not used here, it is supposed to be used by pfkeyv2 * SPD calls. */struct xfrm_policy *xfrm_policy_alloc(gfp_t gfp){ struct xfrm_policy *policy; policy = kzalloc(sizeof(struct xfrm_policy), gfp); if (policy) { INIT_HLIST_NODE(&policy->bydst); INIT_HLIST_NODE(&policy->byidx); rwlock_init(&policy->lock); atomic_set(&policy->refcnt, 1); init_timer(&policy->timer); policy->timer.data = (unsigned long)policy; policy->timer.function = xfrm_policy_timer; } return policy;}EXPORT_SYMBOL(xfrm_policy_alloc);/* Destroy xfrm_policy: descendant resources must be released to this moment. */void __xfrm_policy_destroy(struct xfrm_policy *policy){ BUG_ON(!policy->dead); BUG_ON(policy->bundles); if (del_timer(&policy->timer)) BUG(); security_xfrm_policy_free(policy); kfree(policy);}EXPORT_SYMBOL(__xfrm_policy_destroy);static void xfrm_policy_gc_kill(struct xfrm_policy *policy){ struct dst_entry *dst; while ((dst = policy->bundles) != NULL) { policy->bundles = dst->next; dst_free(dst); } if (del_timer(&policy->timer)) atomic_dec(&policy->refcnt); if (atomic_read(&policy->refcnt) > 1) flow_cache_flush(); xfrm_pol_put(policy);}static void xfrm_policy_gc_task(struct work_struct *work){ struct xfrm_policy *policy; struct hlist_node *entry, *tmp; struct hlist_head gc_list; spin_lock_bh(&xfrm_policy_gc_lock); gc_list.first = xfrm_policy_gc_list.first; INIT_HLIST_HEAD(&xfrm_policy_gc_list); spin_unlock_bh(&xfrm_policy_gc_lock); hlist_for_each_entry_safe(policy, entry, tmp, &gc_list, bydst) xfrm_policy_gc_kill(policy);}/* Rule must be locked. Release descentant resources, announce * entry dead. The rule must be unlinked from lists to the moment. */static void xfrm_policy_kill(struct xfrm_policy *policy){ int dead; write_lock_bh(&policy->lock); dead = policy->dead; policy->dead = 1; write_unlock_bh(&policy->lock); if (unlikely(dead)) { WARN_ON(1); return; } spin_lock(&xfrm_policy_gc_lock); hlist_add_head(&policy->bydst, &xfrm_policy_gc_list); spin_unlock(&xfrm_policy_gc_lock); schedule_work(&xfrm_policy_gc_work);}struct xfrm_policy_hash { struct hlist_head *table; unsigned int hmask;};static struct hlist_head xfrm_policy_inexact[XFRM_POLICY_MAX*2];static struct xfrm_policy_hash xfrm_policy_bydst[XFRM_POLICY_MAX*2] __read_mostly;static struct hlist_head *xfrm_policy_byidx __read_mostly;static unsigned int xfrm_idx_hmask __read_mostly;static unsigned int xfrm_policy_hashmax __read_mostly = 1 * 1024 * 1024;static inline unsigned int idx_hash(u32 index){ return __idx_hash(index, xfrm_idx_hmask);}static struct hlist_head *policy_hash_bysel(struct xfrm_selector *sel, unsigned short family, int dir){ unsigned int hmask = xfrm_policy_bydst[dir].hmask; unsigned int hash = __sel_hash(sel, family, hmask); return (hash == hmask + 1 ? &xfrm_policy_inexact[dir] : xfrm_policy_bydst[dir].table + hash);}static struct hlist_head *policy_hash_direct(xfrm_address_t *daddr, xfrm_address_t *saddr, unsigned short family, int dir){ unsigned int hmask = xfrm_policy_bydst[dir].hmask; unsigned int hash = __addr_hash(daddr, saddr, family, hmask); return xfrm_policy_bydst[dir].table + hash;}static void xfrm_dst_hash_transfer(struct hlist_head *list, struct hlist_head *ndsttable, unsigned int nhashmask){ struct hlist_node *entry, *tmp; struct xfrm_policy *pol; hlist_for_each_entry_safe(pol, entry, tmp, list, bydst) { unsigned int h; h = __addr_hash(&pol->selector.daddr, &pol->selector.saddr, pol->family, nhashmask); hlist_add_head(&pol->bydst, ndsttable+h); }}static void xfrm_idx_hash_transfer(struct hlist_head *list, struct hlist_head *nidxtable, unsigned int nhashmask){ struct hlist_node *entry, *tmp; struct xfrm_policy *pol; hlist_for_each_entry_safe(pol, entry, tmp, list, byidx) { unsigned int h; h = __idx_hash(pol->index, nhashmask); hlist_add_head(&pol->byidx, nidxtable+h); }}static unsigned long xfrm_new_hash_mask(unsigned int old_hmask){ return ((old_hmask + 1) << 1) - 1;}static void xfrm_bydst_resize(int dir){ unsigned int hmask = xfrm_policy_bydst[dir].hmask; unsigned int nhashmask = xfrm_new_hash_mask(hmask); unsigned int nsize = (nhashmask + 1) * sizeof(struct hlist_head); struct hlist_head *odst = xfrm_policy_bydst[dir].table; struct hlist_head *ndst = xfrm_hash_alloc(nsize); int i; if (!ndst) return; write_lock_bh(&xfrm_policy_lock); for (i = hmask; i >= 0; i--) xfrm_dst_hash_transfer(odst + i, ndst, nhashmask); xfrm_policy_bydst[dir].table = ndst; xfrm_policy_bydst[dir].hmask = nhashmask; write_unlock_bh(&xfrm_policy_lock); xfrm_hash_free(odst, (hmask + 1) * sizeof(struct hlist_head));}static void xfrm_byidx_resize(int total){ unsigned int hmask = xfrm_idx_hmask; unsigned int nhashmask = xfrm_new_hash_mask(hmask); unsigned int nsize = (nhashmask + 1) * sizeof(struct hlist_head); struct hlist_head *oidx = xfrm_policy_byidx; struct hlist_head *nidx = xfrm_hash_alloc(nsize); int i; if (!nidx) return; write_lock_bh(&xfrm_policy_lock); for (i = hmask; i >= 0; i--) xfrm_idx_hash_transfer(oidx + i, nidx, nhashmask); xfrm_policy_byidx = nidx; xfrm_idx_hmask = nhashmask; write_unlock_bh(&xfrm_policy_lock); xfrm_hash_free(oidx, (hmask + 1) * sizeof(struct hlist_head));}static inline int xfrm_bydst_should_resize(int dir, int *total){ unsigned int cnt = xfrm_policy_count[dir]; unsigned int hmask = xfrm_policy_bydst[dir].hmask; if (total) *total += cnt; if ((hmask + 1) < xfrm_policy_hashmax && cnt > hmask) return 1; return 0;}static inline int xfrm_byidx_should_resize(int total){ unsigned int hmask = xfrm_idx_hmask; if ((hmask + 1) < xfrm_policy_hashmax && total > hmask) return 1; return 0;}void xfrm_spd_getinfo(struct xfrmk_spdinfo *si){ read_lock_bh(&xfrm_policy_lock); si->incnt = xfrm_policy_count[XFRM_POLICY_IN]; si->outcnt = xfrm_policy_count[XFRM_POLICY_OUT]; si->fwdcnt = xfrm_policy_count[XFRM_POLICY_FWD]; si->inscnt = xfrm_policy_count[XFRM_POLICY_IN+XFRM_POLICY_MAX]; si->outscnt = xfrm_policy_count[XFRM_POLICY_OUT+XFRM_POLICY_MAX]; si->fwdscnt = xfrm_policy_count[XFRM_POLICY_FWD+XFRM_POLICY_MAX]; si->spdhcnt = xfrm_idx_hmask; si->spdhmcnt = xfrm_policy_hashmax; read_unlock_bh(&xfrm_policy_lock);}EXPORT_SYMBOL(xfrm_spd_getinfo);static DEFINE_MUTEX(hash_resize_mutex);static void xfrm_hash_resize(struct work_struct *__unused){ int dir, total; mutex_lock(&hash_resize_mutex); total = 0; for (dir = 0; dir < XFRM_POLICY_MAX * 2; dir++) { if (xfrm_bydst_should_resize(dir, &total)) xfrm_bydst_resize(dir); } if (xfrm_byidx_should_resize(total)) xfrm_byidx_resize(total); mutex_unlock(&hash_resize_mutex);}static DECLARE_WORK(xfrm_hash_work, xfrm_hash_resize);/* Generate new index... KAME seems to generate them ordered by cost * of an absolute inpredictability of ordering of rules. This will not pass. */static u32 xfrm_gen_index(u8 type, int dir){ static u32 idx_generator; for (;;) { struct hlist_node *entry; struct hlist_head *list; struct xfrm_policy *p; u32 idx; int found; idx = (idx_generator | dir); idx_generator += 8; if (idx == 0) idx = 8; list = xfrm_policy_byidx + idx_hash(idx); found = 0; hlist_for_each_entry(p, entry, list, byidx) { if (p->index == idx) { found = 1; break; } } if (!found) return idx; }}static inline int selector_cmp(struct xfrm_selector *s1, struct xfrm_selector *s2){ u32 *p1 = (u32 *) s1; u32 *p2 = (u32 *) s2; int len = sizeof(struct xfrm_selector) / sizeof(u32); int i; for (i = 0; i < len; i++) { if (p1[i] != p2[i]) return 1; } return 0;}int xfrm_policy_insert(int dir, struct xfrm_policy *policy, int excl){ struct xfrm_policy *pol; struct xfrm_policy *delpol; struct hlist_head *chain; struct hlist_node *entry, *newpos; struct dst_entry *gc_list; write_lock_bh(&xfrm_policy_lock); chain = policy_hash_bysel(&policy->selector, policy->family, dir); delpol = NULL; newpos = NULL; hlist_for_each_entry(pol, entry, chain, bydst) { if (pol->type == policy->type && !selector_cmp(&pol->selector, &policy->selector) && xfrm_sec_ctx_match(pol->security, policy->security) && !WARN_ON(delpol)) { if (excl) { write_unlock_bh(&xfrm_policy_lock); return -EEXIST; } delpol = pol; if (policy->priority > pol->priority) continue; } else if (policy->priority >= pol->priority) { newpos = &pol->bydst; continue; } if (delpol) break; } if (newpos) hlist_add_after(newpos, &policy->bydst); else hlist_add_head(&policy->bydst, chain); xfrm_pol_hold(policy); xfrm_policy_count[dir]++; atomic_inc(&flow_cache_genid); if (delpol) { hlist_del(&delpol->bydst); hlist_del(&delpol->byidx); xfrm_policy_count[dir]--; } policy->index = delpol ? delpol->index : xfrm_gen_index(policy->type, dir); hlist_add_head(&policy->byidx, xfrm_policy_byidx+idx_hash(policy->index)); policy->curlft.add_time = get_seconds(); policy->curlft.use_time = 0; if (!mod_timer(&policy->timer, jiffies + HZ)) xfrm_pol_hold(policy); write_unlock_bh(&xfrm_policy_lock); if (delpol) xfrm_policy_kill(delpol); else if (xfrm_bydst_should_resize(dir, NULL)) schedule_work(&xfrm_hash_work); read_lock_bh(&xfrm_policy_lock); gc_list = NULL; entry = &policy->bydst; hlist_for_each_entry_continue(policy, entry, bydst) { struct dst_entry *dst; write_lock(&policy->lock); dst = policy->bundles; if (dst) { struct dst_entry *tail = dst; while (tail->next) tail = tail->next; tail->next = gc_list; gc_list = dst; policy->bundles = NULL; } write_unlock(&policy->lock); } read_unlock_bh(&xfrm_policy_lock); while (gc_list) { struct dst_entry *dst = gc_list; gc_list = dst->next; dst_free(dst); } return 0;}EXPORT_SYMBOL(xfrm_policy_insert);struct xfrm_policy *xfrm_policy_bysel_ctx(u8 type, int dir, struct xfrm_selector *sel, struct xfrm_sec_ctx *ctx, int delete, int *err){ struct xfrm_policy *pol, *ret; struct hlist_head *chain; struct hlist_node *entry; *err = 0; write_lock_bh(&xfrm_policy_lock); chain = policy_hash_bysel(sel, sel->family, dir);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -