#include "opt_param.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/domain.h>
#include <sys/file.h>
#include <sys/kernel.h>
#include <sys/ktr.h>
#include <sys/proc.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/protosw.h>
#include <sys/resourcevar.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/socketops.h>
#include <sys/signalvar.h>
#include <sys/sysctl.h>
#include <sys/event.h>
#include <sys/msgport2.h>
#include <sys/socketvar2.h>
#include <net/netisr2.h>
#ifndef KTR_SOWAKEUP
#define KTR_SOWAKEUP KTR_ALL
#endif
KTR_INFO_MASTER(sowakeup);
KTR_INFO(KTR_SOWAKEUP, sowakeup, nconn_start, 0, "newconn sorwakeup start");
KTR_INFO(KTR_SOWAKEUP, sowakeup, nconn_end, 1, "newconn sorwakeup end");
KTR_INFO(KTR_SOWAKEUP, sowakeup, nconn_wakeupstart, 2, "newconn wakeup start");
KTR_INFO(KTR_SOWAKEUP, sowakeup, nconn_wakeupend, 3, "newconn wakeup end");
#define logsowakeup(name) KTR_LOG(sowakeup_ ## name)
int maxsockets;
u_long sb_max = SB_MAX;
u_long sb_max_adj =
SB_MAX * MCLBYTES / (MSIZE + MCLBYTES);
static u_long sb_efficiency = 8;
SYSCTL_NODE(_kern, KERN_IPC, ipc, CTLFLAG_RW, 0, "IPC");
static int soacceptreuse = 1;
SYSCTL_INT(_kern_ipc, OID_AUTO, soaccept_reuse, CTLFLAG_RW,
&soacceptreuse, 0, "Allow quick reuse of local port");
int
ssb_wait(struct signalsockbuf *ssb)
{
uint32_t flags;
int pflags;
int error;
pflags = (ssb->ssb_flags & SSB_NOINTR) ? 0 : PCATCH;
for (;;) {
flags = ssb->ssb_flags;
cpu_ccfence();
if (flags & SSB_WAKEUP) {
if (atomic_cmpset_int(&ssb->ssb_flags, flags,
flags & ~SSB_WAKEUP)) {
error = 0;
break;
}
continue;
}
tsleep_interlock(&ssb->ssb_cc, pflags);
if (atomic_cmpset_int(&ssb->ssb_flags, flags,
flags | SSB_WAIT)) {
error = tsleep(&ssb->ssb_cc, pflags | PINTERLOCKED,
"sbwait", ssb->ssb_timeo);
break;
}
}
return (error);
}
int
_ssb_lock(struct signalsockbuf *ssb)
{
uint32_t flags;
int pflags;
int error;
pflags = (ssb->ssb_flags & SSB_NOINTR) ? 0 : PCATCH;
for (;;) {
flags = ssb->ssb_flags;
cpu_ccfence();
if (flags & SSB_LOCK) {
tsleep_interlock(&ssb->ssb_flags, pflags);
if (atomic_cmpset_int(&ssb->ssb_flags, flags,
flags | SSB_WANT)) {
error = tsleep(&ssb->ssb_flags,
pflags | PINTERLOCKED,
"sblock", 0);
if (error)
break;
}
} else {
if (atomic_cmpset_int(&ssb->ssb_flags, flags,
flags | SSB_LOCK)) {
lwkt_gettoken(&ssb->ssb_token);
error = 0;
break;
}
}
}
return (error);
}
void
ssbtoxsockbuf(struct signalsockbuf *ssb, struct xsockbuf *xsb)
{
xsb->sb_cc = ssb->ssb_cc;
xsb->sb_hiwat = ssb->ssb_hiwat;
xsb->sb_mbcnt = ssb->ssb_mbcnt;
xsb->sb_mbmax = ssb->ssb_mbmax;
xsb->sb_lowat = ssb->ssb_lowat;
xsb->sb_flags = ssb->ssb_flags;
xsb->sb_timeo = ssb->ssb_timeo;
}
void
soisconnecting(struct socket *so)
{
soclrstate(so, SS_ISCONNECTED | SS_ISDISCONNECTING);
sosetstate(so, SS_ISCONNECTING);
}
void
soisconnected(struct socket *so)
{
struct socket *head;
while ((head = so->so_head) != NULL) {
lwkt_getpooltoken(head);
if (so->so_head == head)
break;
lwkt_relpooltoken(head);
}
soclrstate(so, SS_ISCONNECTING | SS_ISDISCONNECTING | SS_ISCONFIRMING);
sosetstate(so, SS_ISCONNECTED);
if (head && (so->so_state & SS_INCOMP)) {
if ((so->so_options & SO_ACCEPTFILTER) != 0) {
so->so_upcall = head->so_accf->so_accept_filter->accf_callback;
so->so_upcallarg = head->so_accf->so_accept_filter_arg;
atomic_set_int(&so->so_rcv.ssb_flags, SSB_UPCALL);
so->so_options &= ~SO_ACCEPTFILTER;
so->so_upcall(so, so->so_upcallarg, 0);
lwkt_relpooltoken(head);
return;
}
KKASSERT((so->so_state & (SS_COMP | SS_INCOMP)) == SS_INCOMP);
TAILQ_REMOVE(&head->so_incomp, so, so_list);
head->so_incqlen--;
TAILQ_INSERT_TAIL(&head->so_comp, so, so_list);
head->so_qlen++;
sosetstate(so, SS_COMP);
soclrstate(so, SS_INCOMP);
sorwakeup(head);
wakeup_one(&head->so_timeo);
} else {
wakeup(&so->so_timeo);
sorwakeup(so);
sowwakeup(so);
}
if (head)
lwkt_relpooltoken(head);
}
void
soisdisconnecting(struct socket *so)
{
soclrstate(so, SS_ISCONNECTING);
sosetstate(so, SS_ISDISCONNECTING | SS_CANTRCVMORE | SS_CANTSENDMORE);
wakeup((caddr_t)&so->so_timeo);
sowwakeup(so);
sorwakeup(so);
}
void
soisdisconnected(struct socket *so)
{
soclrstate(so, SS_ISCONNECTING | SS_ISCONNECTED | SS_ISDISCONNECTING);
sosetstate(so, SS_CANTRCVMORE | SS_CANTSENDMORE | SS_ISDISCONNECTED);
wakeup((caddr_t)&so->so_timeo);
sbdrop(&so->so_snd.sb, so->so_snd.ssb_cc);
sowwakeup(so);
sorwakeup(so);
}
void
soisreconnecting(struct socket *so)
{
soclrstate(so, SS_ISDISCONNECTING | SS_ISDISCONNECTED |
SS_CANTRCVMORE | SS_CANTSENDMORE);
sosetstate(so, SS_ISCONNECTING);
}
void
soisreconnected(struct socket *so)
{
soclrstate(so, SS_ISDISCONNECTED | SS_CANTRCVMORE | SS_CANTSENDMORE);
soisconnected(so);
}
void
sosetport(struct socket *so, lwkt_port_t port)
{
so->so_port = port;
}
struct socket *
sonewconn_faddr(struct socket *head, int connstatus,
const struct sockaddr *faddr, boolean_t keep_ref)
{
struct socket *so;
struct socket *sp;
struct pru_attach_info ai;
if (head->so_qlen > 3 * head->so_qlimit / 2)
return (NULL);
so = soalloc(1, head->so_proto);
if (so == NULL)
return (NULL);
if (head->so_proto->pr_flags & PR_SYNC_PORT)
sosetport(so, &netisr_sync_port);
else
sosetport(so, netisr_cpuport(mycpuid));
if ((head->so_options & SO_ACCEPTFILTER) != 0)
connstatus = 0;
so->so_head = head;
so->so_type = head->so_type;
so->so_options = head->so_options &~ SO_ACCEPTCONN;
so->so_linger = head->so_linger;
so->so_state = head->so_state | SS_NOFDREF | SS_ASSERTINPROG;
so->so_cred = crhold(head->so_cred);
ai.sb_rlimit = NULL;
ai.p_ucred = NULL;
ai.fd_rdir = NULL;
if (soreserve(so, head->so_snd.ssb_hiwat,
head->so_rcv.ssb_hiwat, NULL) ||
so_pru_attach_direct(so, 0, &ai)) {
so->so_head = NULL;
soclrstate(so, SS_ASSERTINPROG);
sofree(so);
return (NULL);
}
KKASSERT(((so->so_proto->pr_flags & PR_ASYNC_RCVD) == 0 &&
so->so_refs == 2) ||
((so->so_proto->pr_flags & PR_ASYNC_RCVD) &&
so->so_refs == 3));
if (keep_ref) {
} else {
sofree(so);
}
KKASSERT(so->so_port != NULL);
so->so_rcv.ssb_lowat = head->so_rcv.ssb_lowat;
so->so_snd.ssb_lowat = head->so_snd.ssb_lowat;
so->so_rcv.ssb_timeo = head->so_rcv.ssb_timeo;
so->so_snd.ssb_timeo = head->so_snd.ssb_timeo;
if (head->so_rcv.ssb_flags & SSB_AUTOLOWAT)
so->so_rcv.ssb_flags |= SSB_AUTOLOWAT;
else
so->so_rcv.ssb_flags &= ~SSB_AUTOLOWAT;
if (head->so_snd.ssb_flags & SSB_AUTOLOWAT)
so->so_snd.ssb_flags |= SSB_AUTOLOWAT;
else
so->so_snd.ssb_flags &= ~SSB_AUTOLOWAT;
if (head->so_rcv.ssb_flags & SSB_AUTOSIZE)
so->so_rcv.ssb_flags |= SSB_AUTOSIZE;
else
so->so_rcv.ssb_flags &= ~SSB_AUTOSIZE;
if (head->so_snd.ssb_flags & SSB_AUTOSIZE)
so->so_snd.ssb_flags |= SSB_AUTOSIZE;
else
so->so_snd.ssb_flags &= ~SSB_AUTOSIZE;
if (faddr != NULL && so->so_proto->pr_usrreqs->pru_savefaddr != NULL)
so->so_proto->pr_usrreqs->pru_savefaddr(so, faddr);
lwkt_getpooltoken(head);
if (connstatus) {
KKASSERT((so->so_state & (SS_INCOMP | SS_COMP)) == 0);
TAILQ_INSERT_TAIL(&head->so_comp, so, so_list);
head->so_qlen++;
if (soacceptreuse)
connstatus |= SS_ACCEPTMECH;
sosetstate(so, SS_COMP | connstatus);
} else {
if (head->so_incqlen > head->so_qlimit) {
sp = TAILQ_FIRST(&head->so_incomp);
KKASSERT((sp->so_state & (SS_INCOMP | SS_COMP)) ==
SS_INCOMP);
TAILQ_REMOVE(&head->so_incomp, sp, so_list);
head->so_incqlen--;
soclrstate(sp, SS_INCOMP);
soabort_async(sp, TRUE);
}
KKASSERT((so->so_state & (SS_INCOMP | SS_COMP)) == 0);
TAILQ_INSERT_TAIL(&head->so_incomp, so, so_list);
head->so_incqlen++;
sosetstate(so, SS_INCOMP | SS_ACCEPTMECH);
}
soclrstate(so, SS_ASSERTINPROG);
lwkt_relpooltoken(head);
if (connstatus) {
logsowakeup(nconn_start);
sorwakeup(head);
logsowakeup(nconn_end);
logsowakeup(nconn_wakeupstart);
wakeup((caddr_t)&head->so_timeo);
logsowakeup(nconn_wakeupend);
}
return (so);
}
struct socket *
sonewconn(struct socket *head, int connstatus)
{
return sonewconn_faddr(head, connstatus, NULL, FALSE );
}
void
socantsendmore(struct socket *so)
{
sosetstate(so, SS_CANTSENDMORE);
sowwakeup(so);
}
void
socantrcvmore(struct socket *so)
{
sosetstate(so, SS_CANTRCVMORE);
sorwakeup(so);
}
void
soroverflow(struct socket *so)
{
if (so->so_options & SO_RERROR) {
so->so_rerror = ENOBUFS;
sorwakeup(so);
}
}
void
sowakeup(struct socket *so, struct signalsockbuf *ssb)
{
uint32_t flags;
flags = atomic_fetchadd_int(&ssb->ssb_flags, 0);
if ((flags & SSB_NOTIFY_MASK) == 0) {
if (flags & SSB_WAKEUP)
return;
}
for (;;) {
long space;
flags = ssb->ssb_flags;
cpu_ccfence();
if (ssb->ssb_flags & SSB_PREALLOC)
space = ssb_space_prealloc(ssb);
else
space = ssb_space(ssb);
if ((ssb == &so->so_snd && space >= ssb->ssb_lowat) ||
(ssb == &so->so_rcv && ssb->ssb_cc >= ssb->ssb_lowat) ||
(ssb == &so->so_snd && (so->so_state & SS_CANTSENDMORE)) ||
(ssb == &so->so_rcv && (so->so_state & SS_CANTRCVMORE))
) {
if (atomic_cmpset_int(&ssb->ssb_flags, flags,
(flags | SSB_WAKEUP) & ~SSB_WAIT)) {
if (flags & SSB_WAIT)
wakeup(&ssb->ssb_cc);
break;
}
} else {
break;
}
}
if ((so->so_state & SS_ASYNC) && so->so_sigio != NULL)
pgsigio(so->so_sigio, SIGIO, 0);
if (ssb->ssb_flags & SSB_UPCALL)
(*so->so_upcall)(so, so->so_upcallarg, M_NOWAIT);
KNOTE(&ssb->ssb_kq.ki_note, 0);
if (ssb->ssb_flags & SSB_MEVENT) {
struct netmsg_so_notify *msg, *nmsg;
lwkt_getpooltoken(so);
TAILQ_FOREACH_MUTABLE(msg, &ssb->ssb_mlist, nm_list, nmsg) {
if (msg->nm_predicate(msg)) {
TAILQ_REMOVE(&ssb->ssb_mlist, msg, nm_list);
lwkt_replymsg(&msg->base.lmsg,
msg->base.lmsg.ms_error);
}
}
if (TAILQ_EMPTY(&ssb->ssb_mlist))
atomic_clear_int(&ssb->ssb_flags, SSB_MEVENT);
lwkt_relpooltoken(so);
}
}
int
soreserve(struct socket *so, u_long sndcc, u_long rcvcc, struct rlimit *rl)
{
if (so->so_snd.ssb_lowat == 0)
atomic_set_int(&so->so_snd.ssb_flags, SSB_AUTOLOWAT);
if (ssb_reserve(&so->so_snd, sndcc, so, rl) == 0)
goto bad;
if (ssb_reserve(&so->so_rcv, rcvcc, so, rl) == 0)
goto bad2;
if (so->so_rcv.ssb_lowat == 0)
so->so_rcv.ssb_lowat = 1;
if (so->so_snd.ssb_lowat == 0)
so->so_snd.ssb_lowat = MCLBYTES;
if (so->so_snd.ssb_lowat > so->so_snd.ssb_hiwat)
so->so_snd.ssb_lowat = so->so_snd.ssb_hiwat;
return (0);
bad2:
ssb_release(&so->so_snd, so);
bad:
return (ENOBUFS);
}
static int
sysctl_handle_sb_max(SYSCTL_HANDLER_ARGS)
{
int error = 0;
u_long old_sb_max = sb_max;
error = SYSCTL_OUT(req, arg1, sizeof(int));
if (error || !req->newptr)
return (error);
error = SYSCTL_IN(req, arg1, sizeof(int));
if (error)
return (error);
if (sb_max < MSIZE + MCLBYTES) {
sb_max = old_sb_max;
return (EINVAL);
}
sb_max_adj = (u_quad_t)sb_max * MCLBYTES / (MSIZE + MCLBYTES);
return (0);
}
int
ssb_reserve(struct signalsockbuf *ssb, u_long cc, struct socket *so,
struct rlimit *rl)
{
if (rl && cc > sb_max_adj)
cc = sb_max_adj;
if (!chgsbsize(so->so_cred->cr_uidinfo, &ssb->ssb_hiwat, cc,
rl ? rl->rlim_cur : RLIM_INFINITY)) {
return (0);
}
if (rl)
ssb->ssb_mbmax = min(cc * sb_efficiency, sb_max);
else
ssb->ssb_mbmax = cc * sb_efficiency;
if (ssb->ssb_flags & SSB_AUTOLOWAT) {
ssb->ssb_lowat = ssb->ssb_hiwat / 4;
if (ssb->ssb_lowat < MCLBYTES)
ssb->ssb_lowat = MCLBYTES;
}
if (ssb->ssb_lowat > ssb->ssb_hiwat)
ssb->ssb_lowat = ssb->ssb_hiwat;
return (1);
}
void
ssb_release(struct signalsockbuf *ssb, struct socket *so)
{
sbflush(&ssb->sb);
(void)chgsbsize(so->so_cred->cr_uidinfo, &ssb->ssb_hiwat, 0,
RLIM_INFINITY);
ssb->ssb_mbmax = 0;
}
void
pr_generic_notsupp(netmsg_t msg)
{
lwkt_replymsg(&msg->lmsg, EOPNOTSUPP);
}
int
pru_sosend_notsupp(struct socket *so, struct sockaddr *addr, struct uio *uio,
struct mbuf *top, struct mbuf *control, int flags,
struct thread *td)
{
if (top)
m_freem(top);
if (control)
m_freem(control);
return (EOPNOTSUPP);
}
int
pru_soreceive_notsupp(struct socket *so, struct sockaddr **paddr,
struct uio *uio, struct sockbuf *sio,
struct mbuf **controlp, int *flagsp)
{
return (EOPNOTSUPP);
}
void
pru_sense_null(netmsg_t msg)
{
msg->sense.nm_stat->st_blksize = msg->base.nm_so->so_snd.ssb_hiwat;
lwkt_replymsg(&msg->lmsg, 0);
}
struct sockaddr *
dup_sockaddr(const struct sockaddr *sa)
{
struct sockaddr *sa2;
sa2 = kmalloc(sa->sa_len, M_SONAME, M_INTWAIT);
bcopy(sa, sa2, sa->sa_len);
return (sa2);
}
void
sotoxsocket(struct socket *so, struct xsocket *xso)
{
xso->xso_len = sizeof *xso;
xso->xso_so = so;
xso->so_type = so->so_type;
xso->so_options = so->so_options;
xso->so_linger = so->so_linger;
xso->so_state = so->so_state;
xso->so_pcb = so->so_pcb;
xso->xso_protocol = so->so_proto->pr_protocol;
xso->xso_family = so->so_proto->pr_domain->dom_family;
xso->so_qlen = so->so_qlen;
xso->so_incqlen = so->so_incqlen;
xso->so_qlimit = so->so_qlimit;
xso->so_timeo = so->so_timeo;
xso->so_error = so->so_error;
xso->so_pgid = so->so_sigio ? so->so_sigio->sio_pgid : 0;
xso->so_oobmark = so->so_oobmark;
ssbtoxsockbuf(&so->so_snd, &xso->so_snd);
ssbtoxsockbuf(&so->so_rcv, &xso->so_rcv);
xso->so_uid = so->so_cred->cr_uid;
}
static int dummy;
SYSCTL_INT(_kern, KERN_DUMMY, dummy, CTLFLAG_RW, &dummy, 0, "");
SYSCTL_OID(_kern_ipc, KIPC_MAXSOCKBUF, maxsockbuf, CTLTYPE_INT|CTLFLAG_RW,
&sb_max, 0, sysctl_handle_sb_max, "I", "Maximum socket buffer size");
SYSCTL_INT(_kern_ipc, OID_AUTO, maxsockets, CTLFLAG_RD,
&maxsockets, 0, "Maximum number of sockets available");
SYSCTL_INT(_kern_ipc, KIPC_SOCKBUF_WASTE, sockbuf_waste_factor, CTLFLAG_RW,
&sb_efficiency, 0,
"Socket buffer limit scaler");
static void
init_maxsockets(void *ignored)
{
TUNABLE_INT_FETCH("kern.ipc.maxsockets", &maxsockets);
maxsockets = imax(maxsockets, imax(maxfiles, nmbclusters));
}
SYSINIT(param, SI_BOOT1_TUNABLES, SI_ORDER_ANY,
init_maxsockets, NULL);