summaryrefslogtreecommitdiffstats
path: root/sys
diff options
context:
space:
mode:
authorjulian <julian@FreeBSD.org>2001-09-07 07:12:51 +0000
committerjulian <julian@FreeBSD.org>2001-09-07 07:12:51 +0000
commitf307b418db18dcf0791a53a9429ff162625fbd9c (patch)
treed8b77b4786265ddc9ad5cabcb4db3ed65b6ad9a4 /sys
parentc5b125fd9c470012cdddcf6fc1488f62fccd8069 (diff)
downloadFreeBSD-src-f307b418db18dcf0791a53a9429ff162625fbd9c.zip
FreeBSD-src-f307b418db18dcf0791a53a9429ff162625fbd9c.tar.gz
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
Diffstat (limited to 'sys')
-rw-r--r--sys/netgraph/ng_ksocket.c415
-rw-r--r--sys/netgraph/ng_ksocket.h17
2 files changed, 383 insertions, 49 deletions
diff --git a/sys/netgraph/ng_ksocket.c b/sys/netgraph/ng_ksocket.c
index 5baefa2..c29aa7d 100644
--- a/sys/netgraph/ng_ksocket.c
+++ b/sys/netgraph/ng_ksocket.c
@@ -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 */
+ error = socreate(family, &priv->so, type, protocol, p);
+ if (error != 0)
+ return (error);
- /* Create the socket */
- if ((error = socreate(family, &priv->so, type, protocol, p)) != 0)
- return (error);
+ /* XXX call soreserve() ? */
+
+ }
+
+ /* OK */
+ priv->hook = hook;
+ return(0);
+}
- /* XXX call soreserve() ? */
+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 */
+ /* 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);
+ }
- /* OK */
- priv->hook = hook;
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 = ng_ksocket_check_accept(priv);
+ if (error != 0 && error != EWOULDBLOCK)
+ ERROUT(error);
- error = ENODEV;
+ /*
+ * 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
************************************************************************/
+/*
+ * 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_incoming(struct socket *so, void *arg, int waitflag)
+ng_ksocket_incoming2(node_p node, hook_p hook, void *arg1, int waitflag)
{
- const node_p node = arg;
+ 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,10 +1070,133 @@ 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.
*/
static int
diff --git a/sys/netgraph/ng_ksocket.h b/sys/netgraph/ng_ksocket.h
index fd6a579..efb65aa 100644
--- a/sys/netgraph/ng_ksocket.h
+++ b/sys/netgraph/ng_ksocket.h
@@ -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,
OpenPOWER on IntegriCloud