First pass at porting John's "accept" changes to

allow an in-kernel webserver (or similar) to accept
and handle incoming connections using netgraph without ever leaving the
kernel. (allows incoming tunnel requests to be
handled totally within the kernel for example)

Needs work, but shouldn't break existing functionality.

Submitted by:	John Polstra <jdp@polstra.com>
MFC after:	2 weeks
This commit is contained in:
Julian Elischer 2001-09-07 07:12:51 +00:00
parent a8637146f1
commit f97e0a0719
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=83186
2 changed files with 385 additions and 51 deletions

View File

@ -78,17 +78,31 @@ MALLOC_DEFINE(M_NETGRAPH_KSOCKET, "netgraph_ksock", "netgraph ksock node ");
/* Node private data */
struct ng_ksocket_private {
node_p node;
hook_p hook;
struct socket *so;
LIST_HEAD(, ng_ksocket_private) embryos;
LIST_ENTRY(ng_ksocket_private) siblings;
u_int32_t flags;
u_int32_t response_token;
ng_ID_t response_addr;
};
typedef struct ng_ksocket_private *priv_p;
/* Flags for priv_p */
#define KSF_CONNECTING 0x00000001 /* Waiting for connection complete */
#define KSF_ACCEPTING 0x00000002 /* Waiting for accept complete */
#define KSF_EOFSEEN 0x00000004 /* Have sent 0-length EOF mbuf */
#define KSF_CLONED 0x00000008 /* Cloned from an accepting socket */
#define KSF_EMBRYONIC 0x00000010 /* Cloned node with no hooks yet */
/* Netgraph node methods */
static ng_constructor_t ng_ksocket_constructor;
static ng_rcvmsg_t ng_ksocket_rcvmsg;
static ng_shutdown_t ng_ksocket_shutdown;
static ng_newhook_t ng_ksocket_newhook;
static ng_rcvdata_t ng_ksocket_rcvdata;
static ng_connect_t ng_ksocket_connect;
static ng_disconnect_t ng_ksocket_disconnect;
/* Alias structure */
@ -139,9 +153,13 @@ static const struct ng_ksocket_alias ng_ksocket_protos[] = {
};
/* Helper functions */
static int ng_ksocket_check_accept(priv_p);
static void ng_ksocket_finish_accept(priv_p);
static void ng_ksocket_incoming(struct socket *so, void *arg, int waitflag);
static int ng_ksocket_parse(const struct ng_ksocket_alias *aliases,
const char *s, int family);
static void ng_ksocket_incoming2(node_p node, hook_p hook,
void *arg1, int waitflag);
/************************************************************************
STRUCT SOCKADDR PARSE TYPE
@ -402,6 +420,14 @@ static const struct ng_parse_type ng_ksocket_sockopt_type = {
&ng_ksocket_sockopt_type_info,
};
/* Parse type for struct ng_ksocket_accept */
static const struct ng_parse_struct_info ng_ksocket_accept_type_info
= NGM_KSOCKET_ACCEPT_INFO;
static const struct ng_parse_type ng_ksocket_accept_type = {
&ng_parse_struct_type,
&ng_ksocket_accept_type_info
};
/* List of commands and how to convert arguments to/from ASCII */
static const struct ng_cmdlist ng_ksocket_cmds[] = {
{
@ -423,14 +449,14 @@ static const struct ng_cmdlist ng_ksocket_cmds[] = {
NGM_KSOCKET_ACCEPT,
"accept",
NULL,
&ng_ksocket_sockaddr_type
&ng_ksocket_accept_type
},
{
NGM_KSOCKET_COOKIE,
NGM_KSOCKET_CONNECT,
"connect",
&ng_ksocket_sockaddr_type,
NULL
&ng_parse_int32_type
},
{
NGM_KSOCKET_COOKIE,
@ -473,7 +499,7 @@ static struct ng_type ng_ksocket_typestruct = {
ng_ksocket_shutdown,
ng_ksocket_newhook,
NULL,
NULL,
ng_ksocket_connect,
ng_ksocket_rcvdata,
ng_ksocket_disconnect,
ng_ksocket_cmds
@ -488,6 +514,8 @@ NETGRAPH_INIT(ksocket, &ng_ksocket_typestruct);
/*
* Node type constructor
* The NODE part is assumed to be all set up.
* There is already a reference to the node for us.
*/
static int
ng_ksocket_constructor(node_p node)
@ -500,6 +528,9 @@ ng_ksocket_constructor(node_p node)
if (priv == NULL)
return (ENOMEM);
LIST_INIT(&priv->embryos);
/* cross link them */
priv->node = node;
NG_NODE_SET_PRIVATE(node, priv);
/* Done */
@ -526,37 +557,96 @@ ng_ksocket_newhook(node_p node, hook_p hook, const char *name0)
if (priv->hook != NULL)
return (EISCONN);
/* Extract family, type, and protocol from hook name */
snprintf(name, sizeof(name), "%s", name0);
s1 = name;
if ((s2 = index(s1, '/')) == NULL)
return (EINVAL);
*s2++ = '\0';
if ((family = ng_ksocket_parse(ng_ksocket_families, s1, 0)) == -1)
return (EINVAL);
s1 = s2;
if ((s2 = index(s1, '/')) == NULL)
return (EINVAL);
*s2++ = '\0';
if ((type = ng_ksocket_parse(ng_ksocket_types, s1, 0)) == -1)
return (EINVAL);
s1 = s2;
if ((protocol = ng_ksocket_parse(ng_ksocket_protos, s1, family)) == -1)
return (EINVAL);
if (priv->flags & KSF_CLONED) {
if (priv->flags & KSF_EMBRYONIC) {
/* Remove ourselves from our parent's embryo list */
LIST_REMOVE(priv, siblings);
priv->flags &= ~KSF_EMBRYONIC;
}
} else {
/* Extract family, type, and protocol from hook name */
snprintf(name, sizeof(name), "%s", name0);
s1 = name;
if ((s2 = index(s1, '/')) == NULL)
return (EINVAL);
*s2++ = '\0';
family = ng_ksocket_parse(ng_ksocket_families, s1, 0);
if (family == -1)
return (EINVAL);
s1 = s2;
if ((s2 = index(s1, '/')) == NULL)
return (EINVAL);
*s2++ = '\0';
type = ng_ksocket_parse(ng_ksocket_types, s1, 0);
if (type == -1)
return (EINVAL);
s1 = s2;
protocol = ng_ksocket_parse(ng_ksocket_protos, s1, family);
if (protocol == -1)
return (EINVAL);
/* Create the socket */
if ((error = socreate(family, &priv->so, type, protocol, p)) != 0)
return (error);
/* Create the socket */
error = socreate(family, &priv->so, type, protocol, p);
if (error != 0)
return (error);
/* XXX call soreserve() ? */
/* XXX call soreserve() ? */
/* Add our hook for incoming data */
priv->so->so_upcallarg = (caddr_t)node;
priv->so->so_upcall = ng_ksocket_incoming;
priv->so->so_rcv.sb_flags |= SB_UPCALL;
}
/* OK */
priv->hook = hook;
return(0);
}
static int
ng_ksocket_connect(hook_p hook)
{
node_p node = NG_HOOK_NODE(hook);
const priv_p priv = NG_NODE_PRIVATE(node);
struct socket *const so = priv->so;
/* Add our hook for incoming data and other events */
priv->so->so_upcallarg = (caddr_t)node;
priv->so->so_upcall = ng_ksocket_incoming;
priv->so->so_rcv.sb_flags |= SB_UPCALL;
priv->so->so_snd.sb_flags |= SB_UPCALL;
priv->so->so_state |= SS_NBIO;
/*
* --Original comment--
* On a cloned socket we may have already received one or more
* upcalls which we couldn't handle without a hook. Handle
* those now.
* We cannot call the upcall function directly
* from here, because until this function has returned our
* hook isn't connected.
*
* ---meta comment for -current ---
* XXX This is dubius.
* Upcalls between the time that the hook was
* first created and now (on another processesor) will
* be earlier on the queue than the request to finalise the hook.
* By the time the hook is finalised,
* The queued upcalls will have happenned and the code
* will have discarded them because of a lack of a hook.
* (socket not open).
*
* This is a bad byproduct of the complicated way in which hooks
* are now created (3 daisy chained async events).
*
* Since we are a netgraph operation
* We know that we hold a lock on this node. This forces the
* request we make below to be queued rather than implemented
* immediatly which will cause the upcall function to be called a bit
* later.
* However, as we will run any waiting queued operations immediatly
* after doing this one, if we have not finalised the other end
* of the hook, those queued operations will fail.
*/
if (priv->flags & KSF_CLONED) {
ng_send_fn(node, NULL, &ng_ksocket_incoming2, so, M_NOWAIT);
}
return (0);
}
@ -572,6 +662,7 @@ ng_ksocket_rcvmsg(node_p node, item_p item, hook_p lasthook)
struct ng_mesg *resp = NULL;
int error = 0;
struct ng_mesg *msg;
ng_ID_t raddr;
NGI_GET_MSG(item, msg);
switch (msg->header.typecookie) {
@ -596,18 +687,13 @@ ng_ksocket_rcvmsg(node_p node, item_p item, hook_p lasthook)
case NGM_KSOCKET_LISTEN:
{
/* Sanity check */
if (msg->header.arglen != sizeof(int))
if (msg->header.arglen != sizeof(int32_t))
ERROUT(EINVAL);
if (so == NULL)
ERROUT(ENXIO);
/* Listen */
if ((error = solisten(so, *((int *)msg->data), p)) != 0)
break;
/* Notify sender when we get a connection attempt */
/* XXX implement me */
error = ENODEV;
error = solisten(so, *((int32_t *)msg->data), p);
break;
}
@ -619,14 +705,27 @@ ng_ksocket_rcvmsg(node_p node, item_p item, hook_p lasthook)
if (so == NULL)
ERROUT(ENXIO);
/* Accept on the socket in a non-blocking way */
/* Create a new ksocket node for the new connection */
/* Return a response with the peer's sockaddr and
the absolute name of the newly created node */
/* XXX implement me */
/* Make sure the socket is capable of accepting */
if (!(so->so_options & SO_ACCEPTCONN))
ERROUT(EINVAL);
if (priv->flags & KSF_ACCEPTING)
ERROUT(EALREADY);
error = ENODEV;
error = ng_ksocket_check_accept(priv);
if (error != 0 && error != EWOULDBLOCK)
ERROUT(error);
/*
* If a connection is already complete, take it.
* Otherwise let the upcall function deal with
* the connection when it comes in.
*/
priv->response_token = msg->header.token;
raddr = priv->response_addr;
if (error == 0) {
ng_ksocket_finish_accept(priv);
} else
priv->flags |= KSF_ACCEPTING;
break;
}
@ -650,8 +749,10 @@ ng_ksocket_rcvmsg(node_p node, item_p item, hook_p lasthook)
ERROUT(error);
}
if ((so->so_state & SS_ISCONNECTING) != 0)
/* Notify sender when we connect */
/* XXX implement me */
/* We will notify the sender when we connect */
priv->response_token = msg->header.token;
raddr = priv->response_addr;
priv->flags |= KSF_CONNECTING;
ERROUT(EINPROGRESS);
break;
}
@ -720,7 +821,7 @@ ng_ksocket_rcvmsg(node_p node, item_p item, hook_p lasthook)
sopt.sopt_dir = SOPT_GET;
sopt.sopt_level = ksopt->level;
sopt.sopt_name = ksopt->name;
sopt.sopt_p = p;
sopt.sopt_p = NULL;
sopt.sopt_valsize = NG_KSOCKET_MAX_OPTLEN;
ksopt = (struct ng_ksocket_sockopt *)resp->data;
sopt.sopt_val = ksopt->value;
@ -754,7 +855,7 @@ ng_ksocket_rcvmsg(node_p node, item_p item, hook_p lasthook)
sopt.sopt_name = ksopt->name;
sopt.sopt_val = ksopt->value;
sopt.sopt_valsize = valsize;
sopt.sopt_p = p;
sopt.sopt_p = NULL;
error = sosetopt(so, &sopt);
break;
}
@ -800,16 +901,29 @@ static int
ng_ksocket_shutdown(node_p node)
{
const priv_p priv = NG_NODE_PRIVATE(node);
priv_p embryo;
/* Close our socket (if any) */
if (priv->so != NULL) {
priv->so->so_upcall = NULL;
priv->so->so_rcv.sb_flags &= ~SB_UPCALL;
priv->so->so_snd.sb_flags &= ~SB_UPCALL;
soclose(priv->so);
priv->so = NULL;
}
/* If we are an embryo, take ourselves out of the parent's list */
if (priv->flags & KSF_EMBRYONIC) {
LIST_REMOVE(priv, siblings);
priv->flags &= ~KSF_EMBRYONIC;
}
/* Remove any embryonic children we have */
while (!LIST_EMPTY(&priv->embryos)) {
embryo = LIST_FIRST(&priv->embryos);
ng_rmnode_self(embryo->node);
}
/* Take down netgraph node */
bzero(priv, sizeof(*priv));
FREE(priv, M_NETGRAPH_KSOCKET);
@ -835,16 +949,52 @@ ng_ksocket_disconnect(hook_p hook)
/************************************************************************
HELPER STUFF
************************************************************************/
/*
* When incoming data is appended to the socket, we get notified here.
/*
* You should no-longer "just call" a netgraph node function
* from an external asynchronous event.
* This is because in doing so you are ignoring the locking on the netgraph
* nodes. Instead call your function via
* "int ng_send_fn(node_p node, hook_p hook, ng_item_fn *fn,
* void *arg1, int arg2);"
* this will call the function you chose, but will first do all the
* locking rigmarole. Your function MAY only be called at some distant future
* time (several millisecs away) so don't give it any arguments
* that may be revoked soon (e.g. on your stack).
* In this case even the 'so' argument is doubtful.
* While the function request is being processed the node
* has an extra reference and as such will not disappear until
* the request has at least been done, but the 'so' may not be so lucky.
* handle this by checking the validity of the node in the target function
* before dereferencing the socket pointer.
*/
static void
ng_ksocket_incoming(struct socket *so, void *arg, int waitflag)
{
const node_p node = arg;
ng_send_fn(node, NULL, &ng_ksocket_incoming2, so, waitflag);
}
/*
* When incoming data is appended to the socket, we get notified here.
* This is also called whenever a significant event occurs for the socket.
* We know that HOOK is NULL. Because of how we were called we know we have a
* lock on this node an are participating inthe netgraph locking.
* Our original caller may have queued this even some time ago and
* we cannot trust that he even still exists. The node however is being
* held with a reference by the queueing code, at least until we finish,
* even if it has been zapped, so first check it's validiy
* before we trust the socket (which was derived from it).
*/
static void
ng_ksocket_incoming2(node_p node, hook_p hook, void *arg1, int waitflag)
{
struct socket *so = arg1;
const priv_p priv = NG_NODE_PRIVATE(node);
struct mbuf *m;
struct ng_mesg *response;
struct uio auio;
int s, flags, error;
@ -855,8 +1005,52 @@ ng_ksocket_incoming(struct socket *so, void *arg, int waitflag)
splx(s);
return;
}
/* so = priv->so; *//* XXX could have derived this like so */
KASSERT(so == priv->so, ("%s: wrong socket", __FUNCTION__));
KASSERT(priv->hook != NULL, ("%s: no hook", __FUNCTION__));
/* Check whether a pending connect operation has completed */
if (priv->flags & KSF_CONNECTING) {
if ((error = so->so_error) != 0) {
so->so_error = 0;
so->so_state &= ~SS_ISCONNECTING;
}
if (!(so->so_state & SS_ISCONNECTING)) {
NG_MKMESSAGE(response, NGM_KSOCKET_COOKIE,
NGM_KSOCKET_CONNECT, sizeof(int32_t), waitflag);
if (response != NULL) {
response->header.flags |= NGF_RESP;
response->header.token = priv->response_token;
*(int32_t *)response->data = error;
/*
* send an async "response" message
* to the node that set us up
* (if it still exists)
*/
NG_SEND_MSG_ID(error, node, response,
priv->response_addr, NULL);
}
priv->flags &= ~KSF_CONNECTING;
}
}
/* Check whether a pending accept operation has completed */
if (priv->flags & KSF_ACCEPTING) {
error = ng_ksocket_check_accept(priv);
if (error != EWOULDBLOCK)
priv->flags &= ~KSF_ACCEPTING;
if (error == 0)
ng_ksocket_finish_accept(priv);
}
/*
* If we don't have a hook, we must handle data events later. When
* the hook gets created and is connected, this upcall function
* will be called again.
*/
if (priv->hook == NULL) {
splx(s);
return;
}
/* Read and forward available mbuf's */
auio.uio_procp = NULL;
@ -876,9 +1070,132 @@ ng_ksocket_incoming(struct socket *so, void *arg, int waitflag)
NG_SEND_DATA_ONLY(error, priv->hook, m);
}
} while (error == 0 && m != NULL);
/*
* If the peer has closed the connection, forward a 0-length mbuf
* to indicate end-of-file.
*/
if (so->so_state & SS_CANTRCVMORE && !(priv->flags & KSF_EOFSEEN)) {
MGETHDR(m, waitflag, MT_DATA);
if (m != NULL) {
m->m_len = m->m_pkthdr.len = 0;
NG_SEND_DATA_ONLY(error, priv->hook, m);
}
priv->flags |= KSF_EOFSEEN;
}
splx(s);
}
/*
* Check for a completed incoming connection and return 0 if one is found.
* Otherwise return the appropriate error code.
*/
static int
ng_ksocket_check_accept(priv_p priv)
{
struct socket *const head = priv->so;
int error;
if ((error = head->so_error) != 0) {
head->so_error = 0;
return error;
}
if (TAILQ_EMPTY(&head->so_comp)) {
if (head->so_state & SS_CANTRCVMORE)
return ECONNABORTED;
return EWOULDBLOCK;
}
return 0;
}
/*
* Handle the first completed incoming connection, assumed to be already
* on the socket's so_comp queue.
*/
static void
ng_ksocket_finish_accept(priv_p priv)
{
struct socket *const head = priv->so;
struct socket *so;
struct sockaddr *sa = NULL;
struct ng_mesg *resp;
struct ng_ksocket_accept *resp_data;
node_p node;
priv_p priv2;
int len;
int error;
so = TAILQ_FIRST(&head->so_comp);
if (so == NULL) /* Should never happen */
return;
TAILQ_REMOVE(&head->so_comp, so, so_list);
head->so_qlen--;
/* XXX KNOTE(&head->so_rcv.sb_sel.si_note, 0); */
so->so_state &= ~SS_COMP;
so->so_state |= SS_NBIO;
so->so_head = NULL;
soaccept(so, &sa);
len = OFFSETOF(struct ng_ksocket_accept, addr);
if (sa != NULL)
len += sa->sa_len;
NG_MKMESSAGE(resp, NGM_KSOCKET_COOKIE, NGM_KSOCKET_ACCEPT, len,
M_NOWAIT);
if (resp == NULL) {
soclose(so);
goto out;
}
resp->header.flags |= NGF_RESP;
resp->header.token = priv->response_token;
/* Clone a ksocket node to wrap the new socket */
error = ng_make_node_common(&ng_ksocket_typestruct, &node);
if (error) {
FREE(resp, M_NETGRAPH);
soclose(so);
goto out;
}
if (ng_ksocket_constructor(node) != 0) {
NG_NODE_UNREF(node);
FREE(resp, M_NETGRAPH);
soclose(so);
goto out;
}
priv2 = NG_NODE_PRIVATE(node);
priv2->so = so;
priv2->flags |= KSF_CLONED | KSF_EMBRYONIC;
/*
* Insert the cloned node into a list of embryonic children
* on the parent node. When a hook is created on the cloned
* node it will be removed from this list. When the parent
* is destroyed it will destroy any embryonic children it has.
*/
LIST_INSERT_HEAD(&priv->embryos, priv2, siblings);
so->so_upcallarg = (caddr_t)node;
so->so_upcall = ng_ksocket_incoming;
so->so_rcv.sb_flags |= SB_UPCALL;
so->so_snd.sb_flags |= SB_UPCALL;
/* Fill in the response data and send it or return it to the caller */
resp_data = (struct ng_ksocket_accept *)resp->data;
resp_data->nodeid = NG_NODE_ID(node);
if (sa != NULL)
bcopy(sa, &resp_data->addr, sa->sa_len);
NG_SEND_MSG_ID(error, node, resp, priv->response_addr, NULL);
out:
if (sa != NULL)
FREE(sa, M_SONAME);
}
/*
* Parse out either an integer value or an alias.
*/

View File

@ -43,6 +43,8 @@
#ifndef _NETGRAPH_KSOCKET_H_
#define _NETGRAPH_KSOCKET_H_
#include <sys/socket.h>
/* Node type name and magic cookie */
#define NG_KSOCKET_NODE_TYPE "ksocket"
#define NGM_KSOCKET_COOKIE 942710669
@ -69,6 +71,21 @@ struct ng_ksocket_sockopt {
} \
}
/* For NGM_KSOCKET_ACCEPT control message responses */
struct ng_ksocket_accept {
u_int32_t nodeid; /* node ID of connected ksocket */
struct sockaddr addr; /* peer's address (variable length) */
};
/* Keep this in sync with the above structure definition */
#define NGM_KSOCKET_ACCEPT_INFO { \
{ \
{ "nodeid", &ng_parse_hint32_type }, \
{ "addr", &ng_ksocket_generic_sockaddr_type }, \
{ NULL } \
} \
}
/* Netgraph commands */
enum {
NGM_KSOCKET_BIND = 1,