// SPDX-License-Identifier: GPL-2.0-or-later /* * IPv6 BSD socket options interface * Linux INET6 implementation * * Authors: * Pedro Roque <roque@di.fc.ul.pt> * * Based on linux/net/ipv4/ip_sockglue.c * * FIXME: Make the setsockopt code POSIX compliant: That is * * o Truncate getsockopt returns * o Return an optlen of the truncated length if need be * * Changes: * David L Stevens <dlstevens@us.ibm.com>: * - added multicast source filtering API for MLDv2
*/
staticint copy_group_source_from_sockptr(struct group_source_req *greqs,
sockptr_t optval, int optlen)
{ if (in_compat_syscall()) { struct compat_group_source_req gr32;
if (optlen < sizeof(gr32)) return -EINVAL; if (copy_from_sockptr(&gr32, optval, sizeof(gr32))) return -EFAULT;
greqs->gsr_interface = gr32.gsr_interface;
greqs->gsr_group = gr32.gsr_group;
greqs->gsr_source = gr32.gsr_source;
} else { if (optlen < sizeof(*greqs)) return -EINVAL; if (copy_from_sockptr(greqs, optval, sizeof(*greqs))) return -EFAULT;
}
return 0;
}
staticint do_ipv6_mcast_group_source(struct sock *sk, int optname,
sockptr_t optval, int optlen)
{ struct group_source_req greqs; int omode, add; int ret;
ret = copy_group_source_from_sockptr(&greqs, optval, optlen); if (ret) return ret;
if (greqs.gsr_group.ss_family != AF_INET6 ||
greqs.gsr_source.ss_family != AF_INET6) return -EADDRNOTAVAIL;
int do_ipv6_setsockopt(struct sock *sk, int level, int optname,
sockptr_t optval, unsignedint optlen)
{ struct ipv6_pinfo *np = inet6_sk(sk); struct net *net = sock_net(sk); int retv = -ENOPROTOOPT; int val, valbool;
if (sockptr_is_null(optval))
val = 0; else { if (optlen >= sizeof(int)) { if (copy_from_sockptr(&val, optval, sizeof(val))) return -EFAULT;
} else
val = 0;
}
valbool = (val != 0);
if (ip6_mroute_opt(optname)) return ip6_mroute_setsockopt(sk, optname, optval, optlen);
/* Handle options that can be set without locking the socket. */ switch (optname) { case IPV6_UNICAST_HOPS: if (optlen < sizeof(int)) return -EINVAL; if (val > 255 || val < -1) return -EINVAL;
WRITE_ONCE(np->hop_limit, val); return 0; case IPV6_MULTICAST_LOOP: if (optlen < sizeof(int)) return -EINVAL; if (val != valbool) return -EINVAL;
inet6_assign_bit(MC6_LOOP, sk, valbool); return 0; case IPV6_MULTICAST_HOPS: if (sk->sk_type == SOCK_STREAM) return retv; if (optlen < sizeof(int)) return -EINVAL; if (val > 255 || val < -1) return -EINVAL;
WRITE_ONCE(np->mcast_hops,
val == -1 ? IPV6_DEFAULT_MCASTHOPS : val); return 0; case IPV6_MTU: if (optlen < sizeof(int)) return -EINVAL; if (val && val < IPV6_MIN_MTU) return -EINVAL;
WRITE_ONCE(np->frag_size, val); return 0; case IPV6_MINHOPCOUNT: if (optlen < sizeof(int)) return -EINVAL; if (val < 0 || val > 255) return -EINVAL;
if (val)
static_branch_enable(&ip6_min_hopcount);
/* tcp_v6_err() and tcp_v6_rcv() might read min_hopcount * while we are changing it.
*/
WRITE_ONCE(np->min_hopcount, val); return 0; case IPV6_RECVERR_RFC4884: if (optlen < sizeof(int)) return -EINVAL; if (val < 0 || val > 1) return -EINVAL;
inet6_assign_bit(RECVERR6_RFC4884, sk, valbool); return 0; case IPV6_MULTICAST_ALL: if (optlen < sizeof(int)) return -EINVAL;
inet6_assign_bit(MC6_ALL, sk, valbool); return 0; case IPV6_AUTOFLOWLABEL:
inet6_assign_bit(AUTOFLOWLABEL, sk, valbool);
inet6_set_bit(AUTOFLOWLABEL_SET, sk); return 0; case IPV6_DONTFRAG:
inet6_assign_bit(DONTFRAG, sk, valbool); return 0; case IPV6_RECVERR: if (optlen < sizeof(int)) return -EINVAL;
inet6_assign_bit(RECVERR6, sk, valbool); if (!val)
skb_errqueue_purge(&sk->sk_error_queue); return 0; case IPV6_ROUTER_ALERT_ISOLATE: if (optlen < sizeof(int)) return -EINVAL;
inet6_assign_bit(RTALERT_ISOLATE, sk, valbool); return 0; case IPV6_MTU_DISCOVER: if (optlen < sizeof(int)) return -EINVAL; if (val < IPV6_PMTUDISC_DONT || val > IPV6_PMTUDISC_OMIT) return -EINVAL;
WRITE_ONCE(np->pmtudisc, val); return 0; case IPV6_FLOWINFO_SEND: if (optlen < sizeof(int)) return -EINVAL;
inet6_assign_bit(SNDFLOW, sk, valbool); return 0; case IPV6_ADDR_PREFERENCES: if (optlen < sizeof(int)) return -EINVAL; return ip6_sock_set_addr_preferences(sk, val); case IPV6_MULTICAST_IF: if (sk->sk_type == SOCK_STREAM) return -ENOPROTOOPT; if (optlen < sizeof(int)) return -EINVAL; if (val) { struct net_device *dev; int bound_dev_if, midx;
rcu_read_lock();
dev = dev_get_by_index_rcu(net, val); if (!dev) {
rcu_read_unlock(); return -ENODEV;
}
midx = l3mdev_master_ifindex_rcu(dev);
rcu_read_unlock();
bound_dev_if = READ_ONCE(sk->sk_bound_dev_if); if (bound_dev_if &&
bound_dev_if != val &&
(!midx || midx != bound_dev_if)) return -EINVAL;
}
WRITE_ONCE(np->mcast_oif, val); return 0; case IPV6_UNICAST_IF:
{ struct net_device *dev; int ifindex;
/* Paired with READ_ONCE(sk->sk_prot) in inet6_dgram_ops */
WRITE_ONCE(sk->sk_prot, prot);
WRITE_ONCE(sk->sk_socket->ops, &inet_dgram_ops);
WRITE_ONCE(sk->sk_family, PF_INET);
}
/* Disable all options not to allocate memory anymore, * but there is still a race. See the lockless path * in udpv6_sendmsg() and ipv6_local_rxpmtu().
*/
np->rxopt.all = 0;
case IPV6_V6ONLY: if (optlen < sizeof(int) ||
inet_sk(sk)->inet_num) goto e_inval;
sk->sk_ipv6only = valbool;
retv = 0; break;
case IPV6_RECVPKTINFO: if (optlen < sizeof(int)) goto e_inval;
np->rxopt.bits.rxinfo = valbool;
retv = 0; break;
case IPV6_2292PKTINFO: if (optlen < sizeof(int)) goto e_inval;
np->rxopt.bits.rxoinfo = valbool;
retv = 0; break;
case IPV6_RECVHOPLIMIT: if (optlen < sizeof(int)) goto e_inval;
np->rxopt.bits.rxhlim = valbool;
retv = 0; break;
case IPV6_2292HOPLIMIT: if (optlen < sizeof(int)) goto e_inval;
np->rxopt.bits.rxohlim = valbool;
retv = 0; break;
case IPV6_RECVRTHDR: if (optlen < sizeof(int)) goto e_inval;
np->rxopt.bits.srcrt = valbool;
retv = 0; break;
case IPV6_2292RTHDR: if (optlen < sizeof(int)) goto e_inval;
np->rxopt.bits.osrcrt = valbool;
retv = 0; break;
case IPV6_RECVHOPOPTS: if (optlen < sizeof(int)) goto e_inval;
np->rxopt.bits.hopopts = valbool;
retv = 0; break;
case IPV6_2292HOPOPTS: if (optlen < sizeof(int)) goto e_inval;
np->rxopt.bits.ohopopts = valbool;
retv = 0; break;
case IPV6_RECVDSTOPTS: if (optlen < sizeof(int)) goto e_inval;
np->rxopt.bits.dstopts = valbool;
retv = 0; break;
case IPV6_2292DSTOPTS: if (optlen < sizeof(int)) goto e_inval;
np->rxopt.bits.odstopts = valbool;
retv = 0; break;
case IPV6_TCLASS: if (optlen < sizeof(int)) goto e_inval; if (val < -1 || val > 0xff) goto e_inval; /* RFC 3542, 6.5: default traffic class of 0x0 */ if (val == -1)
val = 0; if (sk->sk_type == SOCK_STREAM) {
val &= ~INET_ECN_MASK;
val |= np->tclass & INET_ECN_MASK;
} if (np->tclass != val) {
np->tclass = val;
sk_dst_reset(sk);
}
retv = 0; break;
case IPV6_RECVTCLASS: if (optlen < sizeof(int)) goto e_inval;
np->rxopt.bits.rxtclass = valbool;
retv = 0; break;
case IPV6_FLOWINFO: if (optlen < sizeof(int)) goto e_inval;
np->rxopt.bits.rxflow = valbool;
retv = 0; break;
case IPV6_RECVPATHMTU: if (optlen < sizeof(int)) goto e_inval;
np->rxopt.bits.rxpmtu = valbool;
retv = 0; break;
case IPV6_TRANSPARENT: if (valbool && !sockopt_ns_capable(net->user_ns, CAP_NET_RAW) &&
!sockopt_ns_capable(net->user_ns, CAP_NET_ADMIN)) {
retv = -EPERM; break;
} if (optlen < sizeof(int)) goto e_inval; /* we don't have a separate transparent bit for IPV6 we use the one in the IPv4 socket */
inet_assign_bit(TRANSPARENT, sk, valbool);
retv = 0; break;
case IPV6_FREEBIND: if (optlen < sizeof(int)) goto e_inval; /* we also don't have a separate freebind bit for IPV6 */
inet_assign_bit(FREEBIND, sk, valbool);
retv = 0; break;
case IPV6_RECVORIGDSTADDR: if (optlen < sizeof(int)) goto e_inval;
np->rxopt.bits.rxorigdstaddr = valbool;
retv = 0; break;
case IPV6_HOPOPTS: case IPV6_RTHDRDSTOPTS: case IPV6_RTHDR: case IPV6_DSTOPTS:
retv = ipv6_set_opt_hdr(sk, optname, optval, optlen); break;
err = do_ipv6_setsockopt(sk, level, optname, optval, optlen); #ifdef CONFIG_NETFILTER /* we need to exclude all possible ENOPROTOOPTs except default case */ if (err == -ENOPROTOOPT && optname != IPV6_IPSEC_POLICY &&
optname != IPV6_XFRM_POLICY)
err = nf_setsockopt(sk, PF_INET6, optname, optval, optlen); #endif return err;
}
EXPORT_SYMBOL(ipv6_setsockopt);
staticint ipv6_getsockopt_sticky(struct sock *sk, struct ipv6_txoptions *opt, int optname, sockptr_t optval, int len)
{ struct ipv6_opt_hdr *hdr;
if (!opt) return 0;
switch (optname) { case IPV6_HOPOPTS:
hdr = opt->hopopt; break; case IPV6_RTHDRDSTOPTS:
hdr = opt->dst0opt; break; case IPV6_RTHDR:
hdr = (struct ipv6_opt_hdr *)opt->srcrt; break; case IPV6_DSTOPTS:
hdr = opt->dst1opt; break; default: return -EINVAL; /* should not happen */
}
if (!hdr) return 0;
len = min_t(unsignedint, len, ipv6_optlen(hdr)); if (copy_to_sockptr(optval, hdr, len)) return -EFAULT; return len;
}
staticint ipv6_get_msfilter(struct sock *sk, sockptr_t optval,
sockptr_t optlen, int len)
{ constint size0 = offsetof(struct group_filter, gf_slist_flex); struct group_filter gsf; int num; int err;
if (len < size0) return -EINVAL; if (copy_from_sockptr(&gsf, optval, size0)) return -EFAULT; if (gsf.gf_group.ss_family != AF_INET6) return -EADDRNOTAVAIL;
num = gsf.gf_numsrc;
sockopt_lock_sock(sk);
err = ip6_mc_msfget(sk, &gsf, optval, size0); if (!err) { if (num > gsf.gf_numsrc)
num = gsf.gf_numsrc;
len = GROUP_FILTER_SIZE(num); if (copy_to_sockptr(optlen, &len, sizeof(int)) ||
copy_to_sockptr(optval, &gsf, size0))
err = -EFAULT;
}
sockopt_release_sock(sk); return err;
}
staticint compat_ipv6_get_msfilter(struct sock *sk, sockptr_t optval,
sockptr_t optlen, int len)
{ constint size0 = offsetof(struct compat_group_filter, gf_slist_flex); struct compat_group_filter gf32; struct group_filter gf; int err; int num;
if (len < size0) return -EINVAL;
if (copy_from_sockptr(&gf32, optval, size0)) return -EFAULT;
gf.gf_interface = gf32.gf_interface;
gf.gf_fmode = gf32.gf_fmode;
num = gf.gf_numsrc = gf32.gf_numsrc;
gf.gf_group = gf32.gf_group;
if (gf.gf_group.ss_family != AF_INET6) return -EADDRNOTAVAIL;
sockopt_lock_sock(sk);
err = ip6_mc_msfget(sk, &gf, optval, size0);
sockopt_release_sock(sk); if (err) return err; if (num > gf.gf_numsrc)
num = gf.gf_numsrc;
len = GROUP_FILTER_SIZE(num) - (sizeof(gf)-sizeof(gf32)); if (copy_to_sockptr(optlen, &len, sizeof(int)) ||
copy_to_sockptr_offset(optval, offsetof(struct compat_group_filter, gf_fmode),
&gf.gf_fmode, sizeof(gf32.gf_fmode)) ||
copy_to_sockptr_offset(optval, offsetof(struct compat_group_filter, gf_numsrc),
&gf.gf_numsrc, sizeof(gf32.gf_numsrc))) return -EFAULT; return 0;
}
int do_ipv6_getsockopt(struct sock *sk, int level, int optname,
sockptr_t optval, sockptr_t optlen)
{ struct ipv6_pinfo *np = inet6_sk(sk); int len; int val;
if (ip6_mroute_opt(optname)) return ip6_mroute_getsockopt(sk, optname, optval, optlen);
if (copy_from_sockptr(&len, optlen, sizeof(int))) return -EFAULT; switch (optname) { case IPV6_ADDRFORM: if (sk->sk_protocol != IPPROTO_UDP &&
sk->sk_protocol != IPPROTO_UDPLITE &&
sk->sk_protocol != IPPROTO_TCP) return -ENOPROTOOPT; if (sk->sk_state != TCP_ESTABLISHED) return -ENOTCONN;
val = sk->sk_family; break; case MCAST_MSFILTER: if (in_compat_syscall()) return compat_ipv6_get_msfilter(sk, optval, optlen, len); return ipv6_get_msfilter(sk, optval, optlen, len); case IPV6_2292PKTOPTIONS:
{ struct msghdr msg; struct sk_buff *skb;
if (sk->sk_type != SOCK_STREAM) return -ENOPROTOOPT;
case IPV6_RECVHOPOPTS:
val = np->rxopt.bits.hopopts; break;
case IPV6_2292HOPOPTS:
val = np->rxopt.bits.ohopopts; break;
case IPV6_RECVDSTOPTS:
val = np->rxopt.bits.dstopts; break;
case IPV6_2292DSTOPTS:
val = np->rxopt.bits.odstopts; break;
case IPV6_TCLASS:
val = np->tclass; break;
case IPV6_RECVTCLASS:
val = np->rxopt.bits.rxtclass; break;
case IPV6_FLOWINFO:
val = np->rxopt.bits.rxflow; break;
case IPV6_RECVPATHMTU:
val = np->rxopt.bits.rxpmtu; break;
case IPV6_PATHMTU:
{ struct dst_entry *dst; struct ip6_mtuinfo mtuinfo;
if (len < sizeof(mtuinfo)) return -EINVAL;
len = sizeof(mtuinfo);
memset(&mtuinfo, 0, sizeof(mtuinfo));
rcu_read_lock();
dst = __sk_dst_get(sk); if (dst)
mtuinfo.ip6m_mtu = dst_mtu(dst);
rcu_read_unlock(); if (!mtuinfo.ip6m_mtu) return -ENOTCONN;
if (copy_to_sockptr(optlen, &len, sizeof(int))) return -EFAULT; if (copy_to_sockptr(optval, &mtuinfo, len)) return -EFAULT;
return 0;
}
case IPV6_TRANSPARENT:
val = inet_test_bit(TRANSPARENT, sk); break;
case IPV6_FREEBIND:
val = inet_test_bit(FREEBIND, sk); break;
case IPV6_RECVORIGDSTADDR:
val = np->rxopt.bits.rxorigdstaddr; break;
case IPV6_UNICAST_HOPS: case IPV6_MULTICAST_HOPS:
{ struct dst_entry *dst;
if (optname == IPV6_UNICAST_HOPS)
val = READ_ONCE(np->hop_limit); else
val = READ_ONCE(np->mcast_hops);
if (val < 0) {
rcu_read_lock();
dst = __sk_dst_get(sk); if (dst)
val = ip6_dst_hoplimit(dst);
rcu_read_unlock();
}
if (val < 0)
val = READ_ONCE(sock_net(sk)->ipv6.devconf_all->hop_limit); break;
}
case IPV6_MULTICAST_LOOP:
val = inet6_test_bit(MC6_LOOP, sk); break;
case IPV6_MULTICAST_IF:
val = READ_ONCE(np->mcast_oif); break;
case IPV6_MULTICAST_ALL:
val = inet6_test_bit(MC6_ALL, sk); break;
case IPV6_UNICAST_IF:
val = (__force int)htonl((__u32) READ_ONCE(np->ucast_oif)); break;
case IPV6_MTU_DISCOVER:
val = READ_ONCE(np->pmtudisc); break;
case IPV6_RECVERR:
val = inet6_test_bit(RECVERR6, sk); break;
case IPV6_FLOWINFO_SEND:
val = inet6_test_bit(SNDFLOW, sk); break;
case IPV6_FLOWLABEL_MGR:
{ struct in6_flowlabel_req freq; int flags;
if (len < sizeof(freq)) return -EINVAL;
if (copy_from_sockptr(&freq, optval, sizeof(freq))) return -EFAULT;
if (freq.flr_action != IPV6_FL_A_GET) return -EINVAL;
len = sizeof(freq);
flags = freq.flr_flags;
memset(&freq, 0, sizeof(freq));
val = ipv6_flowlabel_opt_get(sk, &freq, flags); if (val < 0) return val;
if (copy_to_sockptr(optlen, &len, sizeof(int))) return -EFAULT; if (copy_to_sockptr(optval, &freq, len)) return -EFAULT;
return 0;
}
case IPV6_ADDR_PREFERENCES:
{
u8 srcprefs = READ_ONCE(np->srcprefs);
val = 0;
if (srcprefs & IPV6_PREFER_SRC_TMP)
val |= IPV6_PREFER_SRC_TMP; elseif (srcprefs & IPV6_PREFER_SRC_PUBLIC)
val |= IPV6_PREFER_SRC_PUBLIC; else { /* XXX: should we return system default? */
val |= IPV6_PREFER_SRC_PUBTMP_DEFAULT;
}
if (srcprefs & IPV6_PREFER_SRC_COA)
val |= IPV6_PREFER_SRC_COA; else
val |= IPV6_PREFER_SRC_HOME; break;
} case IPV6_MINHOPCOUNT:
val = READ_ONCE(np->min_hopcount); break;
case IPV6_DONTFRAG:
val = inet6_test_bit(DONTFRAG, sk); break;
case IPV6_AUTOFLOWLABEL:
val = ip6_autoflowlabel(sock_net(sk), sk); break;
case IPV6_RECVFRAGSIZE:
val = np->rxopt.bits.recvfragsize; break;
case IPV6_ROUTER_ALERT:
val = inet6_test_bit(RTALERT, sk); break;
case IPV6_ROUTER_ALERT_ISOLATE:
val = inet6_test_bit(RTALERT_ISOLATE, sk); break;
case IPV6_RECVERR_RFC4884:
val = inet6_test_bit(RECVERR6_RFC4884, sk); break;
default: return -ENOPROTOOPT;
}
len = min_t(unsignedint, sizeof(int), len); if (copy_to_sockptr(optlen, &len, sizeof(int))) return -EFAULT; if (copy_to_sockptr(optval, &val, len)) return -EFAULT; return 0;
}
int ipv6_getsockopt(struct sock *sk, int level, int optname, char __user *optval, int __user *optlen)
{ int err;
err = do_ipv6_getsockopt(sk, level, optname,
USER_SOCKPTR(optval), USER_SOCKPTR(optlen)); #ifdef CONFIG_NETFILTER /* we need to exclude all possible ENOPROTOOPTs except default case */ if (err == -ENOPROTOOPT && optname != IPV6_2292PKTOPTIONS) { int len;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.