/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 2015 Joyent, Inc. All rights reserved.
*/
#include <sys/sysmacros.h>
#include <sys/socketvar.h>
#include <inet/sctp_itf.h>
#include "socksctp.h"
/*
* SCTP sockfs sonode operations, 1-1 socket
*/
struct cred *);
int, int, struct cred *);
struct cred *);
struct cred *);
struct cred *);
int, struct cred *);
static int sosctp_setsockopt(struct sonode *, int, int, const void *,
int32_t *);
/*
* SCTP sockfs sonode operations, 1-N socket
*/
struct cred *);
/*
* Socket association upcalls, 1-N socket connection
*/
sock_upcalls_t **);
int *, boolean_t *);
static void sctp_assoc_properties(sock_upper_handle_t,
struct sock_proto_props *);
sosctp_init, /* sop_init */
sosctp_accept, /* sop_accept */
sosctp_bind, /* sop_bind */
sosctp_listen, /* sop_listen */
sosctp_connect, /* sop_connect */
sosctp_recvmsg, /* sop_recvmsg */
sosctp_sendmsg, /* sop_sendmsg */
so_sendmblk_notsupp, /* sop_sendmblk */
sosctp_getpeername, /* sop_getpeername */
sosctp_getsockname, /* sop_getsockname */
sosctp_shutdown, /* sop_shutdown */
sosctp_getsockopt, /* sop_getsockopt */
sosctp_setsockopt, /* sop_setsockopt */
sosctp_ioctl, /* sop_ioctl */
so_poll, /* sop_poll */
sosctp_close, /* sop_close */
};
sosctp_init, /* sop_init */
so_accept_notsupp, /* sop_accept */
sosctp_bind, /* sop_bind */
sosctp_listen, /* sop_listen */
sosctp_seq_connect, /* sop_connect */
sosctp_recvmsg, /* sop_recvmsg */
sosctp_seq_sendmsg, /* sop_sendmsg */
so_sendmblk_notsupp, /* sop_sendmblk */
so_getpeername_notsupp, /* sop_getpeername */
sosctp_getsockname, /* sop_getsockname */
so_shutdown_notsupp, /* sop_shutdown */
sosctp_getsockopt, /* sop_getsockopt */
sosctp_setsockopt, /* sop_setsockopt */
sosctp_ioctl, /* sop_ioctl */
so_poll, /* sop_poll */
sosctp_close, /* sop_close */
};
/* All the upcalls expect the upper handle to be sonode. */
NULL, /* su_signal_oob */
};
/* All the upcalls expect the upper handle to be sctp_sonode/sctp_soassoc. */
NULL, /* su_recv_space */
NULL, /* su_signal_oob */
};
/* ARGSUSED */
static int
{
int err;
/*
* Passive open, just inherit settings from parent. We should
* not end up here for SOCK_SEQPACKET type sockets, since no
* new sonode is created in that case.
*/
return (0);
}
return (err);
} else {
}
return (ENOMEM);
return (0);
}
/*
* Accept incoming connection.
*/
/*ARGSUSED*/
static int
{
int error = 0;
return (EINVAL);
return (error);
}
/*
* Bind local endpoint.
*/
/*ARGSUSED*/
static int
{
int error;
if (!(flags & _SOBIND_LOCK_HELD)) {
} else {
}
/*
*/
goto done;
}
/*
* Protocol module does address family checks.
*/
if (error == 0) {
} else {
}
done:
if (!(flags & _SOBIND_LOCK_HELD)) {
} else {
/* If the caller held the lock don't release it here */
}
return (error);
}
/*
* Turn socket into a listen socket.
*/
/* ARGSUSED */
static int
{
int error = 0;
/*
* If this socket is trying to do connect, or if it has
* been connected, disallow.
*/
goto done;
}
if (backlog < 0) {
backlog = 0;
}
/*
* If listen() is only called to change backlog, we don't
* need to notify protocol module.
*/
goto done;
}
if (error == 0) {
} else {
}
done:
return (error);
}
/*
* Active open.
*/
/*ARGSUSED*/
static int
{
int error = 0;
/*
* Can't connect() after listen(), or if the socket is already
* connected.
*/
} else {
error = EOPNOTSUPP;
}
goto done;
}
/*
* Check for failure of an earlier call
*/
goto done;
}
/*
* Connection is closing, or closed, don't allow reconnect.
* TCP allows this to proceed, but the socket remains unwriteable.
* BSD returns EINVAL.
*/
SS_CANTSENDMORE)) {
goto done;
}
goto done;
}
if (error == 0) {
/*
* Allow other threads to access the socket
*/
}
done:
return (error);
}
/*
* Active open for 1-N sockets, create a new association and
* call connect on that.
* If there parent hasn't been bound yet (this is the first association),
* make it so.
*/
static int
{
int error;
goto done;
}
if (error != 0) {
error = ENETUNREACH;
}
}
}
done:
return (error);
}
/*
* Receive data.
*/
/* ARGSUSED */
static int
{
void *opt;
msg->msg_controllen = 0;
msg->msg_namelen = 0;
SS_CANTRCVMORE))) {
return (ENOTCONN);
}
} else {
/* NOTE: Will come here from vop_read() as well */
/* For 1-N socket, recv() cannot be used. */
if (namelen == 0)
return (EOPNOTSUPP);
/*
* If there are no associations, and no new connections are
* coming in, there's not going to be new messages coming
* in either.
*/
return (ENOTCONN);
}
}
/*
* out-of-band data not supported.
*/
return (EOPNOTSUPP);
}
/*
* flag possibilities:
*
* MSG_PEEK Don't consume data
* MSG_WAITALL Wait for full quantity of data (ignored if MSG_PEEK)
* MSG_DONTWAIT Non-blocking (same as FNDELAY | FNONBLOCK)
*
* MSG_WAITALL can return less than the full buffer if either
*
* 1. we would block and we are non-blocking
* 2. a full message cannot be delivered
*
* Given that we always get a full message from proto below,
* MSG_WAITALL is not meaningful.
*/
/*
* Allow just one reader at a time.
*/
if (error) {
return (error);
}
}
}
if (controllen == 0) {
if (len > 0) {
}
} else if (len > 0) {
}
}
}
}
done:
/*
* Determine if we need to update SCTP about the buffer
* space. For performance reason, we cannot update SCTP
* every time a message is read. The socket buffer low
* watermark is used as the threshold.
*/
so->so_rcv_queued == 0);
/*
* so_dequeue_msg() sets r_val2 to true if flow control was
* cleared and we need to update SCTP. so_flowctrld was
* cleared in so_dequeue_msg() via so_check_flow_control().
*/
} else {
}
} else {
/*
* Each association keeps track of how much data it has
* queued; we need to update the value here. Note that this
* is slightly different from SOCK_STREAM type sockets, which
* does not need to update the byte count, as it is already
* done in so_dequeue_msg().
*/
/*
* Need to clear ssa_flowctrld, different from 1-1
* style.
*/
}
/*
* MOREDATA flag is set if all data could not be copied
*/
}
}
return (error);
}
int
{
int error;
/*
* Loop until we have all data copied into mblk's.
*/
while (count > 0) {
/*
* As a message can be splitted up and sent in different
* packets, each mblk will have the extra space before
* data to accommodate what SCTP wants to put in there.
*/
(flags & MSG_DONTWAIT)) {
return (EAGAIN);
}
return (error);
}
}
if (error != 0) {
return (error);
}
}
return (0);
}
/*
* Send message.
*/
static int
{
int error;
/*
* No out-of-band data support.
*/
return (EOPNOTSUPP);
}
if (msg->msg_controllen != 0) {
return (EINVAL);
}
/* Both flags should not be set together. */
return (EINVAL);
}
/* Initiate a graceful shutdown. */
/* Can't include data in MSG_EOF message. */
return (EINVAL);
}
/*
* This is the same sequence as done in
* shutdown(SHUT_WR).
*/
return (error);
}
}
} else {
optlen = 0;
}
for (;;) {
return (EPIPE);
}
return (error);
}
if (!so->so_snd_qfull)
break;
return (EINTR);
}
/*
* Xmit window full in a blocking socket.
*/
(flags & MSG_DONTWAIT)) {
return (EAGAIN);
} else {
/*
* Wait for space to become available and try again.
*/
if (!error) { /* signal */
return (EINTR);
}
}
}
/* Don't allow sending a message larger than the send buffer size. */
/* XXX Transport module need to enforce this */
return (EMSGSIZE);
}
/*
* Allow piggybacking data on handshake messages (SS_ISCONNECTING).
*/
/*
* We need to check here for listener so that the
* same error will be returned as with a TCP socket.
* In this case, sosctp_connect() returns EOPNOTSUPP
* while a TCP socket returns ENOTCONN instead. Catch it
* here to have the same behavior as a TCP socket.
*
* We also need to make sure that the peer address is
* provided before we attempt to do the connect.
*/
goto error_nofree;
}
if (flags & MSG_DONTWAIT) {
}
cr);
if (error) {
/*
* Check for non-fatal errors, socket connected
* while the lock had been lifted.
*/
goto error_nofree;
}
error = 0;
}
} else {
}
goto error_nofree;
}
/* Copy in the message. */
goto error_ret;
}
if (error == 0)
return (0);
/*
* We received shutdown between the time lock was
* lifted and call to sctp_sendmsg().
*/
return (EPIPE);
}
return (error);
}
/*
* Send message on 1-N socket. Connects automatically if there is
* no association.
*/
static int
{
int aid = 0;
int error;
/*
* There shouldn't be problems with alignment, as the memory for
* msg_control was alloced with kmem_alloc.
*/
return (EINVAL);
}
}
if (msg->msg_controllen > 0) {
} else {
optlen = 0;
}
/*
* If there is no association id, connect to address specified
* in msg_name. Otherwise look up the association using the id.
*/
if (aid == 0) {
/*
* Connect and shutdown cannot be done together, so check for
* MSG_EOF.
*/
goto done;
}
}
if (error) {
(error == EHOSTUNREACH)) {
error = ENETUNREACH;
}
/*
* Fatal error during connect(). Bail out.
* If ssa exists, it means that the handshake
* is in progress.
*/
goto done;
}
/*
* All the errors are non-fatal ones, don't return
* e.g. EINPROGRESS from sendmsg().
*/
error = 0;
}
} else {
goto done;
}
}
/*
* Now we have an association.
*/
/*
* MSG_EOF initiates graceful shutdown.
*/
/*
* Can't include data in MSG_EOF message.
*/
} else {
}
goto refrele;
}
for (;;) {
return (EPIPE);
}
goto refrele;
}
if (!ssa->ssa_snd_qfull)
break;
goto refrele;
}
(flags & MSG_DONTWAIT)) {
goto refrele;
} else {
/*
* Wait for space to become available and try again.
*/
if (!error) { /* signal */
goto refrele;
}
}
}
/* Don't allow sending a message larger than the send buffer size. */
goto refrele;
}
/*
* Update TX buffer usage here so that we can lift the socket lock.
*/
goto lock_rele;
}
/* Copy in the message. */
goto lock_rele;
}
if (error != 0) {
/*
* We received shutdown between the time lock was
* lifted and call to sctp_sendmsg().
*/
return (EPIPE);
}
}
done:
return (error);
}
/*
* Get address of remote node.
*/
/* ARGSUSED */
static int
{
addrlen));
}
/*
* Get local address.
*/
/* ARGSUSED */
static int
{
addrlen));
}
/*
* Called from shutdown().
*/
/* ARGSUSED */
static int
{
int wakesig = 0;
int error = 0;
/*
* Record the current state and then perform any state changes.
* Then use the difference between the old and new states to
* determine which needs to be done.
*/
switch (how) {
case SHUT_RD:
break;
case SHUT_WR:
break;
case SHUT_RDWR:
break;
default:
return (EINVAL);
}
if (state_change & SS_CANTRCVMORE) {
}
}
if (state_change & SS_CANTSENDMORE) {
}
if (state_change & SS_CANTSENDMORE) {
}
/*
* HACK: sctp_disconnect() may return EWOULDBLOCK. But this error is
* not documented in standard socket API. Catch it here.
*/
if (error == EWOULDBLOCK)
error = 0;
return (error);
}
/*
* Get socket options.
*/
/*ARGSUSED5*/
static int
{
int error = 0;
if (level == SOL_SOCKET) {
switch (option_name) {
/* Not supported options */
case SO_SNDTIMEO:
case SO_RCVTIMEO:
case SO_EXCLBIND:
return (ENOPROTOOPT);
default:
if (error >= 0)
return (error);
/* Pass the request to the protocol */
break;
}
}
if (level == IPPROTO_SCTP) {
/*
* Should go through ioctl().
*/
return (EINVAL);
}
}
/*
* If the resulting optlen is greater than the provided maxlen, then
* we sliently trucate.
*/
if (error != 0) {
goto free;
}
free:
}
return (error);
}
/*
* Set socket options
*/
/* ARGSUSED */
static int
{
/*
* For some SCTP level options, one can select the association this
* applies to.
*/
} else {
/*
* SOCK_SEQPACKET only
*/
id = 0;
if (level == IPPROTO_SCTP) {
switch (option_name) {
case SCTP_RTOINFO:
case SCTP_ASSOCINFO:
case SCTP_PRIMARY_ADDR:
case SCTP_PEER_ADDR_PARAMS:
/*
* Association ID is the first element
* params struct
*/
if (optlen < sizeof (sctp_assoc_t)) {
goto done;
}
break;
case SCTP_DEFAULT_SEND_PARAM:
if (optlen != sizeof (struct sctp_sndrcvinfo)) {
goto done;
}
id = ((struct sctp_sndrcvinfo *)
break;
case SCTP_INITMSG:
/*
* Only applies to future associations
*/
break;
default:
break;
}
} else if (level == SOL_SOCKET) {
if (option_name == SO_LINGER) {
error = EOPNOTSUPP;
goto done;
}
/*
* These 2 options are applied to all associations.
* The other socket level options are only applied
* to the socket (not associations).
*/
if ((option_name != SO_RCVBUF) &&
(option_name != SO_SNDBUF)) {
}
} else {
}
/*
* If association ID was specified, do op on that assoc.
* Otherwise set the default setting of a socket.
*/
if (id != 0) {
goto done;
}
}
}
} else {
/*
* 1-N socket, and we have to apply the operation to ALL
* associations. Like with anything of this sort, the
* problem is what to do if the operation fails.
* Just try to apply the setting to everyone, but store
* error number if someone returns such. And since we are
* looping through all possible aids, some of them can be
* invalid. We just ignore this kind (sosctp_assoc()) of
* errors.
*/
continue;
if (error == 0) {
}
}
}
done:
return (error);
}
/*ARGSUSED*/
static int
{
int error;
int intval;
void *conn;
void *buf;
int buflen;
/* handle socket specific ioctls */
switch (cmd) {
case FIONBIO:
return (EFAULT);
}
if (value) {
} else {
}
return (0);
case FIOASYNC:
return (EFAULT);
}
if (value) {
/* Turn on SIGIO */
} else {
/* Turn off SIGIO */
}
return (0);
case SIOCSPGRP:
case FIOSETOWN:
return (EFAULT);
}
return (error);
case SIOCGPGRP:
case FIOGETOWN:
return (EFAULT);
return (0);
case FIONREAD:
/* XXX: Cannot be used unless standard buffer is used */
/*
* Return number of bytes of data in all data messages
* in queue in "arg".
* For stream socket, amount of available data.
* For sock_dgram, # of available bytes + addresses.
*/
return (EFAULT);
return (0);
case SIOCATMARK:
/*
* No support for urgent data.
*/
intval = 0;
return (EFAULT);
return (0);
case _I_GETPEERCRED: {
int error = 0;
return (EINVAL);
} else {
}
return (error);
}
case SIOCSCTPGOPT:
return (EFAULT);
}
return (EINVAL);
/*
* Find the correct sctp_t based on whether it is 1-N socket
* or not.
*/
return (error);
}
} else {
}
/* Copyin the option buffer and then call sctp_get_opt(). */
/* Let's allocate a buffer enough to hold an int */
}
return (EFAULT);
}
/* The option level has to be IPPROTO_SCTP */
}
/* No error, copyout the result with the correct buf len. */
if (error == 0) {
}
}
return (error);
case SIOCSCTPSOPT:
return (EFAULT);
}
return (EINVAL);
/*
* Find the correct sctp_t based on whether it is 1-N socket
* or not.
*/
if (intval != 0) {
return (error);
}
} else {
}
/* Copyin the option buffer and then call sctp_set_opt(). */
}
return (EFAULT);
}
/* The option level has to be IPPROTO_SCTP */
if (ssa) {
}
return (error);
case SIOCSCTPPEELOFF: {
int nfd;
return (EOPNOTSUPP);
}
return (EFAULT);
}
if (intval == 0) {
return (EINVAL);
}
/*
* Find sockparams. This is different from parent's entry,
* as the socket type is different.
*/
&sp);
if (error != 0)
return (error);
/*
* Allocate the user fd.
*/
return (EMFILE);
}
/*
* Copy the fd out.
*/
goto err;
}
/*
* Don't use sosctp_assoc() in order to peel off disconnected
* associations.
*/
goto err;
}
goto err;
}
/* cannot fail, only inheriting properties */
/*
* We have a single ref on the new socket. This is normally
* handled by socket_{create,newconn}, but since they are not
* used we have to do it here.
*/
/*
* Upcalls to new socket are blocked for the duration of
* downcall.
*/
if (error) {
goto peelerr;
}
if (error) {
goto peelerr;
}
/*
* fill in the entries that falloc reserved
*/
return (0);
err:
return (error);
return (error);
}
default:
return (EINVAL);
}
}
/*ARGSUSED*/
static int
{
int32_t i;
/*
* Initiate connection shutdown. Tell SCTP if there is any data
* left unread.
*/
/*
* New associations can't come in, but old ones might get
* closed in upcall. Protect against that by taking a reference
* on the association.
*/
}
}
return (0);
}
/*
* Closes incoming connections which were never accepted, frees
* resources.
*/
/* ARGSUSED */
void
{
int32_t i;
/* We are the sole owner of so now */
/* Free all pending connections */
}
}
}
if (so->so_proto_handle)
/*
* Note until sctp_close() is called, SCTP can still send up
* messages, such as event notifications. So we should flush
* the recevie buffer after calling sctp_close().
*/
}
/*
* Upcalls from SCTP
*/
/*
* This is the upcall function for 1-N (SOCK_SEQPACKET) socket when a new
* association is created. Note that the first argument (handle) is of type
* sctp_sonode *, which is the one changed to a listener for new
* associations. All the other upcalls for 1-N socket take sctp_soassoc *
* as handle. The only exception is the su_properties upcall, which
* can take both types as handle.
*/
/* ARGSUSED */
{
/*
* Array not large enough; increase size.
*/
return (NULL);
}
}
/*
* Create soassoc for this connection
*/
return (NULL);
}
++lss->ss_assoccnt;
*ucp = &sosctp_assoc_upcalls;
return ((sock_upper_handle_t)ssa);
}
/* ARGSUSED */
static void
{
}
/* ARGSUSED */
static int
{
int ret;
ret = 1;
} else {
ret = 0;
}
return (ret);
}
/* ARGSUSED */
static void
{
}
/* ARGSUSED */
static ssize_t
{
*errorp = 0;
/*
* Should be getting T_unitdata_req's only.
* Must have address as part of packet.
*/
/*
* For notify messages, need to fill in association id.
* For data messages, sndrcvinfo could be in ancillary data.
*/
case SCTP_ASSOC_CHANGE:
break;
case SCTP_PEER_ADDR_CHANGE:
break;
case SCTP_REMOTE_ERROR:
break;
case SCTP_SEND_FAILED:
break;
case SCTP_SHUTDOWN_EVENT:
break;
break;
break;
default:
ASSERT(0);
break;
}
} else {
if (tind->OPT_length > 0) {
char *cend;
for (;;) {
break;
}
sinfo = (struct sctp_sndrcvinfo *)
(cmsg + 1);
break;
}
} else {
break;
}
}
}
}
/*
* SCTP has reserved space in the header for storing a pointer.
* Put the pointer to assocation there, and queue the data.
*/
if (space_available <= 0)
/* so_notify_data drops so_lock */
return (space_available);
}
static void
{
/*
* Wake blocked writers.
*/
}
static void
struct sock_proto_props *soppp)
{
/* Per assoc_id properties. */
} else {
/*
* The low water mark should be adjusted properly
* if the high water mark is changed. It should
* not be bigger than 1/4 of high water mark.
*/
/* Sanity check... */
if (lowat == 0)
else
}
}
}
}