#include <sys/cdefs.h>
#include "opt_kern_tls.h"
#include "opt_param.h"
#include <sys/param.h>
#include <sys/aio.h>
#include <sys/kernel.h>
#include <sys/ktls.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/msan.h>
#include <sys/mutex.h>
#include <sys/proc.h>
#include <sys/protosw.h>
#include <sys/resourcevar.h>
#include <sys/signalvar.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/sx.h>
#include <sys/sysctl.h>
#include <netinet/in.h>
void (*aio_swake)(struct socket *, struct sockbuf *);
#define BUF_MAX_ADJ(_sz) (((u_quad_t)(_sz)) * MCLBYTES / (MSIZE + MCLBYTES))
u_long sb_max = SB_MAX;
u_long sb_max_adj = BUF_MAX_ADJ(SB_MAX);
static u_long sb_efficiency = 8;
#ifdef KERN_TLS
static void sbcompress_ktls_rx(struct sockbuf *sb, struct mbuf *m,
struct mbuf *n);
#endif
static struct mbuf *sbcut_internal(struct sockbuf *sb, int len);
static void sbunreserve_locked(struct socket *so, sb_which which);
static void
sbm_clrprotoflags(struct mbuf *m, int flags)
{
int mask;
mask = ~M_PROTOFLAGS;
if (flags & PRUS_NOTREADY)
mask |= M_NOTREADY;
while (m) {
m->m_flags &= mask;
m = m->m_next;
}
}
static void
sbready_compress(struct sockbuf *sb, struct mbuf *m0, struct mbuf *end)
{
struct mbuf *m, *n;
int ext_size;
SOCKBUF_LOCK_ASSERT(sb);
if ((sb->sb_flags & SB_NOCOALESCE) != 0)
return;
for (m = m0; m != end; m = m->m_next) {
MPASS((m->m_flags & M_NOTREADY) == 0);
n = m->m_next;
#ifdef KERN_TLS
if ((n != NULL) && (n != end) && (m->m_flags & M_EOR) == 0 &&
(m->m_flags & M_EXTPG) &&
(n->m_flags & M_EXTPG) &&
!mbuf_has_tls_session(m) &&
!mbuf_has_tls_session(n)) {
int hdr_len, trail_len;
hdr_len = n->m_epg_hdrlen;
trail_len = m->m_epg_trllen;
if (trail_len != 0 && hdr_len != 0 &&
trail_len + hdr_len <= MBUF_PEXT_TRAIL_LEN) {
memcpy(&m->m_epg_trail[trail_len],
n->m_epg_hdr, hdr_len);
m->m_epg_trllen += hdr_len;
m->m_len += hdr_len;
n->m_epg_hdrlen = 0;
n->m_len -= hdr_len;
}
}
#endif
if ((m->m_flags & M_EXTPG) && m->m_len <= MLEN &&
!mbuf_has_tls_session(m)) {
ext_size = m->m_ext.ext_size;
if (mb_unmapped_compress(m) == 0)
sb->sb_mbcnt -= ext_size;
}
while ((n != NULL) && (n != end) && (m->m_flags & M_EOR) == 0 &&
M_WRITABLE(m) &&
(m->m_flags & M_EXTPG) == 0 &&
!mbuf_has_tls_session(n) &&
!mbuf_has_tls_session(m) &&
n->m_len <= MCLBYTES / 4 &&
n->m_len <= M_TRAILINGSPACE(m) &&
m->m_type == n->m_type) {
KASSERT(sb->sb_lastrecord != n,
("%s: merging start of record (%p) into previous mbuf (%p)",
__func__, n, m));
m_copydata(n, 0, n->m_len, mtodo(m, m->m_len));
m->m_len += n->m_len;
m->m_next = n->m_next;
m->m_flags |= n->m_flags & M_EOR;
if (sb->sb_mbtail == n)
sb->sb_mbtail = m;
sb->sb_mbcnt -= MSIZE;
if (n->m_flags & M_EXT)
sb->sb_mbcnt -= n->m_ext.ext_size;
m_free(n);
n = m->m_next;
}
}
SBLASTRECORDCHK(sb);
SBLASTMBUFCHK(sb);
}
int
sbready(struct sockbuf *sb, struct mbuf *m0, int count)
{
struct mbuf *m;
bool blocker;
SOCKBUF_LOCK_ASSERT(sb);
KASSERT(sb->sb_fnrdy != NULL, ("%s: sb %p NULL fnrdy", __func__, sb));
KASSERT(count > 0, ("%s: invalid count %d", __func__, count));
m = m0;
blocker = (sb->sb_fnrdy == m);
while (count > 0) {
KASSERT(m->m_flags & M_NOTREADY,
("%s: m %p !M_NOTREADY", __func__, m));
if ((m->m_flags & M_EXTPG) != 0 && m->m_epg_npgs != 0) {
if (count < m->m_epg_nrdy) {
m->m_epg_nrdy -= count;
count = 0;
break;
}
count -= m->m_epg_nrdy;
m->m_epg_nrdy = 0;
} else
count--;
m->m_flags &= ~M_NOTREADY;
if (blocker)
sb->sb_acc += m->m_len;
m = m->m_next;
}
if (m0 == m) {
MPASS(m->m_flags & M_NOTREADY);
return (EINPROGRESS);
}
if (!blocker) {
sbready_compress(sb, m0, m);
return (EINPROGRESS);
}
for (; m && (m->m_flags & M_NOTREADY) == 0; m = m->m_next)
sb->sb_acc += m->m_len;
sb->sb_fnrdy = m;
sbready_compress(sb, m0, m);
return (0);
}
void
sballoc(struct sockbuf *sb, struct mbuf *m)
{
SOCKBUF_LOCK_ASSERT(sb);
sb->sb_ccc += m->m_len;
if (sb->sb_fnrdy == NULL) {
if (m->m_flags & M_NOTREADY)
sb->sb_fnrdy = m;
else
sb->sb_acc += m->m_len;
}
if (m->m_type != MT_DATA && m->m_type != MT_OOBDATA)
sb->sb_ctl += m->m_len;
sb->sb_mbcnt += MSIZE;
if (m->m_flags & M_EXT)
sb->sb_mbcnt += m->m_ext.ext_size;
}
void
sbfree(struct sockbuf *sb, struct mbuf *m)
{
struct mbuf *n;
#if 0
SOCKBUF_LOCK_ASSERT(sb);
#endif
sb->sb_ccc -= m->m_len;
if (m == sb->sb_fnrdy) {
KASSERT(m->m_flags & M_NOTREADY,
("%s: m %p !M_NOTREADY", __func__, m));
n = m->m_next;
while (n != NULL && !(n->m_flags & M_NOTREADY)) {
sb->sb_acc += n->m_len;
n = n->m_next;
}
sb->sb_fnrdy = n;
} else {
for (n = sb->sb_fnrdy; n != NULL; n = n->m_next)
KASSERT(n != m, ("%s: sb %p freeing %p behind sb_fnrdy",
__func__, sb, m));
sb->sb_acc -= m->m_len;
}
if (m->m_type != MT_DATA && m->m_type != MT_OOBDATA)
sb->sb_ctl -= m->m_len;
sb->sb_mbcnt -= MSIZE;
if (m->m_flags & M_EXT)
sb->sb_mbcnt -= m->m_ext.ext_size;
if (sb->sb_sndptr == m) {
sb->sb_sndptr = NULL;
sb->sb_sndptroff = 0;
}
if (sb->sb_sndptroff != 0)
sb->sb_sndptroff -= m->m_len;
}
#ifdef KERN_TLS
void
sballoc_ktls_rx(struct sockbuf *sb, struct mbuf *m)
{
SOCKBUF_LOCK_ASSERT(sb);
sb->sb_ccc += m->m_len;
sb->sb_tlscc += m->m_len;
sb->sb_mbcnt += MSIZE;
if (m->m_flags & M_EXT)
sb->sb_mbcnt += m->m_ext.ext_size;
}
void
sbfree_ktls_rx(struct sockbuf *sb, struct mbuf *m)
{
#if 0
SOCKBUF_LOCK_ASSERT(sb);
#endif
sb->sb_ccc -= m->m_len;
sb->sb_tlscc -= m->m_len;
sb->sb_mbcnt -= MSIZE;
if (m->m_flags & M_EXT)
sb->sb_mbcnt -= m->m_ext.ext_size;
}
#endif
void
socantsendmore_locked(struct socket *so)
{
SOCK_SENDBUF_LOCK_ASSERT(so);
so->so_snd.sb_state |= SBS_CANTSENDMORE;
sowwakeup_locked(so);
SOCK_SENDBUF_UNLOCK_ASSERT(so);
}
void
socantsendmore(struct socket *so)
{
SOCK_SENDBUF_LOCK(so);
socantsendmore_locked(so);
SOCK_SENDBUF_UNLOCK_ASSERT(so);
}
void
socantrcvmore_locked(struct socket *so)
{
SOCK_RECVBUF_LOCK_ASSERT(so);
so->so_rcv.sb_state |= SBS_CANTRCVMORE;
#ifdef KERN_TLS
if (so->so_rcv.sb_flags & SB_TLS_RX)
ktls_check_rx(&so->so_rcv);
#endif
sorwakeup_locked(so);
SOCK_RECVBUF_UNLOCK_ASSERT(so);
}
void
socantrcvmore(struct socket *so)
{
SOCK_RECVBUF_LOCK(so);
socantrcvmore_locked(so);
SOCK_RECVBUF_UNLOCK_ASSERT(so);
}
void
soroverflow_locked(struct socket *so)
{
SOCK_RECVBUF_LOCK_ASSERT(so);
if (so->so_options & SO_RERROR) {
so->so_rerror = ENOBUFS;
sorwakeup_locked(so);
} else
SOCK_RECVBUF_UNLOCK(so);
SOCK_RECVBUF_UNLOCK_ASSERT(so);
}
void
soroverflow(struct socket *so)
{
SOCK_RECVBUF_LOCK(so);
soroverflow_locked(so);
SOCK_RECVBUF_UNLOCK_ASSERT(so);
}
int
sbwait(struct socket *so, sb_which which)
{
struct sockbuf *sb;
SOCK_BUF_LOCK_ASSERT(so, which);
sb = sobuf(so, which);
sb->sb_flags |= SB_WAIT;
return (msleep_sbt(&sb->sb_acc, soeventmtx(so, which),
PSOCK | PCATCH, "sbwait", sb->sb_timeo, 0, 0));
}
static __always_inline void
sowakeup(struct socket *so, const sb_which which)
{
struct sockbuf *sb;
int ret;
SOCK_BUF_LOCK_ASSERT(so, which);
sb = sobuf(so, which);
selwakeuppri(sb->sb_sel, PSOCK);
if (!SEL_WAITING(sb->sb_sel))
sb->sb_flags &= ~SB_SEL;
if (sb->sb_flags & SB_WAIT) {
sb->sb_flags &= ~SB_WAIT;
wakeup(&sb->sb_acc);
}
KNOTE_LOCKED(&sb->sb_sel->si_note, 0);
if (sb->sb_upcall != NULL) {
ret = sb->sb_upcall(so, sb->sb_upcallarg, M_NOWAIT);
if (ret == SU_ISCONNECTED) {
KASSERT(sb == &so->so_rcv,
("SO_SND upcall returned SU_ISCONNECTED"));
soupcall_clear(so, SO_RCV);
}
} else
ret = SU_OK;
if (sb->sb_flags & SB_AIO)
sowakeup_aio(so, which);
SOCK_BUF_UNLOCK(so, which);
if (ret == SU_ISCONNECTED)
soisconnected(so);
if ((so->so_state & SS_ASYNC) && so->so_sigio != NULL)
pgsigio(&so->so_sigio, SIGIO, 0);
SOCK_BUF_UNLOCK_ASSERT(so, which);
}
static void
splice_push(struct socket *so)
{
struct so_splice *sp;
SOCK_RECVBUF_LOCK_ASSERT(so);
sp = so->so_splice;
mtx_lock(&sp->mtx);
SOCK_RECVBUF_UNLOCK(so);
so_splice_dispatch(sp);
}
static void
splice_pull(struct socket *so)
{
struct so_splice *sp;
SOCK_SENDBUF_LOCK_ASSERT(so);
sp = so->so_splice_back;
mtx_lock(&sp->mtx);
SOCK_SENDBUF_UNLOCK(so);
so_splice_dispatch(sp);
}
static __always_inline bool
sb_notify(const struct sockbuf *sb)
{
return ((sb->sb_flags & (SB_WAIT | SB_SEL | SB_ASYNC |
SB_UPCALL | SB_AIO | SB_KNOTE)) != 0);
}
void
sorwakeup_locked(struct socket *so)
{
SOCK_RECVBUF_LOCK_ASSERT(so);
if (so->so_rcv.sb_flags & SB_SPLICED)
splice_push(so);
else if (sb_notify(&so->so_rcv))
sowakeup(so, SO_RCV);
else
SOCK_RECVBUF_UNLOCK(so);
}
void
sowwakeup_locked(struct socket *so)
{
SOCK_SENDBUF_LOCK_ASSERT(so);
if (so->so_snd.sb_flags & SB_SPLICED)
splice_pull(so);
else if (sb_notify(&so->so_snd))
sowakeup(so, SO_SND);
else
SOCK_SENDBUF_UNLOCK(so);
}
int
soreserve(struct socket *so, u_long sndcc, u_long rcvcc)
{
struct thread *td = curthread;
SOCK_SENDBUF_LOCK(so);
SOCK_RECVBUF_LOCK(so);
if (sbreserve_locked(so, SO_SND, sndcc, td) == 0)
goto bad;
if (sbreserve_locked(so, SO_RCV, rcvcc, td) == 0)
goto bad2;
if (so->so_rcv.sb_lowat == 0)
so->so_rcv.sb_lowat = 1;
if (so->so_snd.sb_lowat == 0)
so->so_snd.sb_lowat = MCLBYTES;
if (so->so_snd.sb_lowat > so->so_snd.sb_hiwat)
so->so_snd.sb_lowat = so->so_snd.sb_hiwat;
SOCK_RECVBUF_UNLOCK(so);
SOCK_SENDBUF_UNLOCK(so);
return (0);
bad2:
sbunreserve_locked(so, SO_SND);
bad:
SOCK_RECVBUF_UNLOCK(so);
SOCK_SENDBUF_UNLOCK(so);
return (ENOBUFS);
}
static int
sysctl_handle_sb_max(SYSCTL_HANDLER_ARGS)
{
int error = 0;
u_long tmp_sb_max = sb_max;
error = sysctl_handle_long(oidp, &tmp_sb_max, arg2, req);
if (error || !req->newptr)
return (error);
if (tmp_sb_max < MSIZE + MCLBYTES)
return (EINVAL);
sb_max = tmp_sb_max;
sb_max_adj = BUF_MAX_ADJ(sb_max);
return (0);
}
bool
sbreserve_locked_limit(struct socket *so, sb_which which, u_long cc,
u_long buf_max, struct thread *td)
{
struct sockbuf *sb = sobuf(so, which);
rlim_t sbsize_limit;
SOCK_BUF_LOCK_ASSERT(so, which);
if (cc > BUF_MAX_ADJ(buf_max))
return (false);
if (td != NULL) {
sbsize_limit = lim_cur(td, RLIMIT_SBSIZE);
} else
sbsize_limit = RLIM_INFINITY;
if (!chgsbsize(so->so_cred->cr_uidinfo, &sb->sb_hiwat, cc,
sbsize_limit))
return (false);
sb->sb_mbmax = min(cc * sb_efficiency, buf_max);
if (sb->sb_lowat > sb->sb_hiwat)
sb->sb_lowat = sb->sb_hiwat;
return (true);
}
bool
sbreserve_locked(struct socket *so, sb_which which, u_long cc,
struct thread *td)
{
return (sbreserve_locked_limit(so, which, cc, sb_max, td));
}
static void
sbunreserve_locked(struct socket *so, sb_which which)
{
struct sockbuf *sb = sobuf(so, which);
SOCK_BUF_LOCK_ASSERT(so, which);
(void)chgsbsize(so->so_cred->cr_uidinfo, &sb->sb_hiwat, 0,
RLIM_INFINITY);
sb->sb_mbmax = 0;
}
int
sbsetopt(struct socket *so, struct sockopt *sopt)
{
struct sockbuf *sb;
sb_which wh;
short *flags;
u_int cc, *hiwat, *lowat;
int error, optval;
error = sooptcopyin(sopt, &optval, sizeof optval, sizeof optval);
if (error != 0)
return (error);
if (optval < 1)
return (EINVAL);
cc = optval;
sb = NULL;
SOCK_LOCK(so);
if (SOLISTENING(so)) {
switch (sopt->sopt_name) {
case SO_SNDLOWAT:
case SO_SNDBUF:
lowat = &so->sol_sbsnd_lowat;
hiwat = &so->sol_sbsnd_hiwat;
flags = &so->sol_sbsnd_flags;
break;
case SO_RCVLOWAT:
case SO_RCVBUF:
lowat = &so->sol_sbrcv_lowat;
hiwat = &so->sol_sbrcv_hiwat;
flags = &so->sol_sbrcv_flags;
break;
}
} else {
switch (sopt->sopt_name) {
case SO_SNDLOWAT:
case SO_SNDBUF:
sb = &so->so_snd;
wh = SO_SND;
break;
case SO_RCVLOWAT:
case SO_RCVBUF:
sb = &so->so_rcv;
wh = SO_RCV;
break;
}
flags = &sb->sb_flags;
hiwat = &sb->sb_hiwat;
lowat = &sb->sb_lowat;
SOCK_BUF_LOCK(so, wh);
}
error = 0;
switch (sopt->sopt_name) {
case SO_SNDBUF:
case SO_RCVBUF:
if (SOLISTENING(so)) {
if (cc > sb_max_adj) {
error = ENOBUFS;
break;
}
*hiwat = cc;
if (*lowat > *hiwat)
*lowat = *hiwat;
} else {
if (!sbreserve_locked(so, wh, cc, curthread))
error = ENOBUFS;
}
if (error == 0)
*flags &= ~SB_AUTOSIZE;
break;
case SO_SNDLOWAT:
case SO_RCVLOWAT:
*lowat = (cc > *hiwat) ? *hiwat : cc;
*flags &= ~SB_AUTOLOWAT;
break;
}
if (!SOLISTENING(so))
SOCK_BUF_UNLOCK(so, wh);
SOCK_UNLOCK(so);
return (error);
}
void
sbrelease_locked(struct socket *so, sb_which which)
{
struct sockbuf *sb = sobuf(so, which);
SOCK_BUF_LOCK_ASSERT(so, which);
sbflush_locked(sb);
sbunreserve_locked(so, which);
}
void
sbrelease(struct socket *so, sb_which which)
{
SOCK_BUF_LOCK(so, which);
sbrelease_locked(so, which);
SOCK_BUF_UNLOCK(so, which);
}
void
sbdestroy(struct socket *so, sb_which which)
{
#ifdef KERN_TLS
struct sockbuf *sb = sobuf(so, which);
if (sb->sb_tls_info != NULL)
ktls_free(sb->sb_tls_info);
sb->sb_tls_info = NULL;
#endif
sbrelease_locked(so, which);
}
#ifdef SOCKBUF_DEBUG
void
sblastrecordchk(struct sockbuf *sb, const char *file, int line)
{
struct mbuf *m = sb->sb_mb;
SOCKBUF_LOCK_ASSERT(sb);
while (m && m->m_nextpkt)
m = m->m_nextpkt;
if (m != sb->sb_lastrecord) {
printf("%s: sb_mb %p sb_lastrecord %p last %p\n",
__func__, sb->sb_mb, sb->sb_lastrecord, m);
printf("packet chain:\n");
for (m = sb->sb_mb; m != NULL; m = m->m_nextpkt)
printf("\t%p\n", m);
panic("%s from %s:%u", __func__, file, line);
}
}
void
sblastmbufchk(struct sockbuf *sb, const char *file, int line)
{
struct mbuf *m = sb->sb_mb;
struct mbuf *n;
SOCKBUF_LOCK_ASSERT(sb);
while (m && m->m_nextpkt)
m = m->m_nextpkt;
while (m && m->m_next)
m = m->m_next;
if (m != sb->sb_mbtail) {
printf("%s: sb_mb %p sb_mbtail %p last %p\n",
__func__, sb->sb_mb, sb->sb_mbtail, m);
printf("packet tree:\n");
for (m = sb->sb_mb; m != NULL; m = m->m_nextpkt) {
printf("\t");
for (n = m; n != NULL; n = n->m_next)
printf("%p ", n);
printf("\n");
}
panic("%s from %s:%u", __func__, file, line);
}
#ifdef KERN_TLS
m = sb->sb_mtls;
while (m && m->m_next)
m = m->m_next;
if (m != sb->sb_mtlstail) {
printf("%s: sb_mtls %p sb_mtlstail %p last %p\n",
__func__, sb->sb_mtls, sb->sb_mtlstail, m);
printf("TLS packet tree:\n");
printf("\t");
for (m = sb->sb_mtls; m != NULL; m = m->m_next) {
printf("%p ", m);
}
printf("\n");
panic("%s from %s:%u", __func__, file, line);
}
#endif
}
#endif
#define SBLINKRECORD(sb, m0) do { \
SOCKBUF_LOCK_ASSERT(sb); \
if ((sb)->sb_lastrecord != NULL) \
(sb)->sb_lastrecord->m_nextpkt = (m0); \
else \
(sb)->sb_mb = (m0); \
(sb)->sb_lastrecord = (m0); \
} while (0)
void
sbappend_locked(struct sockbuf *sb, struct mbuf *m, int flags)
{
struct mbuf *n;
SOCKBUF_LOCK_ASSERT(sb);
if (m == NULL)
return;
kmsan_check_mbuf(m, "sbappend");
sbm_clrprotoflags(m, flags);
SBLASTRECORDCHK(sb);
n = sb->sb_mb;
if (n) {
while (n->m_nextpkt)
n = n->m_nextpkt;
do {
if (n->m_flags & M_EOR) {
sbappendrecord_locked(sb, m);
return;
}
} while (n->m_next && (n = n->m_next));
} else {
if ((n = sb->sb_lastrecord) != NULL) {
do {
if (n->m_flags & M_EOR) {
sbappendrecord_locked(sb, m);
return;
}
} while (n->m_next && (n = n->m_next));
} else {
sb->sb_lastrecord = m;
}
}
sbcompress(sb, m, n);
SBLASTRECORDCHK(sb);
}
void
sbappend(struct sockbuf *sb, struct mbuf *m, int flags)
{
SOCKBUF_LOCK(sb);
sbappend_locked(sb, m, flags);
SOCKBUF_UNLOCK(sb);
}
#ifdef KERN_TLS
static void
sbappend_ktls_rx(struct sockbuf *sb, struct mbuf *m)
{
struct ifnet *ifp;
struct mbuf *n;
int flags;
ifp = NULL;
flags = M_NOTREADY;
SBLASTMBUFCHK(sb);
MPASS((m->m_flags & M_PKTHDR) != 0);
for (n = m; n != NULL; n = n->m_next) {
if (n->m_flags & M_PKTHDR) {
ifp = m->m_pkthdr.leaf_rcvif;
if ((n->m_pkthdr.csum_flags & CSUM_TLS_MASK) ==
CSUM_TLS_DECRYPTED) {
flags = M_NOTREADY | M_DECRYPTED;
} else {
flags = M_NOTREADY;
}
m_demote_pkthdr(n);
}
n->m_flags &= M_DEMOTEFLAGS;
n->m_flags |= flags;
MPASS((n->m_flags & M_NOTREADY) != 0);
}
sbcompress_ktls_rx(sb, m, sb->sb_mtlstail);
ktls_check_rx(sb);
if (ifp != NULL && sb->sb_tls_info->rx_ifp != NULL &&
sb->sb_tls_info->rx_ifp != ifp)
ktls_input_ifp_mismatch(sb, ifp);
}
#endif
void
sbappendstream_locked(struct sockbuf *sb, struct mbuf *m, int flags)
{
SOCKBUF_LOCK_ASSERT(sb);
KASSERT(m->m_nextpkt == NULL,("sbappendstream 0"));
kmsan_check_mbuf(m, "sbappend");
#ifdef KERN_TLS
if (sb->sb_flags & SB_TLS_RX) {
sbappend_ktls_rx(sb, m);
return;
}
#endif
KASSERT(sb->sb_mb == sb->sb_lastrecord,("sbappendstream 1"));
SBLASTMBUFCHK(sb);
#ifdef KERN_TLS
if (sb->sb_tls_info != NULL)
ktls_seq(sb, m);
#endif
m_demote(m, 1, flags & PRUS_NOTREADY ? M_NOTREADY : 0);
sbcompress(sb, m, sb->sb_mbtail);
sb->sb_lastrecord = sb->sb_mb;
SBLASTRECORDCHK(sb);
}
void
sbappendstream(struct sockbuf *sb, struct mbuf *m, int flags)
{
SOCKBUF_LOCK(sb);
sbappendstream_locked(sb, m, flags);
SOCKBUF_UNLOCK(sb);
}
#ifdef SOCKBUF_DEBUG
void
sbcheck(struct sockbuf *sb, const char *file, int line)
{
struct mbuf *m, *n, *fnrdy;
u_long acc, ccc, mbcnt;
#ifdef KERN_TLS
u_long tlscc;
#endif
SOCKBUF_LOCK_ASSERT(sb);
acc = ccc = mbcnt = 0;
fnrdy = NULL;
for (m = sb->sb_mb; m; m = n) {
n = m->m_nextpkt;
for (; m; m = m->m_next) {
if (m->m_len == 0) {
printf("sb %p empty mbuf %p\n", sb, m);
goto fail;
}
if ((m->m_flags & M_NOTREADY) && fnrdy == NULL) {
if (m != sb->sb_fnrdy) {
printf("sb %p: fnrdy %p != m %p\n",
sb, sb->sb_fnrdy, m);
goto fail;
}
fnrdy = m;
}
if (fnrdy == NULL)
acc += m->m_len;
ccc += m->m_len;
mbcnt += MSIZE;
if (m->m_flags & M_EXT)
mbcnt += m->m_ext.ext_size;
}
}
#ifdef KERN_TLS
ccc += sb->sb_tlsdcc;
tlscc = 0;
for (m = sb->sb_mtls; m; m = m->m_next) {
if (m->m_nextpkt != NULL) {
printf("sb %p TLS mbuf %p with nextpkt\n", sb, m);
goto fail;
}
if ((m->m_flags & M_NOTREADY) == 0) {
printf("sb %p TLS mbuf %p ready\n", sb, m);
goto fail;
}
tlscc += m->m_len;
ccc += m->m_len;
mbcnt += MSIZE;
if (m->m_flags & M_EXT)
mbcnt += m->m_ext.ext_size;
}
if (sb->sb_tlscc != tlscc) {
printf("tlscc %ld/%u dcc %u\n", tlscc, sb->sb_tlscc,
sb->sb_tlsdcc);
goto fail;
}
#endif
if (acc != sb->sb_acc || ccc != sb->sb_ccc || mbcnt != sb->sb_mbcnt) {
printf("acc %ld/%u ccc %ld/%u mbcnt %ld/%u\n",
acc, sb->sb_acc, ccc, sb->sb_ccc, mbcnt, sb->sb_mbcnt);
#ifdef KERN_TLS
printf("tlscc %ld/%u dcc %u\n", tlscc, sb->sb_tlscc,
sb->sb_tlsdcc);
#endif
goto fail;
}
return;
fail:
panic("%s from %s:%u", __func__, file, line);
}
#endif
void
sbappendrecord_locked(struct sockbuf *sb, struct mbuf *m0)
{
struct mbuf *m;
SOCKBUF_LOCK_ASSERT(sb);
if (m0 == NULL)
return;
kmsan_check_mbuf(m0, "sbappend");
m_clrprotoflags(m0);
sballoc(sb, m0);
SBLASTRECORDCHK(sb);
SBLINKRECORD(sb, m0);
sb->sb_mbtail = m0;
m = m0->m_next;
m0->m_next = 0;
if (m && (m0->m_flags & M_EOR)) {
m0->m_flags &= ~M_EOR;
m->m_flags |= M_EOR;
}
sbcompress(sb, m, m0);
}
void
sbappendrecord(struct sockbuf *sb, struct mbuf *m0)
{
SOCKBUF_LOCK(sb);
sbappendrecord_locked(sb, m0);
SOCKBUF_UNLOCK(sb);
}
static int
sbappendaddr_locked_internal(struct sockbuf *sb, const struct sockaddr *asa,
struct mbuf *m0, struct mbuf *control, struct mbuf *ctrl_last)
{
struct mbuf *m, *n, *nlast;
if (m0 != NULL)
kmsan_check_mbuf(m0, "sbappend");
if (control != NULL)
kmsan_check_mbuf(control, "sbappend");
#if MSIZE <= 256
if (asa->sa_len > MLEN)
return (0);
#endif
m = m_get(M_NOWAIT, MT_SONAME);
if (m == NULL)
return (0);
m->m_len = asa->sa_len;
bcopy(asa, mtod(m, caddr_t), asa->sa_len);
if (m0) {
M_ASSERT_NO_SND_TAG(m0);
m_clrprotoflags(m0);
m_tag_delete_chain(m0, NULL);
m0->m_pkthdr.rcvif = NULL;
m0->m_pkthdr.flowid = 0;
m0->m_pkthdr.csum_flags = 0;
m0->m_pkthdr.fibnum = 0;
m0->m_pkthdr.rsstype = 0;
}
if (ctrl_last)
ctrl_last->m_next = m0;
else
control = m0;
m->m_next = control;
for (n = m; n->m_next != NULL; n = n->m_next)
sballoc(sb, n);
sballoc(sb, n);
nlast = n;
SBLINKRECORD(sb, m);
sb->sb_mbtail = nlast;
SBLASTMBUFCHK(sb);
SBLASTRECORDCHK(sb);
return (1);
}
int
sbappendaddr_locked(struct sockbuf *sb, const struct sockaddr *asa,
struct mbuf *m0, struct mbuf *control)
{
struct mbuf *ctrl_last;
int space = asa->sa_len;
SOCKBUF_LOCK_ASSERT(sb);
if (m0 && (m0->m_flags & M_PKTHDR) == 0)
panic("sbappendaddr_locked");
if (m0)
space += m0->m_pkthdr.len;
space += m_length(control, &ctrl_last);
if (space > sbspace(sb))
return (0);
return (sbappendaddr_locked_internal(sb, asa, m0, control, ctrl_last));
}
int
sbappendaddr_nospacecheck_locked(struct sockbuf *sb, const struct sockaddr *asa,
struct mbuf *m0, struct mbuf *control)
{
struct mbuf *ctrl_last;
SOCKBUF_LOCK_ASSERT(sb);
ctrl_last = (control == NULL) ? NULL : m_last(control);
return (sbappendaddr_locked_internal(sb, asa, m0, control, ctrl_last));
}
int
sbappendaddr(struct sockbuf *sb, const struct sockaddr *asa,
struct mbuf *m0, struct mbuf *control)
{
int retval;
SOCKBUF_LOCK(sb);
retval = sbappendaddr_locked(sb, asa, m0, control);
SOCKBUF_UNLOCK(sb);
return (retval);
}
void
sbappendcontrol_locked(struct sockbuf *sb, struct mbuf *m0,
struct mbuf *control, int flags)
{
struct mbuf *m, *mlast;
if (m0 != NULL)
kmsan_check_mbuf(m0, "sbappend");
kmsan_check_mbuf(control, "sbappend");
sbm_clrprotoflags(m0, flags);
m_last(control)->m_next = m0;
SBLASTRECORDCHK(sb);
for (m = control; m->m_next; m = m->m_next)
sballoc(sb, m);
sballoc(sb, m);
mlast = m;
SBLINKRECORD(sb, control);
sb->sb_mbtail = mlast;
SBLASTMBUFCHK(sb);
SBLASTRECORDCHK(sb);
}
void
sbappendcontrol(struct sockbuf *sb, struct mbuf *m0, struct mbuf *control,
int flags)
{
SOCKBUF_LOCK(sb);
sbappendcontrol_locked(sb, m0, control, flags);
SOCKBUF_UNLOCK(sb);
}
void
sbcompress(struct sockbuf *sb, struct mbuf *m, struct mbuf *n)
{
int eor = 0;
struct mbuf *o;
SOCKBUF_LOCK_ASSERT(sb);
while (m) {
eor |= m->m_flags & M_EOR;
if (m->m_len == 0 &&
(eor == 0 ||
(((o = m->m_next) || (o = n)) &&
o->m_type == m->m_type))) {
if (sb->sb_lastrecord == m)
sb->sb_lastrecord = m->m_next;
m = m_free(m);
continue;
}
if (n && (n->m_flags & M_EOR) == 0 &&
M_WRITABLE(n) &&
((sb->sb_flags & SB_NOCOALESCE) == 0) &&
!(m->m_flags & M_NOTREADY) &&
!(n->m_flags & (M_NOTREADY | M_EXTPG)) &&
!mbuf_has_tls_session(m) &&
!mbuf_has_tls_session(n) &&
m->m_len <= MCLBYTES / 4 &&
m->m_len <= M_TRAILINGSPACE(n) &&
n->m_type == m->m_type) {
m_copydata(m, 0, m->m_len, mtodo(n, n->m_len));
n->m_len += m->m_len;
sb->sb_ccc += m->m_len;
if (sb->sb_fnrdy == NULL)
sb->sb_acc += m->m_len;
if (m->m_type != MT_DATA && m->m_type != MT_OOBDATA)
sb->sb_ctl += m->m_len;
m = m_free(m);
continue;
}
if (m->m_len <= MLEN && (m->m_flags & M_EXTPG) &&
(m->m_flags & M_NOTREADY) == 0 &&
!mbuf_has_tls_session(m))
(void)mb_unmapped_compress(m);
if (n)
n->m_next = m;
else
sb->sb_mb = m;
sb->sb_mbtail = m;
sballoc(sb, m);
n = m;
m->m_flags &= ~M_EOR;
m = m->m_next;
n->m_next = 0;
}
if (eor) {
KASSERT(n != NULL, ("sbcompress: eor && n == NULL"));
n->m_flags |= eor;
}
SBLASTMBUFCHK(sb);
}
#ifdef KERN_TLS
static void
sbcompress_ktls_rx(struct sockbuf *sb, struct mbuf *m, struct mbuf *n)
{
SOCKBUF_LOCK_ASSERT(sb);
while (m) {
KASSERT((m->m_flags & M_EOR) == 0,
("TLS RX mbuf %p with EOR", m));
KASSERT(m->m_type == MT_DATA,
("TLS RX mbuf %p is not MT_DATA", m));
KASSERT((m->m_flags & M_NOTREADY) != 0,
("TLS RX mbuf %p ready", m));
KASSERT((m->m_flags & M_EXTPG) == 0,
("TLS RX mbuf %p unmapped", m));
if (m->m_len == 0) {
m = m_free(m);
continue;
}
if (n &&
M_WRITABLE(n) &&
((sb->sb_flags & SB_NOCOALESCE) == 0) &&
!((m->m_flags ^ n->m_flags) & M_DECRYPTED) &&
!(n->m_flags & M_EXTPG) &&
m->m_len <= MCLBYTES / 4 &&
m->m_len <= M_TRAILINGSPACE(n)) {
m_copydata(m, 0, m->m_len, mtodo(n, n->m_len));
n->m_len += m->m_len;
sb->sb_ccc += m->m_len;
sb->sb_tlscc += m->m_len;
m = m_free(m);
continue;
}
if (n)
n->m_next = m;
else
sb->sb_mtls = m;
sb->sb_mtlstail = m;
sballoc_ktls_rx(sb, m);
n = m;
m = m->m_next;
n->m_next = NULL;
}
SBLASTMBUFCHK(sb);
}
#endif
void
sbflush_locked(struct sockbuf *sb)
{
SOCKBUF_LOCK_ASSERT(sb);
while (sb->sb_mbcnt || sb->sb_tlsdcc) {
if (sb->sb_ccc == 0 && (sb->sb_mb == NULL || sb->sb_mb->m_len))
break;
m_freem(sbcut_internal(sb, (int)sb->sb_ccc));
}
KASSERT(sb->sb_ccc == 0 && sb->sb_mb == 0 && sb->sb_mbcnt == 0,
("%s: ccc %u mb %p mbcnt %u", __func__,
sb->sb_ccc, (void *)sb->sb_mb, sb->sb_mbcnt));
}
void
sbflush(struct sockbuf *sb)
{
SOCKBUF_LOCK(sb);
sbflush_locked(sb);
SOCKBUF_UNLOCK(sb);
}
static struct mbuf *
sbcut_internal(struct sockbuf *sb, int len)
{
struct mbuf *m, *next, *mfree;
bool is_tls;
KASSERT(len >= 0, ("%s: len is %d but it is supposed to be >= 0",
__func__, len));
KASSERT(len <= sb->sb_ccc, ("%s: len: %d is > ccc: %u",
__func__, len, sb->sb_ccc));
next = (m = sb->sb_mb) ? m->m_nextpkt : 0;
is_tls = false;
mfree = NULL;
while (len > 0) {
if (m == NULL) {
#ifdef KERN_TLS
if (next == NULL && !is_tls) {
if (sb->sb_tlsdcc != 0) {
MPASS(len >= sb->sb_tlsdcc);
len -= sb->sb_tlsdcc;
sb->sb_ccc -= sb->sb_tlsdcc;
sb->sb_tlsdcc = 0;
if (len == 0)
break;
}
next = sb->sb_mtls;
is_tls = true;
}
#endif
KASSERT(next, ("%s: no next, len %d", __func__, len));
m = next;
next = m->m_nextpkt;
}
if (m->m_len > len) {
KASSERT(!(m->m_flags & M_NOTREADY),
("%s: m %p M_NOTREADY", __func__, m));
m->m_len -= len;
m->m_data += len;
sb->sb_ccc -= len;
sb->sb_acc -= len;
if (sb->sb_sndptroff != 0)
sb->sb_sndptroff -= len;
if (m->m_type != MT_DATA && m->m_type != MT_OOBDATA)
sb->sb_ctl -= len;
break;
}
len -= m->m_len;
#ifdef KERN_TLS
if (is_tls)
sbfree_ktls_rx(sb, m);
else
#endif
sbfree(sb, m);
if (m->m_flags & M_NOTREADY && !is_tls)
m = m->m_next;
else {
struct mbuf *n;
n = m->m_next;
m->m_next = mfree;
mfree = m;
m = n;
}
}
while (m && m->m_len == 0) {
struct mbuf *n;
sbfree(sb, m);
n = m->m_next;
m->m_next = mfree;
mfree = m;
m = n;
}
#ifdef KERN_TLS
if (is_tls) {
sb->sb_mb = NULL;
sb->sb_mtls = m;
if (m == NULL)
sb->sb_mtlstail = NULL;
} else
#endif
if (m) {
sb->sb_mb = m;
m->m_nextpkt = next;
} else
sb->sb_mb = next;
m = sb->sb_mb;
if (m == NULL) {
sb->sb_mbtail = NULL;
sb->sb_lastrecord = NULL;
} else if (m->m_nextpkt == NULL) {
sb->sb_lastrecord = m;
}
return (mfree);
}
void
sbdrop_locked(struct sockbuf *sb, int len)
{
SOCKBUF_LOCK_ASSERT(sb);
m_freem(sbcut_internal(sb, len));
}
struct mbuf *
sbcut_locked(struct sockbuf *sb, int len)
{
SOCKBUF_LOCK_ASSERT(sb);
return (sbcut_internal(sb, len));
}
void
sbdrop(struct sockbuf *sb, int len)
{
struct mbuf *mfree;
SOCKBUF_LOCK(sb);
mfree = sbcut_internal(sb, len);
SOCKBUF_UNLOCK(sb);
m_freem(mfree);
}
struct mbuf *
sbsndptr_noadv(struct sockbuf *sb, uint32_t off, uint32_t *moff)
{
struct mbuf *m;
KASSERT(sb->sb_mb != NULL, ("%s: sb_mb is NULL", __func__));
if (sb->sb_sndptr == NULL || sb->sb_sndptroff > off) {
*moff = off;
if (sb->sb_sndptr == NULL) {
sb->sb_sndptr = sb->sb_mb;
sb->sb_sndptroff = 0;
}
return (sb->sb_mb);
} else {
m = sb->sb_sndptr;
off -= sb->sb_sndptroff;
}
*moff = off;
return (m);
}
void
sbsndptr_adv(struct sockbuf *sb, struct mbuf *mb, uint32_t len)
{
struct mbuf *m;
if (mb != sb->sb_sndptr) {
return;
}
m = mb;
while (m && (len > 0)) {
if (len >= m->m_len) {
len -= m->m_len;
if (m->m_next) {
sb->sb_sndptroff += m->m_len;
sb->sb_sndptr = m->m_next;
}
m = m->m_next;
} else {
len = 0;
}
}
}
struct mbuf *
sbsndmbuf(struct sockbuf *sb, u_int off, u_int *moff)
{
struct mbuf *m;
KASSERT(sb->sb_mb != NULL, ("%s: sb_mb is NULL", __func__));
if (sb->sb_sndptr == NULL || sb->sb_sndptroff > off) {
m = sb->sb_mb;
} else {
m = sb->sb_sndptr;
off -= sb->sb_sndptroff;
}
while (off > 0 && m != NULL) {
if (off < m->m_len)
break;
off -= m->m_len;
m = m->m_next;
}
*moff = off;
return (m);
}
void
sbdroprecord_locked(struct sockbuf *sb)
{
struct mbuf *m;
SOCKBUF_LOCK_ASSERT(sb);
m = sb->sb_mb;
if (m) {
sb->sb_mb = m->m_nextpkt;
do {
sbfree(sb, m);
m = m_free(m);
} while (m);
}
SB_EMPTY_FIXUP(sb);
}
void
sbdroprecord(struct sockbuf *sb)
{
SOCKBUF_LOCK(sb);
sbdroprecord_locked(sb);
SOCKBUF_UNLOCK(sb);
}
struct mbuf *
sbcreatecontrol(const void *p, u_int size, int type, int level, int wait)
{
struct cmsghdr *cp;
struct mbuf *m;
MBUF_CHECKSLEEP(wait);
if (wait == M_NOWAIT) {
if (CMSG_SPACE(size) > MCLBYTES)
return (NULL);
} else
KASSERT(CMSG_SPACE(size) <= MCLBYTES,
("%s: passed CMSG_SPACE(%u) > MCLBYTES", __func__, size));
if (CMSG_SPACE(size) > MLEN)
m = m_getcl(wait, MT_CONTROL, 0);
else
m = m_get(wait, MT_CONTROL);
if (m == NULL)
return (NULL);
KASSERT(CMSG_SPACE(size) <= M_TRAILINGSPACE(m),
("sbcreatecontrol: short mbuf"));
cp = mtod(m, struct cmsghdr *);
bzero(cp, CMSG_SPACE(size));
if (p != NULL)
(void)memcpy(CMSG_DATA(cp), p, size);
m->m_len = CMSG_SPACE(size);
cp->cmsg_len = CMSG_LEN(size);
cp->cmsg_level = level;
cp->cmsg_type = type;
return (m);
}
void
sbtoxsockbuf(struct sockbuf *sb, struct xsockbuf *xsb)
{
xsb->sb_cc = sb->sb_ccc;
xsb->sb_hiwat = sb->sb_hiwat;
xsb->sb_mbcnt = sb->sb_mbcnt;
xsb->sb_mbmax = sb->sb_mbmax;
xsb->sb_lowat = sb->sb_lowat;
xsb->sb_flags = sb->sb_flags;
xsb->sb_timeo = sb->sb_timeo;
}
static int dummy;
SYSCTL_INT(_kern, KERN_DUMMY, dummy, CTLFLAG_RW | CTLFLAG_SKIP, &dummy, 0, "");
SYSCTL_OID(_kern_ipc, KIPC_MAXSOCKBUF, maxsockbuf,
CTLTYPE_ULONG | CTLFLAG_RW | CTLFLAG_MPSAFE, &sb_max, 0,
sysctl_handle_sb_max, "LU",
"Maximum socket buffer size");
SYSCTL_ULONG(_kern_ipc, KIPC_SOCKBUF_WASTE, sockbuf_waste_factor, CTLFLAG_RW,
&sb_efficiency, 0, "Socket buffer size waste factor");