#include <memory.h>
#include <signal.h>
#include <unistd.h>
+#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
#include "tcpwrapper.h"
#endif
+#include "sockaddr.h"
#include "rpcmisc.h"
#include "xlog.h"
#ifdef HAVE_LIBTIRPC
+#define SVC_CREATE_XPRT_CACHE_SIZE (8)
+static SVCXPRT *svc_create_xprt_cache[SVC_CREATE_XPRT_CACHE_SIZE] = { NULL, };
+
+/*
+ * Cache an SVC xprt, in case there are more programs or versions to
+ * register against it.
+ */
+static void
+svc_create_cache_xprt(SVCXPRT *xprt)
+{
+ unsigned int i;
+
+ /* Check if we've already got this one... */
+ for (i = 0; i < SVC_CREATE_XPRT_CACHE_SIZE; i++)
+ if (svc_create_xprt_cache[i] == xprt)
+ return;
+
+ /* No, we don't. Cache it. */
+ for (i = 0; i < SVC_CREATE_XPRT_CACHE_SIZE; i++)
+ if (svc_create_xprt_cache[i] == NULL) {
+ svc_create_xprt_cache[i] = xprt;
+ return;
+ }
+
+ xlog(L_ERROR, "%s: Failed to cache an xprt", __func__);
+}
+
+/*
+ * Find a previously cached SVC xprt structure with the given bind address
+ * and transport semantics.
+ *
+ * Returns pointer to a cached SVC xprt.
+ *
+ * If no matching SVC XPRT can be found, NULL is returned.
+ */
+static SVCXPRT *
+svc_create_find_xprt(const struct sockaddr *bindaddr, const struct netconfig *nconf)
+{
+ unsigned int i;
+
+ for (i = 0; i < SVC_CREATE_XPRT_CACHE_SIZE; i++) {
+ SVCXPRT *xprt = svc_create_xprt_cache[i];
+ struct sockaddr *sap;
+
+ if (xprt == NULL)
+ continue;
+ if (strcmp(nconf->nc_netid, xprt->xp_netid) != 0)
+ continue;
+ sap = (struct sockaddr *)xprt->xp_ltaddr.buf;
+ if (!nfs_compare_sockaddr(bindaddr, sap))
+ continue;
+ return xprt;
+ }
+ return NULL;
+}
+
/*
* Set up an appropriate bind address, given @port and @nconf.
*
return ai;
}
+/*
+ * Create a listener socket on a specific bindaddr, and set
+ * special socket options to allow it to share the same port
+ * as other listeners.
+ *
+ * Returns an open, bound, and possibly listening network
+ * socket on success.
+ *
+ * Otherwise returns -1 if some error occurs.
+ */
+static int
+svc_create_sock(const struct sockaddr *sap, socklen_t salen,
+ struct netconfig *nconf)
+{
+ int fd, type, protocol;
+ int one = 1;
+
+ switch(nconf->nc_semantics) {
+ case NC_TPI_CLTS:
+ type = SOCK_DGRAM;
+ break;
+ case NC_TPI_COTS_ORD:
+ type = SOCK_STREAM;
+ break;
+ default:
+ xlog(D_GENERAL, "%s: Unrecognized bind address semantics: %u",
+ __func__, nconf->nc_semantics);
+ return -1;
+ }
+
+ if (strcmp(nconf->nc_proto, NC_UDP) == 0)
+ protocol = (int)IPPROTO_UDP;
+ else if (strcmp(nconf->nc_proto, NC_TCP) == 0)
+ protocol = (int)IPPROTO_TCP;
+ else {
+ xlog(D_GENERAL, "%s: Unrecognized bind address protocol: %s",
+ __func__, nconf->nc_proto);
+ return -1;
+ }
+
+ fd = socket((int)sap->sa_family, type, protocol);
+ if (fd == -1) {
+ xlog(L_ERROR, "Could not make a socket: (%d) %m",
+ errno);
+ return -1;
+ }
+
+#ifdef IPV6_SUPPORTED
+ if (sap->sa_family == AF_INET6) {
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY,
+ &one, sizeof(one)) == -1) {
+ xlog(L_ERROR, "Failed to set IPV6_V6ONLY: (%d) %m",
+ errno);
+ (void)close(fd);
+ return -1;
+ }
+ }
+#endif /* IPV6_SUPPORTED */
+
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
+ &one, sizeof(one)) == -1) {
+ xlog(L_ERROR, "Failed to set SO_REUSEADDR: (%d) %m",
+ errno);
+ (void)close(fd);
+ return -1;
+ }
+
+ if (bind(fd, sap, salen) == -1) {
+ xlog(L_ERROR, "Could not bind socket: (%d) %m",
+ errno);
+ (void)close(fd);
+ return -1;
+ }
+
+ if (nconf->nc_semantics == NC_TPI_COTS_ORD)
+ if (listen(fd, SOMAXCONN) == -1) {
+ xlog(L_ERROR, "Could not listen on socket: (%d) %m",
+ errno);
+ (void)close(fd);
+ return -1;
+ }
+
+ return fd;
+}
+
+/*
+ * The simple case is allowing the TI-RPC library to create a
+ * transport itself, given just the bind address and transport
+ * semantics.
+ *
+ * Our local xprt cache is ignored in this path, since the
+ * caller is not interested in sharing listeners or ports, and
+ * the library automatically avoids ports already in use.
+ *
+ * Returns the count of started listeners (one or zero).
+ */
static unsigned int
-svc_create_nconf(const char *name, const rpcprog_t program,
+svc_create_nconf_rand_port(const char *name, const rpcprog_t program,
const rpcvers_t version,
void (*dispatch)(struct svc_req *, SVCXPRT *),
- const uint16_t port, struct netconfig *nconf)
+ struct netconfig *nconf)
{
struct t_bind bindaddr;
struct addrinfo *ai;
SVCXPRT *xprt;
- ai = svc_create_bindaddr(nconf, port);
+ ai = svc_create_bindaddr(nconf, 0);
if (ai == NULL)
return 0;
freeaddrinfo(ai);
if (xprt == NULL) {
xlog(D_GENERAL, "Failed to create listener xprt "
- "(%s, %u, %s)", name, version, nconf->nc_netid);
+ "(%s, %u, %s)", name, version, nconf->nc_netid);
return 0;
}
return 1;
}
+/*
+ * If a port is specified on the command line, that port value will be
+ * the same for all listeners created here. Create each listener
+ * socket in advance and set SO_REUSEADDR, rather than allowing the
+ * RPC library to create the listeners for us on a randomly chosen
+ * port via svc_tli_create(RPC_ANYFD).
+ *
+ * Some callers want to listen for more than one RPC version using the
+ * same port number. For example, mountd could want to listen for MNT
+ * version 1, 2, and 3 requests. This means mountd must use the same
+ * set of listener sockets for multiple RPC versions, since, on one
+ * system, you can't have two listener sockets with the exact same
+ * bind address (and port) and transport protocol.
+ *
+ * To accomplish this, this function caches xprts as they are created.
+ * This cache is checked to see if a previously created xprt can be
+ * used, before creating a new xprt for this [program, version]. If
+ * there is a cached xprt with the same bindaddr and transport
+ * semantics, we simply register the new version with that xprt,
+ * rather than creating a fresh xprt for it.
+ *
+ * The xprt cache implemented here is local to a process. Two
+ * separate RPC daemons can not share a set of listeners.
+ *
+ * Returns the count of started listeners (one or zero).
+ */
+static unsigned int
+svc_create_nconf_fixed_port(const char *name, const rpcprog_t program,
+ const rpcvers_t version,
+ void (*dispatch)(struct svc_req *, SVCXPRT *),
+ const uint16_t port, struct netconfig *nconf)
+{
+ struct addrinfo *ai;
+ SVCXPRT *xprt;
+
+ ai = svc_create_bindaddr(nconf, port);
+ if (ai == NULL)
+ return 0;
+
+ xprt = svc_create_find_xprt(ai->ai_addr, nconf);
+ if (xprt == NULL) {
+ int fd;
+
+ fd = svc_create_sock(ai->ai_addr, ai->ai_addrlen, nconf);
+ if (fd == -1)
+ goto out_free;
+
+ xprt = svc_tli_create(fd, nconf, NULL, 0, 0);
+ if (xprt == NULL) {
+ xlog(D_GENERAL, "Failed to create listener xprt "
+ "(%s, %u, %s)", name, version, nconf->nc_netid);
+ (void)close(fd);
+ goto out_free;
+ }
+ }
+
+ if (!svc_reg(xprt, program, version, dispatch, nconf)) {
+ /* svc_reg(3) destroys @xprt in this case */
+ xlog(D_GENERAL, "Failed to register (%s, %u, %s)",
+ name, version, nconf->nc_netid);
+ goto out_free;
+ }
+
+ svc_create_cache_xprt(xprt);
+
+ freeaddrinfo(ai);
+ return 1;
+
+out_free:
+ freeaddrinfo(ai);
+ return 0;
+}
+
+static unsigned int
+svc_create_nconf(const char *name, const rpcprog_t program,
+ const rpcvers_t version,
+ void (*dispatch)(struct svc_req *, SVCXPRT *),
+ const uint16_t port, struct netconfig *nconf)
+{
+ if (port != 0)
+ return svc_create_nconf_fixed_port(name, program,
+ version, dispatch, port, nconf);
+
+ return svc_create_nconf_rand_port(name, program,
+ version, dispatch, nconf);
+}
+
/**
* nfs_svc_create - start up RPC svc listeners
* @name: C string containing name of new service
* the RPC dispatcher. Returns the number of started network transports.
*/
unsigned int
-nfs_svc_create(__attribute__((unused)) char *name,
- const rpcprog_t program, const rpcvers_t version,
+nfs_svc_create(char *name, const rpcprog_t program, const rpcvers_t version,
void (*dispatch)(struct svc_req *, SVCXPRT *),
const uint16_t port)
{