diff options
Diffstat (limited to 'support/nfs/svc_create.c')
-rw-r--r-- | support/nfs/svc_create.c | 525 |
1 files changed, 525 insertions, 0 deletions
diff --git a/support/nfs/svc_create.c b/support/nfs/svc_create.c new file mode 100644 index 0000000..976c2d2 --- /dev/null +++ b/support/nfs/svc_create.c @@ -0,0 +1,525 @@ +/* + * Copyright 2009 Oracle. All rights reserved. + * + * This file is part of nfs-utils. + * + * nfs-utils is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * nfs-utils is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with nfs-utils. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <memory.h> +#include <signal.h> +#include <unistd.h> +#include <errno.h> +#include <netdb.h> +#include "nfslib.h" + +#include <netinet/in.h> + +#include <sys/socket.h> +#include <sys/resource.h> + +#include <rpc/rpc.h> +#include <rpc/svc.h> + +#ifdef HAVE_TCP_WRAPPER +#include "tcpwrapper.h" +#endif + +#include "sockaddr.h" +#include "rpcmisc.h" +#include "xlog.h" + +#ifdef HAVE_LIBTIRPC + +#include <rpc/rpc_com.h> + +#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. + * + * Returns getaddrinfo(3) results if successful. Caller must + * invoke freeaddrinfo(3) on these results. + * + * Otherwise NULL is returned if an error occurs. + */ +__attribute__((__malloc__)) +static struct addrinfo * +svc_create_bindaddr(struct netconfig *nconf, const uint16_t port) +{ + struct addrinfo *ai = NULL; + struct addrinfo hint = { + .ai_flags = AI_PASSIVE | AI_NUMERICSERV, + }; + char buf[8]; + int error; + + if (strcmp(nconf->nc_protofmly, NC_INET) == 0) + hint.ai_family = AF_INET; +#ifdef IPV6_SUPPORTED + else if (strcmp(nconf->nc_protofmly, NC_INET6) == 0) + hint.ai_family = AF_INET6; +#endif /* IPV6_SUPPORTED */ + else { + xlog(L_ERROR, "Unrecognized bind address family: %s", + nconf->nc_protofmly); + return NULL; + } + + if (strcmp(nconf->nc_proto, NC_UDP) == 0) + hint.ai_protocol = (int)IPPROTO_UDP; + else if (strcmp(nconf->nc_proto, NC_TCP) == 0) + hint.ai_protocol = (int)IPPROTO_TCP; + else { + xlog(L_ERROR, "Unrecognized bind address protocol: %s", + nconf->nc_proto); + return NULL; + } + + (void)snprintf(buf, sizeof(buf), "%u", port); + error = getaddrinfo(NULL, buf, &hint, &ai); + if (error != 0) { + xlog(L_ERROR, "Failed to construct bind address: %s", + gai_strerror(error)); + return NULL; + } + + 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: %lu", + __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_rand_port(const char *name, const rpcprog_t program, + const rpcvers_t version, + void (*dispatch)(struct svc_req *, SVCXPRT *), + struct netconfig *nconf) +{ + struct t_bind bindaddr; + struct addrinfo *ai; + SVCXPRT *xprt; + + ai = svc_create_bindaddr(nconf, 0); + if (ai == NULL) + return 0; + + bindaddr.addr.buf = ai->ai_addr; + bindaddr.qlen = SOMAXCONN; + + xprt = svc_tli_create(RPC_ANYFD, nconf, &bindaddr, 0, 0); + nfs_freeaddrinfo(ai); + if (xprt == NULL) { + xlog(L_ERROR, "Failed to create listener xprt " + "(%s, %u, %s)", name, version, nconf->nc_netid); + return 0; + } + if (svcsock_nonblock(xprt->xp_fd) < 0) { + /* close() already done by svcsock_nonblock() */ + xprt->xp_fd = RPC_ANYFD; + SVC_DESTROY(xprt); + return 0; + } + + rpc_createerr.cf_stat = rpc_createerr.cf_error.re_errno = 0; + if (!svc_reg(xprt, program, version, dispatch, nconf)) { + /* svc_reg(3) destroys @xprt in this case */ + xlog(L_ERROR, "Failed to register (%s, %u, %s): %s", + name, version, nconf->nc_netid, + clnt_spcreateerror("svc_reg() err")); + 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); + fd = svcsock_nonblock(fd); + 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); + + nfs_freeaddrinfo(ai); + return 1; + +out_free: + nfs_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 + * @program: RPC program number to register + * @version: RPC version number to register + * @dispatch: address of function that handles incoming RPC requests + * @port: if not zero, transport listens on this port + * + * Sets up network transports for receiving RPC requests, and starts + * the RPC dispatcher. Returns the number of started network transports. + */ +unsigned int +nfs_svc_create(char *name, const rpcprog_t program, const rpcvers_t version, + void (*dispatch)(struct svc_req *, SVCXPRT *), + const uint16_t port) +{ + const struct sigaction create_sigaction = { + .sa_handler = SIG_IGN, + }; + int maxrec = RPC_MAXDATASIZE; + unsigned int visible, up, servport; + struct netconfig *nconf; + void *handlep; + + /* + * Ignore SIGPIPE to avoid exiting sideways when peers + * close their TCP connection while we're trying to reply + * to them. + */ + (void)sigaction(SIGPIPE, &create_sigaction, NULL); + + /* + * Setting MAXREC also enables non-blocking mode for tcp connections. + * This avoids DOS attacks by a client sending many requests but never + * reading the reply: + * - if a second request already is present for reading in the socket, + * after the first request just was read, libtirpc will break the + * connection. Thus an attacker can't simply send requests as fast as + * he can without waiting for the response. + * - if the write buffer of the socket is full, the next write() will + * fail with EAGAIN. libtirpc will retry the write in a loop for max. + * 2 seconds. If write still fails, the connection will be closed. + */ + rpc_control(RPC_SVC_CONNMAXREC_SET, &maxrec); + + handlep = setnetconfig(); + if (handlep == NULL) { + xlog(L_ERROR, "Failed to access local netconfig database: %s", + nc_sperror()); + return 0; + } + + visible = 0; + up = 0; + while ((nconf = getnetconfig(handlep)) != NULL) { + if (!(nconf->nc_flag & NC_VISIBLE)) + continue; + visible++; + + if (!strcmp(nconf->nc_proto, NC_UDP) && !NFSCTL_UDPISSET(_rpcprotobits)) + continue; + + if (!strcmp(nconf->nc_proto, NC_TCP) && !NFSCTL_TCPISSET(_rpcprotobits)) + continue; + + if (port == 0) + servport = getservport(program, nconf->nc_proto); + else + servport = port; + + up += svc_create_nconf(name, program, version, dispatch, + servport, nconf); + } + + if (visible == 0) + xlog(L_ERROR, "Failed to find any visible netconfig entries"); + + if (endnetconfig(handlep) == -1) + xlog(L_ERROR, "Failed to close local netconfig database: %s", + nc_sperror()); + + return up; +} + +/** + * nfs_svc_unregister - remove service registrations from local rpcbind database + * @program: RPC program number to unregister + * @version: RPC version number to unregister + * + * Removes all registrations for [ @program, @version ] . + */ +void +nfs_svc_unregister(const rpcprog_t program, const rpcvers_t version) +{ + if (rpcb_unset(program, version, NULL) == FALSE) + xlog(D_GENERAL, "Failed to unregister program %lu, version %lu", + (unsigned long)program, (unsigned long)version); +} + +#else /* !HAVE_LIBTIRPC */ + +/** + * nfs_svc_create - start up RPC svc listeners + * @name: C string containing name of new service + * @program: RPC program number to register + * @version: RPC version number to register + * @dispatch: address of function that handles incoming RPC requests + * @port: if not zero, transport listens on this port + * + * Sets up network transports for receiving RPC requests, and starts + * the RPC dispatcher. Returns the number of started network transports. + */ +unsigned int +nfs_svc_create(char *name, const rpcprog_t program, const rpcvers_t version, + void (*dispatch)(struct svc_req *, SVCXPRT *), + const uint16_t port) +{ + rpc_init(name, (int)program, (int)version, dispatch, (int)port); + return 1; +} + +/** + * nfs_svc_unregister - remove service registrations from local rpcbind database + * @program: RPC program number to unregister + * @version: RPC version number to unregister + * + * Removes all registrations for [ @program, @version ] . + */ +void +nfs_svc_unregister(const rpcprog_t program, const rpcvers_t version) +{ + if (pmap_unset((unsigned long)program, (unsigned long)version) == FALSE) + xlog(D_GENERAL, "Failed to unregister program %lu, version %lu", + (unsigned long)program, (unsigned long)version); +} + +#endif /* !HAVE_LIBTIRPC */ |