|
|
At the expert level, network selection is done exactly as at the intermediate level. The only difference here is in the level of control that the application has over the details of the transport's configuration. Control at this level is much greater. These examples illustrate that control, which is exercised using the clnt_tli_create and svc_tli_create routines.
This is the client side of some code that implements a version of clntudp_create (the client-side creation routine for the UDP transport) in terms of clnt_tli_create. The example shows how to do network selection based on the family of the transport one wishes to use.
clnt_tli_create is normally used to create a client handle when:
#include <stdio.h> #include <rpc/rpc.h> #include <netconfig.h> #include <netinet/in.h> /* * In an earlier implementation of RPC, the only transports supported * were TCP/IP and UDP/IP. Here they are shown based on the Berkeley * socket, but implemented on the top of XTI/Streams. */ CLIENT * clntudp_create(raddr, prog, vers, wait, sockp) struct sockaddr_in *raddr; /* Remote address */ u_long prog; /* Program number */ u_long vers; /* Version number */ struct timeval wait; /* Time to wait */ int *sockp; /* fd pointer */ { CLIENT *cl; /* Client handle */ int madefd = FALSE; /* Is fd opened here */ int fd = *sockp; /* fd */ struct t_bind *tbind; /* bind address */ struct netconfig *nconf; /* netconfig structure */ void *handlep;The network selection is done using the library functions setnetconfig, getnetconfig, and endnetconfig. (Note that endnetconfig is not called until after the call to clnt_tli_create, near the end of the example.)if ((handlep = setnetconfig()) == 0) { /* No transports available */ rpc_createerr.cf_stat = RPC_UNKNOWNPROTO; return ((CLIENT *)NULL); } /* * Try all the transports till it gets one which is * connectionless, family is INET and name is UDP */ while (nconf = getnetconfig(handlep)) { if ((nconf->nc_semantics == NC_TPI_CLTS) && (strcmp(nconf->nc_protofmly, NC_INET) == 0) && (strcmp(nconf->nc_proto, NC_UDP) == 0)) break; } if (nconf == NULL) { rpc_createerr.cf_stat = RPC_UNKNOWNPROTO; goto err; } if (fd == RPC_ANYSOCK) { fd = t_open(nconf->nc_device, O_RDWR, NULL); if (fd == -1) { rpc_createerr.cf_stat = RPC_SYSTEMERROR; goto err; } madefd = TRUE; /* The fd was opened here */ } if (raddr->sin_port == 0) { /* remote addr unknown */ ushort_t sport;
/* * rpcb_getport() is a user provided routine * which will call rpcb_getaddr and translate * the netbuf address to port number. */ sport = rpcb_getport(raddr, prog, vers, nconf); if (sport == 0) { rpc_createerr.cf_stat = RPC_PROGUNAVAIL; goto err; } raddr->sin_port = sport; }
/* Transform sockaddr_in to netbuf */ tbind = (struct t_bind *)t_alloc(fd, T_BIND, T_ADDR); if (tbind == NULL) { rpc_createerr.cf_stat = RPC_SYSTEMERROR; goto err; } (void) memcpy(tbind->addr.buf, (char *)raddr, (int)tbind->addr.maxlen); tbind->addr.len = tbind->addr.maxlen;
/* Bind endpoint to a reserved address */ (void) bind_resv(fd); cl = clnt_tli_create(fd, nconf, &(tbind->addr), prog, vers, 8800, 8800); (void) endnetconfig(handlep); /* Close the netconfig file */ (void) t_free((char *)tbind, T_BIND); if (cl) { *sockp = fd; if (madefd == TRUE) { /* fd should be closed while destroying the handle */ (void) CLNT_CONTROL(cl, CLSET_FD_CLOSE, NULL); } /* Set the retry time */ (void) CLNT_CONTROL(cl, CLSET_RETRY_TIMEOUT, &wait); return (cl); }
err: if (madefd == TRUE) (void) t_close(fd); (void) endnetconfig(handlep); return ((CLIENT *)NULL); }
clntudp_create
can be passed an open
fd,
but if not
(fd == RPC_ANYSOCK)
,
it will open its own using the
netconfig
structure for
UDP.
If the remote address is not known,
(raddr->sin_port == 0)
,
then it is obtained from the remote
rpcbind.
Note the call to
bind_resv,
which is a user-supplied function that
serves to bind a transport endpoint to a reserved address.
This
call is necessary because there is no notion of a reserved address in
RPC under
XTI,
as there is in both
TCP
and
UDP.
The implementation of
this routine is of no interest here, because it is entirely transport
specific.
What is of interest is the scaffolding necessary to call it.
After the client handle has been created, the programmer can suitably customize it using calls to clnt_control. Here, the RPC library closes the file descriptor while destroying the handle (as it usually does with a call to clnt_destroy when it opens the fd itself) and sets the retry timeout period.
Below is the corresponding server code. It implements svcudp_create in terms of svc_tli_create, and calls the user provided bind_resv to bind the transport endpoint to a reserved address.
svc_tli_create is normally used when the application needs a fine degree of control, and especially if it is necessary to:
#include <stdio.h> #include <rpc/rpc.h> #include <netconfig.h> #include <netinet/in.h>The network selection here is done in a similar way as in clntudp_create./* * On the server side */ SVCXPRT * svcudp_create(fd) register int fd; { struct netconfig *nconf; SVCXPRT *svc; int madefd = FALSE; int port; void *handlep;
if ((handlep = setnetconfig()) == 0) { /* No transports available */ nc_perror("server"); return ((SVCXPRT *)NULL); } /* * Try all the transports till it gets one which is * a connection less, family is INET and name is UDP */ while (nconf = getnetconfig(handlep)) { if ((nconf->nc_semantics == NC_TPI_CLTS) && (strcmp(nconf->nc_protofmly, NC_INET) == 0) && (strcmp(nconf->nc_proto, NC_UDP) == 0)) break; } if (nconf == NULL) { endnetconfig(handlep); fprintf(stderr, "UDP transport not available\n"); return ((SVCXPRT *)NULL); } if (fd == RPC_ANYFD) { fd = t_open(nconf->nc_device, O_RDWR, NULL); if (fd == -1) { (void) endnetconfig(); (void) fprintf(stderr, "svcudp_create: could not open connection\n"); return ((SVCXPRT *)NULL); } madefd = TRUE; }
/* * Bind Endpoint to a reserved address */ port = bind_resv(fd); svc = svc_tli_create(fd, nconf, (struct t_bind *)NULL, 8800, 8800); (void) endnetconfig(handlep); if (svc == (SVCXPRT *)NULL) { if (madefd) (void) t_close(fd); return ((SVCXPRT *)NULL); } if (port == -1) /* Specifically set xp_port now */ svc->xp_port = ((struct sockaddr_in *)svc->xp_ltaddr.buf)->sin_port; else svc->xp_port = port; return (svc); }
svcudp_create is set up to receive an open fd, but if it does not, it will open one itself using the selected netconfig structure.
bind_resv is a user-provided function that binds the fd to a reserved port if the caller is an RPC administrator.