]>
Commit | Line | Data |
---|---|---|
927aa100 JR |
1 | commit 0e94118815e8ff9c1142117764ee3e6cddba0395 |
2 | Author: Chuck Lever <chuck.lever@oracle.com> | |
3 | Date: Fri Oct 1 15:04:20 2010 -0400 | |
4 | ||
5 | libnfs.a: Allow multiple RPC listeners to share listener port number | |
6 | ||
7 | Normally, when "-p" is not specified on the mountd command line, the | |
8 | TI-RPC library chooses random port numbers for each listener. If a | |
9 | port number _is_ specified on the command line, all the listeners | |
10 | will get the same port number, so SO_REUSEADDR needs to be set on | |
11 | each socket. | |
12 | ||
13 | Thus we can't let TI-RPC create the listener sockets for us in this | |
14 | case; we must create them ourselves and then set SO_REUSEADDR (and | |
15 | other socket options) by hand. | |
16 | ||
17 | Different versions of the same RPC program have to share the same | |
18 | listener and SVCXPRT, so we have to cache xprts we create, and re-use | |
19 | them when additional requests for registration come from the | |
20 | application. | |
21 | ||
22 | Though it doesn't look like it, this fix was "copied" from the legacy | |
23 | rpc_init() function. It's more complicated for TI-RPC, of course, | |
24 | since you can have an arbitrary number of listeners, not just two | |
25 | (one for AF_INET UDP and one for AF_INET TCP). | |
26 | ||
27 | Fix for: | |
28 | ||
29 | https://bugzilla.linux-nfs.org/show_bug.cgi?id=190 | |
30 | ||
31 | There have been no reports of problems with specifying statd's | |
32 | listener port, but I expect this is a problem for statd too. | |
33 | ||
34 | Signed-off-by: Chuck Lever <chuck.lever@oracle.com> | |
35 | ||
36 | diff --git a/support/nfs/svc_create.c b/support/nfs/svc_create.c | |
37 | index 59ba505..fdc4846 100644 | |
38 | --- a/support/nfs/svc_create.c | |
39 | +++ b/support/nfs/svc_create.c | |
40 | @@ -27,6 +27,7 @@ | |
41 | #include <memory.h> | |
42 | #include <signal.h> | |
43 | #include <unistd.h> | |
44 | +#include <errno.h> | |
45 | #include <netdb.h> | |
46 | ||
47 | #include <netinet/in.h> | |
48 | @@ -41,11 +42,68 @@ | |
49 | #include "tcpwrapper.h" | |
50 | #endif | |
51 | ||
52 | +#include "sockaddr.h" | |
53 | #include "rpcmisc.h" | |
54 | #include "xlog.h" | |
55 | ||
56 | #ifdef HAVE_LIBTIRPC | |
57 | ||
58 | +#define SVC_CREATE_XPRT_CACHE_SIZE (8) | |
59 | +static SVCXPRT *svc_create_xprt_cache[SVC_CREATE_XPRT_CACHE_SIZE] = { NULL, }; | |
60 | + | |
61 | +/* | |
62 | + * Cache an SVC xprt, in case there are more programs or versions to | |
63 | + * register against it. | |
64 | + */ | |
65 | +static void | |
66 | +svc_create_cache_xprt(SVCXPRT *xprt) | |
67 | +{ | |
68 | + unsigned int i; | |
69 | + | |
70 | + /* Check if we've already got this one... */ | |
71 | + for (i = 0; i < SVC_CREATE_XPRT_CACHE_SIZE; i++) | |
72 | + if (svc_create_xprt_cache[i] == xprt) | |
73 | + return; | |
74 | + | |
75 | + /* No, we don't. Cache it. */ | |
76 | + for (i = 0; i < SVC_CREATE_XPRT_CACHE_SIZE; i++) | |
77 | + if (svc_create_xprt_cache[i] == NULL) { | |
78 | + svc_create_xprt_cache[i] = xprt; | |
79 | + return; | |
80 | + } | |
81 | + | |
82 | + xlog(L_ERROR, "%s: Failed to cache an xprt", __func__); | |
83 | +} | |
84 | + | |
85 | +/* | |
86 | + * Find a previously cached SVC xprt structure with the given bind address | |
87 | + * and transport semantics. | |
88 | + * | |
89 | + * Returns pointer to a SVC xprt. | |
90 | + * | |
91 | + * If no matching SVC XPRT can be found, NULL is returned. | |
92 | + */ | |
93 | +static SVCXPRT * | |
94 | +svc_create_find_xprt(const struct sockaddr *bindaddr, const struct netconfig *nconf) | |
95 | +{ | |
96 | + unsigned int i; | |
97 | + | |
98 | + for (i = 0; i < SVC_CREATE_XPRT_CACHE_SIZE; i++) { | |
99 | + SVCXPRT *xprt = svc_create_xprt_cache[i]; | |
100 | + struct sockaddr *sap; | |
101 | + | |
102 | + if (xprt == NULL) | |
103 | + continue; | |
104 | + if (strcmp(nconf->nc_netid, xprt->xp_netid) != 0) | |
105 | + continue; | |
106 | + sap = (struct sockaddr *)xprt->xp_ltaddr.buf; | |
107 | + if (!nfs_compare_sockaddr(bindaddr, sap)) | |
108 | + continue; | |
109 | + return xprt; | |
110 | + } | |
111 | + return NULL; | |
112 | +} | |
113 | + | |
114 | /* | |
115 | * Set up an appropriate bind address, given @port and @nconf. | |
116 | * | |
117 | @@ -98,17 +156,112 @@ svc_create_bindaddr(struct netconfig *nconf, const uint16_t port) | |
118 | return ai; | |
119 | } | |
120 | ||
121 | +/* | |
122 | + * Create a listener socket on a specific bindaddr, and set | |
123 | + * special socket options to allow it to share the same port | |
124 | + * as other listeners. | |
125 | + * | |
126 | + * Returns an open, bound, and possibly listening network | |
127 | + * socket on success. | |
128 | + * | |
129 | + * Otherwise returns -1 if some error occurs. | |
130 | + */ | |
131 | +static int | |
132 | +svc_create_sock(const struct sockaddr *sap, socklen_t salen, | |
133 | + struct netconfig *nconf) | |
134 | +{ | |
135 | + int fd, type, protocol; | |
136 | + int one = 1; | |
137 | + | |
138 | + switch(nconf->nc_semantics) { | |
139 | + case NC_TPI_CLTS: | |
140 | + type = SOCK_DGRAM; | |
141 | + break; | |
142 | + case NC_TPI_COTS_ORD: | |
143 | + type = SOCK_STREAM; | |
144 | + break; | |
145 | + default: | |
146 | + xlog(D_GENERAL, "%s: Unrecognized bind address semantics: %u", | |
147 | + __func__, nconf->nc_semantics); | |
148 | + return -1; | |
149 | + } | |
150 | + | |
151 | + if (strcmp(nconf->nc_proto, NC_UDP) == 0) | |
152 | + protocol = (int)IPPROTO_UDP; | |
153 | + else if (strcmp(nconf->nc_proto, NC_TCP) == 0) | |
154 | + protocol = (int)IPPROTO_TCP; | |
155 | + else { | |
156 | + xlog(D_GENERAL, "%s: Unrecognized bind address protocol: %s", | |
157 | + __func__, nconf->nc_proto); | |
158 | + return -1; | |
159 | + } | |
160 | + | |
161 | + fd = socket((int)sap->sa_family, type, protocol); | |
162 | + if (fd == -1) { | |
163 | + xlog(L_ERROR, "Could not make a socket: (%d) %m", | |
164 | + errno); | |
165 | + return -1; | |
166 | + } | |
167 | + | |
168 | +#ifdef IPV6_SUPPORTED | |
169 | + if (sap->sa_family == AF_INET6) { | |
170 | + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, | |
171 | + &one, sizeof(one)) == -1) { | |
172 | + xlog(L_ERROR, "Failed to set IPV6_V6ONLY: (%d) %m", | |
173 | + errno); | |
174 | + (void)close(fd); | |
175 | + return -1; | |
176 | + } | |
177 | + } | |
178 | +#endif /* IPV6_SUPPORTED */ | |
179 | + | |
180 | + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, | |
181 | + &one, sizeof(one)) == -1) { | |
182 | + xlog(L_ERROR, "Failed to set SO_REUSEADDR: (%d) %m", | |
183 | + errno); | |
184 | + (void)close(fd); | |
185 | + return -1; | |
186 | + } | |
187 | + | |
188 | + if (bind(fd, sap, salen) == -1) { | |
189 | + xlog(L_ERROR, "Could not bind socket: (%d) %m", | |
190 | + errno); | |
191 | + (void)close(fd); | |
192 | + return -1; | |
193 | + } | |
194 | + | |
195 | + if (nconf->nc_semantics == NC_TPI_COTS_ORD) | |
196 | + if (listen(fd, SOMAXCONN) == -1) { | |
197 | + xlog(L_ERROR, "Could not listen on socket: (%d) %m", | |
198 | + errno); | |
199 | + (void)close(fd); | |
200 | + return -1; | |
201 | + } | |
202 | + | |
203 | + return fd; | |
204 | +} | |
205 | + | |
206 | +/* | |
207 | + * The simple case is allowing the TI-RPC library to create a | |
208 | + * transport itself, given just the bind address and transport | |
209 | + * semantics. | |
210 | + * | |
211 | + * The port is chosen at random by the library; we don't know | |
212 | + * what it is. So the new xprt cannot be cached here. | |
213 | + * | |
214 | + * Returns the count of started listeners (one or zero). | |
215 | + */ | |
216 | static unsigned int | |
217 | -svc_create_nconf(const char *name, const rpcprog_t program, | |
218 | +svc_create_nconf_rand_port(const char *name, const rpcprog_t program, | |
219 | const rpcvers_t version, | |
220 | void (*dispatch)(struct svc_req *, SVCXPRT *), | |
221 | - const uint16_t port, struct netconfig *nconf) | |
222 | + struct netconfig *nconf) | |
223 | { | |
224 | struct t_bind bindaddr; | |
225 | struct addrinfo *ai; | |
226 | SVCXPRT *xprt; | |
227 | ||
228 | - ai = svc_create_bindaddr(nconf, port); | |
229 | + ai = svc_create_bindaddr(nconf, 0); | |
230 | if (ai == NULL) | |
231 | return 0; | |
232 | ||
233 | @@ -119,7 +272,7 @@ svc_create_nconf(const char *name, const rpcprog_t program, | |
234 | freeaddrinfo(ai); | |
235 | if (xprt == NULL) { | |
236 | xlog(D_GENERAL, "Failed to create listener xprt " | |
237 | - "(%s, %u, %s)", name, version, nconf->nc_netid); | |
238 | + "(%s, %u, %s)", name, version, nconf->nc_netid); | |
239 | return 0; | |
240 | } | |
241 | ||
242 | @@ -133,6 +286,81 @@ svc_create_nconf(const char *name, const rpcprog_t program, | |
243 | return 1; | |
244 | } | |
245 | ||
246 | +/* | |
247 | + * If a port is specified on the command line, that port value will be | |
248 | + * the same for all listeners created here. Create each listener socket | |
249 | + * in advance and set SO_REUSEADDR, rather than allowing the RPC library | |
250 | + * to create the listeners for us on a randomly chosen port (RPC_ANYFD). | |
251 | + * | |
252 | + * Also, to support multiple RPC versions on the same listener, register | |
253 | + * any new versions on the same transport that is already handling other | |
254 | + * versions on the same bindaddr and transport. To accomplish this, | |
255 | + * cache previously created xprts on a list, and check that list before | |
256 | + * creating a new socket for this [program, version]. | |
257 | + * | |
258 | + * Returns the count of started listeners (one or zero). | |
259 | + */ | |
260 | +static unsigned int | |
261 | +svc_create_nconf_fixed_port(const char *name, const rpcprog_t program, | |
262 | + const rpcvers_t version, | |
263 | + void (*dispatch)(struct svc_req *, SVCXPRT *), | |
264 | + const uint16_t port, struct netconfig *nconf) | |
265 | +{ | |
266 | + struct addrinfo *ai; | |
267 | + SVCXPRT *xprt; | |
268 | + | |
269 | + ai = svc_create_bindaddr(nconf, port); | |
270 | + if (ai == NULL) | |
271 | + return 0; | |
272 | + | |
273 | + xprt = svc_create_find_xprt(ai->ai_addr, nconf); | |
274 | + if (xprt == NULL) { | |
275 | + int fd; | |
276 | + | |
277 | + fd = svc_create_sock(ai->ai_addr, ai->ai_addrlen, nconf); | |
278 | + if (fd == -1) | |
279 | + goto out_free; | |
280 | + | |
281 | + xprt = svc_tli_create(fd, nconf, NULL, 0, 0); | |
282 | + if (xprt == NULL) { | |
283 | + xlog(D_GENERAL, "Failed to create listener xprt " | |
284 | + "(%s, %u, %s)", name, version, nconf->nc_netid); | |
285 | + (void)close(fd); | |
286 | + goto out_free; | |
287 | + } | |
288 | + } | |
289 | + | |
290 | + if (!svc_reg(xprt, program, version, dispatch, nconf)) { | |
291 | + /* svc_reg(3) destroys @xprt in this case */ | |
292 | + xlog(D_GENERAL, "Failed to register (%s, %u, %s)", | |
293 | + name, version, nconf->nc_netid); | |
294 | + goto out_free; | |
295 | + } | |
296 | + | |
297 | + svc_create_cache_xprt(xprt); | |
298 | + | |
299 | + freeaddrinfo(ai); | |
300 | + return 1; | |
301 | + | |
302 | +out_free: | |
303 | + freeaddrinfo(ai); | |
304 | + return 0; | |
305 | +} | |
306 | + | |
307 | +static unsigned int | |
308 | +svc_create_nconf(const char *name, const rpcprog_t program, | |
309 | + const rpcvers_t version, | |
310 | + void (*dispatch)(struct svc_req *, SVCXPRT *), | |
311 | + const uint16_t port, struct netconfig *nconf) | |
312 | +{ | |
313 | + if (port != 0) | |
314 | + return svc_create_nconf_fixed_port(name, program, | |
315 | + version, dispatch, port, nconf); | |
316 | + | |
317 | + return svc_create_nconf_rand_port(name, program, | |
318 | + version, dispatch, nconf); | |
319 | +} | |
320 | + | |
321 | /** | |
322 | * nfs_svc_create - start up RPC svc listeners | |
323 | * @name: C string containing name of new service |