Commit ff6a4cf2 authored by Christoph Hellwig's avatar Christoph Hellwig Committed by David S. Miller

net/ipv6: split up ipv6_flowlabel_opt

Split ipv6_flowlabel_opt into a subfunction for each action and a small
wrapper.
Signed-off-by: default avatarChristoph Hellwig <hch@lst.de>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent b43c6153
...@@ -533,187 +533,210 @@ int ipv6_flowlabel_opt_get(struct sock *sk, struct in6_flowlabel_req *freq, ...@@ -533,187 +533,210 @@ int ipv6_flowlabel_opt_get(struct sock *sk, struct in6_flowlabel_req *freq,
return -ENOENT; return -ENOENT;
} }
int ipv6_flowlabel_opt(struct sock *sk, char __user *optval, int optlen) #define socklist_dereference(__sflp) \
rcu_dereference_protected(__sflp, lockdep_is_held(&ip6_sk_fl_lock))
static int ipv6_flowlabel_put(struct sock *sk, struct in6_flowlabel_req *freq)
{ {
int uninitialized_var(err);
struct net *net = sock_net(sk);
struct ipv6_pinfo *np = inet6_sk(sk); struct ipv6_pinfo *np = inet6_sk(sk);
struct in6_flowlabel_req freq;
struct ipv6_fl_socklist *sfl1 = NULL;
struct ipv6_fl_socklist *sfl;
struct ipv6_fl_socklist __rcu **sflp; struct ipv6_fl_socklist __rcu **sflp;
struct ip6_flowlabel *fl, *fl1 = NULL; struct ipv6_fl_socklist *sfl;
if (freq->flr_flags & IPV6_FL_F_REFLECT) {
if (sk->sk_protocol != IPPROTO_TCP)
return -ENOPROTOOPT;
if (!np->repflow)
return -ESRCH;
np->flow_label = 0;
np->repflow = 0;
return 0;
}
if (optlen < sizeof(freq)) spin_lock_bh(&ip6_sk_fl_lock);
return -EINVAL; for (sflp = &np->ipv6_fl_list;
(sfl = socklist_dereference(*sflp)) != NULL;
sflp = &sfl->next) {
if (sfl->fl->label == freq->flr_label)
goto found;
}
spin_unlock_bh(&ip6_sk_fl_lock);
return -ESRCH;
found:
if (freq->flr_label == (np->flow_label & IPV6_FLOWLABEL_MASK))
np->flow_label &= ~IPV6_FLOWLABEL_MASK;
*sflp = sfl->next;
spin_unlock_bh(&ip6_sk_fl_lock);
fl_release(sfl->fl);
kfree_rcu(sfl, rcu);
return 0;
}
if (copy_from_user(&freq, optval, sizeof(freq))) static int ipv6_flowlabel_renew(struct sock *sk, struct in6_flowlabel_req *freq)
return -EFAULT; {
struct ipv6_pinfo *np = inet6_sk(sk);
struct net *net = sock_net(sk);
struct ipv6_fl_socklist *sfl;
int err;
switch (freq.flr_action) { rcu_read_lock_bh();
case IPV6_FL_A_PUT: for_each_sk_fl_rcu(np, sfl) {
if (freq.flr_flags & IPV6_FL_F_REFLECT) { if (sfl->fl->label == freq->flr_label) {
if (sk->sk_protocol != IPPROTO_TCP) err = fl6_renew(sfl->fl, freq->flr_linger,
return -ENOPROTOOPT; freq->flr_expires);
if (!np->repflow) rcu_read_unlock_bh();
return -ESRCH; return err;
np->flow_label = 0;
np->repflow = 0;
return 0;
}
spin_lock_bh(&ip6_sk_fl_lock);
for (sflp = &np->ipv6_fl_list;
(sfl = rcu_dereference_protected(*sflp,
lockdep_is_held(&ip6_sk_fl_lock))) != NULL;
sflp = &sfl->next) {
if (sfl->fl->label == freq.flr_label) {
if (freq.flr_label == (np->flow_label&IPV6_FLOWLABEL_MASK))
np->flow_label &= ~IPV6_FLOWLABEL_MASK;
*sflp = sfl->next;
spin_unlock_bh(&ip6_sk_fl_lock);
fl_release(sfl->fl);
kfree_rcu(sfl, rcu);
return 0;
}
} }
spin_unlock_bh(&ip6_sk_fl_lock); }
return -ESRCH; rcu_read_unlock_bh();
case IPV6_FL_A_RENEW: if (freq->flr_share == IPV6_FL_S_NONE &&
rcu_read_lock_bh(); ns_capable(net->user_ns, CAP_NET_ADMIN)) {
for_each_sk_fl_rcu(np, sfl) { struct ip6_flowlabel *fl = fl_lookup(net, freq->flr_label);
if (sfl->fl->label == freq.flr_label) {
err = fl6_renew(sfl->fl, freq.flr_linger, freq.flr_expires);
rcu_read_unlock_bh();
return err;
}
}
rcu_read_unlock_bh();
if (freq.flr_share == IPV6_FL_S_NONE && if (fl) {
ns_capable(net->user_ns, CAP_NET_ADMIN)) { err = fl6_renew(fl, freq->flr_linger,
fl = fl_lookup(net, freq.flr_label); freq->flr_expires);
if (fl) { fl_release(fl);
err = fl6_renew(fl, freq.flr_linger, freq.flr_expires); return err;
fl_release(fl);
return err;
}
} }
return -ESRCH; }
return -ESRCH;
case IPV6_FL_A_GET: }
if (freq.flr_flags & IPV6_FL_F_REFLECT) {
struct net *net = sock_net(sk);
if (net->ipv6.sysctl.flowlabel_consistency) {
net_info_ratelimited("Can not set IPV6_FL_F_REFLECT if flowlabel_consistency sysctl is enable\n");
return -EPERM;
}
if (sk->sk_protocol != IPPROTO_TCP) static int ipv6_flowlabel_get(struct sock *sk, struct in6_flowlabel_req *freq,
return -ENOPROTOOPT; void __user *optval, int optlen)
{
struct ipv6_fl_socklist *sfl, *sfl1 = NULL;
struct ip6_flowlabel *fl, *fl1 = NULL;
struct ipv6_pinfo *np = inet6_sk(sk);
struct net *net = sock_net(sk);
int uninitialized_var(err);
np->repflow = 1; if (freq->flr_flags & IPV6_FL_F_REFLECT) {
return 0; if (net->ipv6.sysctl.flowlabel_consistency) {
net_info_ratelimited("Can not set IPV6_FL_F_REFLECT if flowlabel_consistency sysctl is enable\n");
return -EPERM;
} }
if (freq.flr_label & ~IPV6_FLOWLABEL_MASK) if (sk->sk_protocol != IPPROTO_TCP)
return -EINVAL; return -ENOPROTOOPT;
np->repflow = 1;
return 0;
}
if (net->ipv6.sysctl.flowlabel_state_ranges && if (freq->flr_label & ~IPV6_FLOWLABEL_MASK)
(freq.flr_label & IPV6_FLOWLABEL_STATELESS_FLAG)) return -EINVAL;
return -ERANGE; if (net->ipv6.sysctl.flowlabel_state_ranges &&
(freq->flr_label & IPV6_FLOWLABEL_STATELESS_FLAG))
return -ERANGE;
fl = fl_create(net, sk, &freq, optval, optlen, &err); fl = fl_create(net, sk, freq, optval, optlen, &err);
if (!fl) if (!fl)
return err; return err;
sfl1 = kmalloc(sizeof(*sfl1), GFP_KERNEL);
if (freq.flr_label) { sfl1 = kmalloc(sizeof(*sfl1), GFP_KERNEL);
err = -EEXIST;
rcu_read_lock_bh(); if (freq->flr_label) {
for_each_sk_fl_rcu(np, sfl) { err = -EEXIST;
if (sfl->fl->label == freq.flr_label) { rcu_read_lock_bh();
if (freq.flr_flags&IPV6_FL_F_EXCL) { for_each_sk_fl_rcu(np, sfl) {
rcu_read_unlock_bh(); if (sfl->fl->label == freq->flr_label) {
goto done; if (freq->flr_flags & IPV6_FL_F_EXCL) {
} rcu_read_unlock_bh();
fl1 = sfl->fl; goto done;
if (!atomic_inc_not_zero(&fl1->users))
fl1 = NULL;
break;
} }
fl1 = sfl->fl;
if (!atomic_inc_not_zero(&fl1->users))
fl1 = NULL;
break;
} }
rcu_read_unlock_bh(); }
rcu_read_unlock_bh();
if (!fl1) if (!fl1)
fl1 = fl_lookup(net, freq.flr_label); fl1 = fl_lookup(net, freq->flr_label);
if (fl1) { if (fl1) {
recheck: recheck:
err = -EEXIST; err = -EEXIST;
if (freq.flr_flags&IPV6_FL_F_EXCL) if (freq->flr_flags&IPV6_FL_F_EXCL)
goto release; goto release;
err = -EPERM; err = -EPERM;
if (fl1->share == IPV6_FL_S_EXCL || if (fl1->share == IPV6_FL_S_EXCL ||
fl1->share != fl->share || fl1->share != fl->share ||
((fl1->share == IPV6_FL_S_PROCESS) && ((fl1->share == IPV6_FL_S_PROCESS) &&
(fl1->owner.pid != fl->owner.pid)) || (fl1->owner.pid != fl->owner.pid)) ||
((fl1->share == IPV6_FL_S_USER) && ((fl1->share == IPV6_FL_S_USER) &&
!uid_eq(fl1->owner.uid, fl->owner.uid))) !uid_eq(fl1->owner.uid, fl->owner.uid)))
goto release; goto release;
err = -ENOMEM; err = -ENOMEM;
if (!sfl1) if (!sfl1)
goto release; goto release;
if (fl->linger > fl1->linger) if (fl->linger > fl1->linger)
fl1->linger = fl->linger; fl1->linger = fl->linger;
if ((long)(fl->expires - fl1->expires) > 0) if ((long)(fl->expires - fl1->expires) > 0)
fl1->expires = fl->expires; fl1->expires = fl->expires;
fl_link(np, sfl1, fl1); fl_link(np, sfl1, fl1);
fl_free(fl); fl_free(fl);
return 0; return 0;
release: release:
fl_release(fl1); fl_release(fl1);
goto done;
}
}
err = -ENOENT;
if (!(freq.flr_flags&IPV6_FL_F_CREATE))
goto done; goto done;
}
}
err = -ENOENT;
if (!(freq->flr_flags & IPV6_FL_F_CREATE))
goto done;
err = -ENOMEM; err = -ENOMEM;
if (!sfl1) if (!sfl1)
goto done; goto done;
err = mem_check(sk); err = mem_check(sk);
if (err != 0) if (err != 0)
goto done; goto done;
fl1 = fl_intern(net, fl, freq.flr_label); fl1 = fl_intern(net, fl, freq->flr_label);
if (fl1) if (fl1)
goto recheck; goto recheck;
if (!freq.flr_label) { if (!freq->flr_label) {
if (copy_to_user(&((struct in6_flowlabel_req __user *) optval)->flr_label, if (copy_to_user(&((struct in6_flowlabel_req __user *) optval)->flr_label,
&fl->label, sizeof(fl->label))) { &fl->label, sizeof(fl->label))) {
/* Intentionally ignore fault. */ /* Intentionally ignore fault. */
}
} }
fl_link(np, sfl1, fl);
return 0;
default:
return -EINVAL;
} }
fl_link(np, sfl1, fl);
return 0;
done: done:
fl_free(fl); fl_free(fl);
kfree(sfl1); kfree(sfl1);
return err; return err;
} }
int ipv6_flowlabel_opt(struct sock *sk, char __user *optval, int optlen)
{
struct in6_flowlabel_req freq;
if (optlen < sizeof(freq))
return -EINVAL;
if (copy_from_user(&freq, optval, sizeof(freq)))
return -EFAULT;
switch (freq.flr_action) {
case IPV6_FL_A_PUT:
return ipv6_flowlabel_put(sk, &freq);
case IPV6_FL_A_RENEW:
return ipv6_flowlabel_renew(sk, &freq);
case IPV6_FL_A_GET:
return ipv6_flowlabel_get(sk, &freq, optval, optlen);
default:
return -EINVAL;
}
}
#ifdef CONFIG_PROC_FS #ifdef CONFIG_PROC_FS
struct ip6fl_iter_state { struct ip6fl_iter_state {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment