diff options
Diffstat (limited to 'net/key/af_key.c')
-rw-r--r-- | net/key/af_key.c | 143 |
1 files changed, 110 insertions, 33 deletions
diff --git a/net/key/af_key.c b/net/key/af_key.c index e9ef9af4a53..1fb0fe42a72 100644 --- a/net/key/af_key.c +++ b/net/key/af_key.c @@ -48,6 +48,17 @@ struct pfkey_sock { struct sock sk; int registered; int promisc; + + struct { + uint8_t msg_version; + uint32_t msg_pid; + int (*dump)(struct pfkey_sock *sk); + void (*done)(struct pfkey_sock *sk); + union { + struct xfrm_policy_walk policy; + struct xfrm_state_walk state; + } u; + } dump; }; static inline struct pfkey_sock *pfkey_sk(struct sock *sk) @@ -55,6 +66,27 @@ static inline struct pfkey_sock *pfkey_sk(struct sock *sk) return (struct pfkey_sock *)sk; } +static int pfkey_can_dump(struct sock *sk) +{ + if (3 * atomic_read(&sk->sk_rmem_alloc) <= 2 * sk->sk_rcvbuf) + return 1; + return 0; +} + +static int pfkey_do_dump(struct pfkey_sock *pfk) +{ + int rc; + + rc = pfk->dump.dump(pfk); + if (rc == -ENOBUFS) + return 0; + + pfk->dump.done(pfk); + pfk->dump.dump = NULL; + pfk->dump.done = NULL; + return rc; +} + static void pfkey_sock_destruct(struct sock *sk) { skb_queue_purge(&sk->sk_receive_queue); @@ -1709,45 +1741,60 @@ static int pfkey_flush(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hd return 0; } -struct pfkey_dump_data -{ - struct sk_buff *skb; - struct sadb_msg *hdr; - struct sock *sk; -}; - static int dump_sa(struct xfrm_state *x, int count, void *ptr) { - struct pfkey_dump_data *data = ptr; + struct pfkey_sock *pfk = ptr; struct sk_buff *out_skb; struct sadb_msg *out_hdr; + if (!pfkey_can_dump(&pfk->sk)) + return -ENOBUFS; + out_skb = pfkey_xfrm_state2msg(x); if (IS_ERR(out_skb)) return PTR_ERR(out_skb); out_hdr = (struct sadb_msg *) out_skb->data; - out_hdr->sadb_msg_version = data->hdr->sadb_msg_version; + out_hdr->sadb_msg_version = pfk->dump.msg_version; out_hdr->sadb_msg_type = SADB_DUMP; out_hdr->sadb_msg_satype = pfkey_proto2satype(x->id.proto); out_hdr->sadb_msg_errno = 0; out_hdr->sadb_msg_reserved = 0; out_hdr->sadb_msg_seq = count; - out_hdr->sadb_msg_pid = data->hdr->sadb_msg_pid; - pfkey_broadcast(out_skb, GFP_ATOMIC, BROADCAST_ONE, data->sk); + out_hdr->sadb_msg_pid = pfk->dump.msg_pid; + pfkey_broadcast(out_skb, GFP_ATOMIC, BROADCAST_ONE, &pfk->sk); return 0; } +static int pfkey_dump_sa(struct pfkey_sock *pfk) +{ + return xfrm_state_walk(&pfk->dump.u.state, dump_sa, (void *) pfk); +} + +static void pfkey_dump_sa_done(struct pfkey_sock *pfk) +{ + xfrm_state_walk_done(&pfk->dump.u.state); +} + static int pfkey_dump(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs) { u8 proto; - struct pfkey_dump_data data = { .skb = skb, .hdr = hdr, .sk = sk }; + struct pfkey_sock *pfk = pfkey_sk(sk); + + if (pfk->dump.dump != NULL) + return -EBUSY; proto = pfkey_satype2proto(hdr->sadb_msg_satype); if (proto == 0) return -EINVAL; - return xfrm_state_walk(proto, dump_sa, &data); + pfk->dump.msg_version = hdr->sadb_msg_version; + pfk->dump.msg_pid = hdr->sadb_msg_pid; + pfk->dump.dump = pfkey_dump_sa; + pfk->dump.done = pfkey_dump_sa_done; + xfrm_state_walk_init(&pfk->dump.u.state, proto); + + return pfkey_do_dump(pfk); } static int pfkey_promisc(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs) @@ -1780,7 +1827,9 @@ static int check_reqid(struct xfrm_policy *xp, int dir, int count, void *ptr) static u32 gen_reqid(void) { + struct xfrm_policy_walk walk; u32 start; + int rc; static u32 reqid = IPSEC_MANUAL_REQID_MAX; start = reqid; @@ -1788,8 +1837,10 @@ static u32 gen_reqid(void) ++reqid; if (reqid == 0) reqid = IPSEC_MANUAL_REQID_MAX+1; - if (xfrm_policy_walk(XFRM_POLICY_TYPE_MAIN, check_reqid, - (void*)&reqid) != -EEXIST) + xfrm_policy_walk_init(&walk, XFRM_POLICY_TYPE_MAIN); + rc = xfrm_policy_walk(&walk, check_reqid, (void*)&reqid); + xfrm_policy_walk_done(&walk); + if (rc != -EEXIST) return reqid; } while (reqid != start); return 0; @@ -2241,7 +2292,7 @@ static int pfkey_spdadd(struct sock *sk, struct sk_buff *skb, struct sadb_msg *h goto out; } - err = security_xfrm_policy_alloc(xp, uctx); + err = security_xfrm_policy_alloc(&xp->security, uctx); kfree(uctx); if (err) @@ -2301,10 +2352,11 @@ static int pfkey_spddelete(struct sock *sk, struct sk_buff *skb, struct sadb_msg int err; struct sadb_address *sa; struct sadb_x_policy *pol; - struct xfrm_policy *xp, tmp; + struct xfrm_policy *xp; struct xfrm_selector sel; struct km_event c; struct sadb_x_sec_ctx *sec_ctx; + struct xfrm_sec_ctx *pol_ctx; if (!present_and_same_family(ext_hdrs[SADB_EXT_ADDRESS_SRC-1], ext_hdrs[SADB_EXT_ADDRESS_DST-1]) || @@ -2334,25 +2386,23 @@ static int pfkey_spddelete(struct sock *sk, struct sk_buff *skb, struct sadb_msg sel.dport_mask = htons(0xffff); sec_ctx = (struct sadb_x_sec_ctx *) ext_hdrs[SADB_X_EXT_SEC_CTX-1]; - memset(&tmp, 0, sizeof(struct xfrm_policy)); - if (sec_ctx != NULL) { struct xfrm_user_sec_ctx *uctx = pfkey_sadb2xfrm_user_sec_ctx(sec_ctx); if (!uctx) return -ENOMEM; - err = security_xfrm_policy_alloc(&tmp, uctx); + err = security_xfrm_policy_alloc(&pol_ctx, uctx); kfree(uctx); - if (err) return err; - } - - xp = xfrm_policy_bysel_ctx(XFRM_POLICY_TYPE_MAIN, pol->sadb_x_policy_dir-1, - &sel, tmp.security, 1, &err); - security_xfrm_policy_free(&tmp); + } else + pol_ctx = NULL; + xp = xfrm_policy_bysel_ctx(XFRM_POLICY_TYPE_MAIN, + pol->sadb_x_policy_dir - 1, &sel, pol_ctx, + 1, &err); + security_xfrm_policy_free(pol_ctx); if (xp == NULL) return -ENOENT; @@ -2638,11 +2688,14 @@ out: static int dump_sp(struct xfrm_policy *xp, int dir, int count, void *ptr) { - struct pfkey_dump_data *data = ptr; + struct pfkey_sock *pfk = ptr; struct sk_buff *out_skb; struct sadb_msg *out_hdr; int err; + if (!pfkey_can_dump(&pfk->sk)) + return -ENOBUFS; + out_skb = pfkey_xfrm_policy2msg_prep(xp); if (IS_ERR(out_skb)) return PTR_ERR(out_skb); @@ -2652,21 +2705,40 @@ static int dump_sp(struct xfrm_policy *xp, int dir, int count, void *ptr) return err; out_hdr = (struct sadb_msg *) out_skb->data; - out_hdr->sadb_msg_version = data->hdr->sadb_msg_version; + out_hdr->sadb_msg_version = pfk->dump.msg_version; out_hdr->sadb_msg_type = SADB_X_SPDDUMP; out_hdr->sadb_msg_satype = SADB_SATYPE_UNSPEC; out_hdr->sadb_msg_errno = 0; out_hdr->sadb_msg_seq = count; - out_hdr->sadb_msg_pid = data->hdr->sadb_msg_pid; - pfkey_broadcast(out_skb, GFP_ATOMIC, BROADCAST_ONE, data->sk); + out_hdr->sadb_msg_pid = pfk->dump.msg_pid; + pfkey_broadcast(out_skb, GFP_ATOMIC, BROADCAST_ONE, &pfk->sk); return 0; } +static int pfkey_dump_sp(struct pfkey_sock *pfk) +{ + return xfrm_policy_walk(&pfk->dump.u.policy, dump_sp, (void *) pfk); +} + +static void pfkey_dump_sp_done(struct pfkey_sock *pfk) +{ + xfrm_policy_walk_done(&pfk->dump.u.policy); +} + static int pfkey_spddump(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs) { - struct pfkey_dump_data data = { .skb = skb, .hdr = hdr, .sk = sk }; + struct pfkey_sock *pfk = pfkey_sk(sk); + + if (pfk->dump.dump != NULL) + return -EBUSY; + + pfk->dump.msg_version = hdr->sadb_msg_version; + pfk->dump.msg_pid = hdr->sadb_msg_pid; + pfk->dump.dump = pfkey_dump_sp; + pfk->dump.done = pfkey_dump_sp_done; + xfrm_policy_walk_init(&pfk->dump.u.policy, XFRM_POLICY_TYPE_MAIN); - return xfrm_policy_walk(XFRM_POLICY_TYPE_MAIN, dump_sp, &data); + return pfkey_do_dump(pfk); } static int key_notify_policy_flush(struct km_event *c) @@ -3225,7 +3297,7 @@ static struct xfrm_policy *pfkey_compile_policy(struct sock *sk, int opt, if ((*dir = verify_sec_ctx_len(p))) goto out; uctx = pfkey_sadb2xfrm_user_sec_ctx(sec_ctx); - *dir = security_xfrm_policy_alloc(xp, uctx); + *dir = security_xfrm_policy_alloc(&xp->security, uctx); kfree(uctx); if (*dir) @@ -3671,6 +3743,7 @@ static int pfkey_recvmsg(struct kiocb *kiocb, int flags) { struct sock *sk = sock->sk; + struct pfkey_sock *pfk = pfkey_sk(sk); struct sk_buff *skb; int copied, err; @@ -3698,6 +3771,10 @@ static int pfkey_recvmsg(struct kiocb *kiocb, err = (flags & MSG_TRUNC) ? skb->len : copied; + if (pfk->dump.dump != NULL && + 3 * atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf) + pfkey_do_dump(pfk); + out_free: skb_free_datagram(sk, skb); out: |