On 5/18/2026 2:27 PM, Warner Losh wrote:
> Add the getsockopt(2) implementation handling SOL_SOCKET, IPPROTO_TCP,
> IPPROTO_IP, and IPPROTO_IPV6 levels with proper target-to-host option
> name translation.
>
> Signed-off-by: Stacey Son <[email protected]>
> Signed-off-by: Mikaƫl Urankar <[email protected]>
> Signed-off-by: Kyle Evans <[email protected]>
> Signed-off-by: Warner Losh <[email protected]>
> Assisted-by: Claude Opus 4.6 (1M context)
> ---
> bsd-user/freebsd/os-socket.h | 284
> +++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 284 insertions(+)
>
> diff --git a/bsd-user/freebsd/os-socket.h b/bsd-user/freebsd/os-socket.h
> index 897a1f4d2f..f8521780c1 100644
> --- a/bsd-user/freebsd/os-socket.h
> +++ b/bsd-user/freebsd/os-socket.h
> @@ -399,5 +399,289 @@ unimplemented:
> return ret;
> }
>
> +/* getsockopt(2) */
> +static inline abi_long do_bsd_getsockopt(int sockfd, int level, int optname,
> + abi_ulong optval_addr, abi_ulong optlen)
> +{
> + abi_long ret;
> + int len, val;
> + socklen_t lv;
> + void *p;
> +
> + switch (level) {
> + case TARGET_SOL_SOCKET:
> + level = SOL_SOCKET;
> + switch (optname) {
> +
> + /* These don't just return a single integer */
> + case TARGET_SO_LINGER:
> + case TARGET_SO_RCVTIMEO:
> + case TARGET_SO_SNDTIMEO:
> + case TARGET_SO_ACCEPTFILTER:
> + goto unimplemented;
> +
> + /* Options with 'int' argument. */
> + case TARGET_SO_DEBUG:
> + optname = SO_DEBUG;
> + goto int_case;
> +
> + case TARGET_SO_REUSEADDR:
> + optname = SO_REUSEADDR;
> + goto int_case;
> +
> + case TARGET_SO_REUSEPORT:
> + optname = SO_REUSEPORT;
> + goto int_case;
> +
> + case TARGET_SO_TYPE:
> + optname = SO_TYPE;
> + goto int_case;
> +
> + case TARGET_SO_ERROR:
> + optname = SO_ERROR;
> + goto int_case;
> +
> + case TARGET_SO_DONTROUTE:
> + optname = SO_DONTROUTE;
> + goto int_case;
> +
> + case TARGET_SO_BROADCAST:
> + optname = SO_BROADCAST;
> + goto int_case;
> +
> + case TARGET_SO_SNDBUF:
> + optname = SO_SNDBUF;
> + goto int_case;
> +
> + case TARGET_SO_RCVBUF:
> + optname = SO_RCVBUF;
> + goto int_case;
> +
> + case TARGET_SO_KEEPALIVE:
> + optname = SO_KEEPALIVE;
> + goto int_case;
> +
> + case TARGET_SO_OOBINLINE:
> + optname = SO_OOBINLINE;
> + goto int_case;
> +
> + case TARGET_SO_TIMESTAMP:
> + optname = SO_TIMESTAMP;
> + goto int_case;
> +
> + case TARGET_SO_RCVLOWAT:
> + optname = SO_RCVLOWAT;
> + goto int_case;
> +
> + case TARGET_SO_LISTENINCQLEN:
> + optname = SO_LISTENINCQLEN;
> + goto int_case;
> +
This duplicates what there is in previous patch.
We should add a target_to_host_sockopt that takes care of this.
> + default:
> +int_case:
> + if (get_user_u32(len, optlen)) {
> + return -TARGET_EFAULT;
> + }
> + if (len < 0) {
> + return -TARGET_EINVAL;
> + }
> + lv = sizeof(lv);
> + ret = get_errno(getsockopt(sockfd, level, optname, &val, &lv));
> + if (ret < 0) {
> + return ret;
> + }
> + if (len > lv) {
> + len = lv;
> + }
> + if (len == 4) {
> + if (put_user_u32(val, optval_addr)) {
> + return -TARGET_EFAULT;
> + }
> + } else {
> + if (put_user_u8(val, optval_addr)) {
> + return -TARGET_EFAULT;
> + }
> + }
> + if (put_user_u32(len, optlen)) {
> + return -TARGET_EFAULT;
> + }
> + break;
> +
> + }
> + break;
> +
> + case IPPROTO_TCP:
> + /* TCP options all take an 'int' value. */
> + goto int_case;
> +
> + case IPPROTO_IP:
> + switch (optname) {
> + case IP_OPTIONS:
> + if (get_user_u32(len, optlen)) {
> + return -TARGET_EFAULT;
> + }
> + lv = (socklen_t)len;
> + p = lock_user(VERIFY_WRITE, optval_addr, len, 0);
> + if (p == NULL) {
> + return -TARGET_EFAULT;
> + }
> + ret = get_errno(getsockopt(sockfd, level, optname, p, &lv));
> + unlock_user(p, optval_addr, len);
> + if (put_user_u32(lv, optlen)) {
> + return -TARGET_EFAULT;
> + }
> + break;
> + case IP_HDRINCL:
> + case IP_TOS:
> + case IP_TTL:
> + case IP_RECVOPTS:
> + case IP_RECVRETOPTS:
> + case IP_RECVDSTADDR:
> +
> + case IP_RETOPTS:
> +#if defined(IP_RECVTOS)
> + case IP_RECVTOS:
> +#endif
> + case IP_MULTICAST_TTL:
> + case IP_MULTICAST_LOOP:
> + case IP_PORTRANGE:
> + case IP_IPSEC_POLICY:
> + case IP_ONESBCAST:
> + case IP_BINDANY:
> + if (get_user_u32(len, optlen)) {
> + return -TARGET_EFAULT;
> + }
> + if (len < 0) {
> + return -TARGET_EINVAL;
> + }
> + lv = sizeof(lv);
> + ret = get_errno(getsockopt(sockfd, level, optname,
> + &val, &lv));
> + if (ret < 0) {
> + return ret;
> + }
> + if (len < sizeof(int) && len > 0 && val >= 0 &&
> + val < 255) {
> + len = 1;
> + if (put_user_u32(len, optlen) ||
> + put_user_u8(val, optval_addr)) {
> + return -TARGET_EFAULT;
> + }
> + } else {
> + if (len > sizeof(int)) {
> + len = sizeof(int);
> + }
> + if (put_user_u32(len, optlen) ||
> + put_user_u32(val, optval_addr)) {
> + return -TARGET_EFAULT;
> + }
> + }
> + break;
> +
> + default:
> + goto unimplemented;
> + }
> + break;
> +
> + case IPPROTO_IPV6:
> + switch (optname) {
> + case IPV6_UNICAST_HOPS: /* int; IP6 hops */
> + case IPV6_MULTICAST_IF: /* u_int; set/get IP6 multicast i/f */
> + case IPV6_MULTICAST_HOPS: /* int; set/get IP6 multicast hops */
> + case IPV6_MULTICAST_LOOP: /* u_int; set/get IP6 multicast loopback
> */
> + case IPV6_PORTRANGE: /* int; range to choose for unspec port
> */
> + case IPV6_CHECKSUM: /* int; checksum offset for raw socket */
> + case IPV6_V6ONLY: /* bool; make AF_INET6 sockets v6 only */
> + case IPV6_RECVPKTINFO: /* bool; recv if, dst addr */
> + case IPV6_RECVHOPLIMIT: /* bool; recv hop limit */
> + case IPV6_RECVRTHDR: /* bool; recv routing header */
> + case IPV6_RECVHOPOPTS: /* bool; recv hop-by-hop option */
> + case IPV6_RECVDSTOPTS: /* bool; recv dst option after rthdr */
> + case IPV6_USE_MIN_MTU: /* bool; send packets at the minimum MTU
> */
> + case IPV6_RECVPATHMTU: /* bool; notify an according MTU */
> + case IPV6_HOPLIMIT: /* int; send hop limit */
> + case IPV6_RECVTCLASS: /* bool; recv traffic class values */
> + case IPV6_AUTOFLOWLABEL: /* bool; attach flowlabel automagically
> */
> + case IPV6_TCLASS: /* int; send traffic class value */
> + case IPV6_DONTFRAG: /* bool; disable IPv6 fragmentation */
> + case IPV6_PREFER_TEMPADDR: /* int; prefer temporary addresses */
> + case IPV6_BINDANY: /* bool: allow bind to any address */
> +#ifdef IPV6_BINDMULTI
> + case IPV6_BINDMULTI: /* bool; allow multibind to same
> addr/port*/
> +#endif /* IPV6_BINDMULTI */
> +#ifdef IPV6_RSS_LISTEN_BUCKET
> + case IPV6_RSS_LISTEN_BUCKET: /* int; set RSS listen bucket */
> +#endif /* IPV6_RSS_LISTEN_BUCKET */
> +#ifdef IPV6_FLOWID
> + case IPV6_FLOWID: /* int; flowid of given socket */
> +#endif /* IPV6_FLOWID */
> +#ifdef IPV6_FLOWTYPE
> + case IPV6_FLOWTYPE: /* int; flowtype of given socket */
> +#endif /* IPV6_FLOWTYPE */
> +#ifdef IPV6_RSSBUCKETID
> + case IPV6_RSSBUCKETID: /* int; RSS bucket ID of given socket */
> +#endif /* IPV6_RSSBUCKETID */
> + if (get_user_u32(len, optlen)) {
> + return -TARGET_EFAULT;
> + }
> + if (len < 0) {
> + return -TARGET_EINVAL;
> + }
> + lv = sizeof(lv);
> + ret = get_errno(getsockopt(sockfd, level, optname,
> + &val, &lv));
> + if (ret < 0) {
> + return ret;
> + }
> + if (len < sizeof(int) && len > 0 && val >= 0 &&
> + val < 255) {
> + len = 1;
> + if (put_user_u32(len, optlen) ||
> + put_user_u8(val, optval_addr)) {
> + return -TARGET_EFAULT;
> + }
> + } else {
> + if (len > sizeof(int)) {
> + len = sizeof(int);
> + }
> + if (put_user_u32(len, optlen) ||
> + put_user_u32(val, optval_addr)) {
> + return -TARGET_EFAULT;
> + }
> + }
> + break;
> +
> + case IPV6_JOIN_GROUP: /* ipv6_mreq; join a group membership */
> + case IPV6_LEAVE_GROUP: /* ipv6_mreq; leave a group membership */
> + case ICMP6_FILTER: /* icmp6_filter; icmp6 filter */
> + case IPV6_IPSEC_POLICY: /* struct; get/set security policy */
> + case IPV6_FW_ADD: /* add a firewall rule to chain */
> + case IPV6_FW_DEL: /* delete a firewall rule from chain */
> + case IPV6_FW_FLUSH: /* flush firewall rule chain */
> + case IPV6_FW_ZERO: /* clear single/all firewall counter(s) */
> + case IPV6_FW_GET: /* get entire firewall rule chain */
> + case IPV6_RTHDRDSTOPTS: /* ip6_dest; send dst option before rthdr */
> + case IPV6_PATHMTU: /* mtuinfo; get the current path MTU */
> + case IPV6_PKTINFO: /* in6_pktinfo; send if, src addr */
> + case IPV6_NEXTHOP: /* sockaddr; next hop addr */
> + case IPV6_HOPOPTS: /* ip6_hbh; send hop-by-hop option */
> + case IPV6_DSTOPTS: /* ip6_dest; send dst option befor rthdr */
> + case IPV6_RTHDR: /* ip6_rthdr; send routing header */
> + case IPV6_MSFILTER: /* struct __msfilterreq; */
> + default:
> + goto unimplemented;
> + }
> + break;
> +
> + default:
> +unimplemented:
> + gemu_log("getsockopt level=%d optname=%d not yet supported\n",
> + level, optname);
> + ret = -TARGET_EOPNOTSUPP;
> + break;
> + }
> + return ret;
> +}
> +
>
> #endif /* BSD_USER_FREEBSD_OS_SOCKET_H */
>