diff options
-rw-r--r-- | sys/netgraph/ng_ksocket.c | 415 | ||||
-rw-r--r-- | sys/netgraph/ng_ksocket.h | 17 |
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, |