]>
Commit | Line | Data |
---|---|---|
a02d6926 CL |
1 | /* |
2 | * Copyright 2009, Oracle. All rights reserved. | |
3 | * | |
4 | * Convert socket addresses to presentation addresses and universal | |
5 | * addresses, and vice versa. | |
6 | * | |
7 | * Universal addresses are introduced by RFC 1833 and further refined by | |
8 | * recent RFCs describing NFSv4. The universal address format is part | |
9 | * of the external (network) interface provided by rpcbind version 3 | |
10 | * and 4, and by NFSv4. Such an address is a string containing a | |
11 | * presentation format IP address followed by a port number in | |
12 | * "hibyte.lobyte" format. | |
13 | * | |
14 | * IPv6 addresses can also include a scope ID, typically denoted by | |
15 | * a '%' followed by a device name or a non-negative integer. Refer to | |
16 | * RFC 4291, Section 2.2 for details on IPv6 presentation formats. | |
17 | */ | |
18 | ||
19 | #include <net/ipv6.h> | |
20 | #include <linux/sunrpc/clnt.h> | |
21 | ||
22 | #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) | |
23 | ||
24 | static size_t rpc_ntop6_noscopeid(const struct sockaddr *sap, | |
25 | char *buf, const int buflen) | |
26 | { | |
27 | const struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sap; | |
28 | const struct in6_addr *addr = &sin6->sin6_addr; | |
29 | ||
30 | /* | |
31 | * RFC 4291, Section 2.2.2 | |
32 | * | |
33 | * Shorthanded ANY address | |
34 | */ | |
35 | if (ipv6_addr_any(addr)) | |
36 | return snprintf(buf, buflen, "::"); | |
37 | ||
38 | /* | |
39 | * RFC 4291, Section 2.2.2 | |
40 | * | |
41 | * Shorthanded loopback address | |
42 | */ | |
43 | if (ipv6_addr_loopback(addr)) | |
44 | return snprintf(buf, buflen, "::1"); | |
45 | ||
46 | /* | |
47 | * RFC 4291, Section 2.2.3 | |
48 | * | |
49 | * Special presentation address format for mapped v4 | |
50 | * addresses. | |
51 | */ | |
52 | if (ipv6_addr_v4mapped(addr)) | |
53 | return snprintf(buf, buflen, "::ffff:%pI4", | |
54 | &addr->s6_addr32[3]); | |
55 | ||
56 | /* | |
57 | * RFC 4291, Section 2.2.1 | |
58 | * | |
59 | * To keep the result as short as possible, especially | |
60 | * since we don't shorthand, we don't want leading zeros | |
61 | * in each halfword, so avoid %pI6. | |
62 | */ | |
63 | return snprintf(buf, buflen, "%x:%x:%x:%x:%x:%x:%x:%x", | |
64 | ntohs(addr->s6_addr16[0]), ntohs(addr->s6_addr16[1]), | |
65 | ntohs(addr->s6_addr16[2]), ntohs(addr->s6_addr16[3]), | |
66 | ntohs(addr->s6_addr16[4]), ntohs(addr->s6_addr16[5]), | |
67 | ntohs(addr->s6_addr16[6]), ntohs(addr->s6_addr16[7])); | |
68 | } | |
69 | ||
70 | static size_t rpc_ntop6(const struct sockaddr *sap, | |
71 | char *buf, const size_t buflen) | |
72 | { | |
73 | const struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sap; | |
74 | char scopebuf[IPV6_SCOPE_ID_LEN]; | |
75 | size_t len; | |
76 | int rc; | |
77 | ||
78 | len = rpc_ntop6_noscopeid(sap, buf, buflen); | |
79 | if (unlikely(len == 0)) | |
80 | return len; | |
81 | ||
82 | if (!(ipv6_addr_type(&sin6->sin6_addr) & IPV6_ADDR_LINKLOCAL) && | |
83 | !(ipv6_addr_type(&sin6->sin6_addr) & IPV6_ADDR_SITELOCAL)) | |
84 | return len; | |
85 | ||
86 | rc = snprintf(scopebuf, sizeof(scopebuf), "%c%u", | |
87 | IPV6_SCOPE_DELIMITER, sin6->sin6_scope_id); | |
88 | if (unlikely((size_t)rc > sizeof(scopebuf))) | |
89 | return 0; | |
90 | ||
91 | len += rc; | |
92 | if (unlikely(len > buflen)) | |
93 | return 0; | |
94 | ||
95 | strcat(buf, scopebuf); | |
96 | return len; | |
97 | } | |
98 | ||
99 | #else /* !(defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)) */ | |
100 | ||
101 | static size_t rpc_ntop6_noscopeid(const struct sockaddr *sap, | |
102 | char *buf, const int buflen) | |
103 | { | |
104 | return 0; | |
105 | } | |
106 | ||
107 | static size_t rpc_ntop6(const struct sockaddr *sap, | |
108 | char *buf, const size_t buflen) | |
109 | { | |
110 | return 0; | |
111 | } | |
112 | ||
113 | #endif /* !(defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)) */ | |
114 | ||
115 | static int rpc_ntop4(const struct sockaddr *sap, | |
116 | char *buf, const size_t buflen) | |
117 | { | |
118 | const struct sockaddr_in *sin = (struct sockaddr_in *)sap; | |
119 | ||
120 | return snprintf(buf, buflen, "%pI4", &sin->sin_addr); | |
121 | } | |
122 | ||
123 | /** | |
124 | * rpc_ntop - construct a presentation address in @buf | |
125 | * @sap: socket address | |
126 | * @buf: construction area | |
127 | * @buflen: size of @buf, in bytes | |
128 | * | |
129 | * Plants a %NUL-terminated string in @buf and returns the length | |
130 | * of the string, excluding the %NUL. Otherwise zero is returned. | |
131 | */ | |
132 | size_t rpc_ntop(const struct sockaddr *sap, char *buf, const size_t buflen) | |
133 | { | |
134 | switch (sap->sa_family) { | |
135 | case AF_INET: | |
136 | return rpc_ntop4(sap, buf, buflen); | |
137 | case AF_INET6: | |
138 | return rpc_ntop6(sap, buf, buflen); | |
139 | } | |
140 | ||
141 | return 0; | |
142 | } | |
143 | EXPORT_SYMBOL_GPL(rpc_ntop); | |
144 | ||
145 | static size_t rpc_pton4(const char *buf, const size_t buflen, | |
146 | struct sockaddr *sap, const size_t salen) | |
147 | { | |
148 | struct sockaddr_in *sin = (struct sockaddr_in *)sap; | |
149 | u8 *addr = (u8 *)&sin->sin_addr.s_addr; | |
150 | ||
151 | if (buflen > INET_ADDRSTRLEN || salen < sizeof(struct sockaddr_in)) | |
152 | return 0; | |
153 | ||
154 | memset(sap, 0, sizeof(struct sockaddr_in)); | |
155 | ||
156 | if (in4_pton(buf, buflen, addr, '\0', NULL) == 0) | |
157 | return 0; | |
158 | ||
159 | sin->sin_family = AF_INET; | |
160 | return sizeof(struct sockaddr_in);; | |
161 | } | |
162 | ||
163 | #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) | |
164 | static int rpc_parse_scope_id(const char *buf, const size_t buflen, | |
165 | const char *delim, struct sockaddr_in6 *sin6) | |
166 | { | |
167 | char *p; | |
168 | size_t len; | |
169 | ||
170 | if ((buf + buflen) == delim) | |
171 | return 1; | |
172 | ||
173 | if (*delim != IPV6_SCOPE_DELIMITER) | |
174 | return 0; | |
175 | ||
176 | if (!(ipv6_addr_type(&sin6->sin6_addr) & IPV6_ADDR_LINKLOCAL) && | |
177 | !(ipv6_addr_type(&sin6->sin6_addr) & IPV6_ADDR_SITELOCAL)) | |
178 | return 0; | |
179 | ||
180 | len = (buf + buflen) - delim - 1; | |
181 | p = kstrndup(delim + 1, len, GFP_KERNEL); | |
182 | if (p) { | |
183 | unsigned long scope_id = 0; | |
184 | struct net_device *dev; | |
185 | ||
186 | dev = dev_get_by_name(&init_net, p); | |
187 | if (dev != NULL) { | |
188 | scope_id = dev->ifindex; | |
189 | dev_put(dev); | |
190 | } else { | |
191 | if (strict_strtoul(p, 10, &scope_id) == 0) { | |
192 | kfree(p); | |
193 | return 0; | |
194 | } | |
195 | } | |
196 | ||
197 | kfree(p); | |
198 | ||
199 | sin6->sin6_scope_id = scope_id; | |
200 | return 1; | |
201 | } | |
202 | ||
203 | return 0; | |
204 | } | |
205 | ||
206 | static size_t rpc_pton6(const char *buf, const size_t buflen, | |
207 | struct sockaddr *sap, const size_t salen) | |
208 | { | |
209 | struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sap; | |
210 | u8 *addr = (u8 *)&sin6->sin6_addr.in6_u; | |
211 | const char *delim; | |
212 | ||
213 | if (buflen > (INET6_ADDRSTRLEN + IPV6_SCOPE_ID_LEN) || | |
214 | salen < sizeof(struct sockaddr_in6)) | |
215 | return 0; | |
216 | ||
217 | memset(sap, 0, sizeof(struct sockaddr_in6)); | |
218 | ||
219 | if (in6_pton(buf, buflen, addr, IPV6_SCOPE_DELIMITER, &delim) == 0) | |
220 | return 0; | |
221 | ||
222 | if (!rpc_parse_scope_id(buf, buflen, delim, sin6)) | |
223 | return 0; | |
224 | ||
225 | sin6->sin6_family = AF_INET6; | |
226 | return sizeof(struct sockaddr_in6); | |
227 | } | |
228 | #else | |
229 | static size_t rpc_pton6(const char *buf, const size_t buflen, | |
230 | struct sockaddr *sap, const size_t salen) | |
231 | { | |
232 | return 0; | |
233 | } | |
234 | #endif | |
235 | ||
236 | /** | |
237 | * rpc_pton - Construct a sockaddr in @sap | |
238 | * @buf: C string containing presentation format IP address | |
239 | * @buflen: length of presentation address in bytes | |
240 | * @sap: buffer into which to plant socket address | |
241 | * @salen: size of buffer in bytes | |
242 | * | |
243 | * Returns the size of the socket address if successful; otherwise | |
244 | * zero is returned. | |
245 | * | |
246 | * Plants a socket address in @sap and returns the size of the | |
247 | * socket address, if successful. Returns zero if an error | |
248 | * occurred. | |
249 | */ | |
250 | size_t rpc_pton(const char *buf, const size_t buflen, | |
251 | struct sockaddr *sap, const size_t salen) | |
252 | { | |
253 | unsigned int i; | |
254 | ||
255 | for (i = 0; i < buflen; i++) | |
256 | if (buf[i] == ':') | |
257 | return rpc_pton6(buf, buflen, sap, salen); | |
258 | return rpc_pton4(buf, buflen, sap, salen); | |
259 | } | |
260 | EXPORT_SYMBOL_GPL(rpc_pton); | |
261 | ||
262 | /** | |
263 | * rpc_sockaddr2uaddr - Construct a universal address string from @sap. | |
264 | * @sap: socket address | |
265 | * | |
266 | * Returns a %NUL-terminated string in dynamically allocated memory; | |
267 | * otherwise NULL is returned if an error occurred. Caller must | |
268 | * free the returned string. | |
269 | */ | |
270 | char *rpc_sockaddr2uaddr(const struct sockaddr *sap) | |
271 | { | |
272 | char portbuf[RPCBIND_MAXUADDRPLEN]; | |
273 | char addrbuf[RPCBIND_MAXUADDRLEN]; | |
274 | unsigned short port; | |
275 | ||
276 | switch (sap->sa_family) { | |
277 | case AF_INET: | |
278 | if (rpc_ntop4(sap, addrbuf, sizeof(addrbuf)) == 0) | |
279 | return NULL; | |
280 | port = ntohs(((struct sockaddr_in *)sap)->sin_port); | |
281 | break; | |
282 | case AF_INET6: | |
283 | if (rpc_ntop6_noscopeid(sap, addrbuf, sizeof(addrbuf)) == 0) | |
284 | return NULL; | |
285 | port = ntohs(((struct sockaddr_in6 *)sap)->sin6_port); | |
286 | break; | |
287 | default: | |
288 | return NULL; | |
289 | } | |
290 | ||
291 | if (snprintf(portbuf, sizeof(portbuf), | |
292 | ".%u.%u", port >> 8, port & 0xff) > (int)sizeof(portbuf)) | |
293 | return NULL; | |
294 | ||
295 | if (strlcat(addrbuf, portbuf, sizeof(addrbuf)) > sizeof(addrbuf)) | |
296 | return NULL; | |
297 | ||
298 | return kstrdup(addrbuf, GFP_KERNEL); | |
299 | } | |
300 | EXPORT_SYMBOL_GPL(rpc_sockaddr2uaddr); | |
301 | ||
302 | /** | |
303 | * rpc_uaddr2sockaddr - convert a universal address to a socket address. | |
304 | * @uaddr: C string containing universal address to convert | |
305 | * @uaddr_len: length of universal address string | |
306 | * @sap: buffer into which to plant socket address | |
307 | * @salen: size of buffer | |
308 | * | |
309 | * Returns the size of the socket address if successful; otherwise | |
310 | * zero is returned. | |
311 | */ | |
312 | size_t rpc_uaddr2sockaddr(const char *uaddr, const size_t uaddr_len, | |
313 | struct sockaddr *sap, const size_t salen) | |
314 | { | |
315 | char *c, buf[RPCBIND_MAXUADDRLEN]; | |
316 | unsigned long portlo, porthi; | |
317 | unsigned short port; | |
318 | ||
319 | if (uaddr_len > sizeof(buf)) | |
320 | return 0; | |
321 | ||
322 | memcpy(buf, uaddr, uaddr_len); | |
323 | ||
324 | buf[uaddr_len] = '\n'; | |
325 | buf[uaddr_len + 1] = '\0'; | |
326 | ||
327 | c = strrchr(buf, '.'); | |
328 | if (unlikely(c == NULL)) | |
329 | return 0; | |
330 | if (unlikely(strict_strtoul(c + 1, 10, &portlo) != 0)) | |
331 | return 0; | |
332 | if (unlikely(portlo > 255)) | |
333 | return 0; | |
334 | ||
335 | c[0] = '\n'; | |
336 | c[1] = '\0'; | |
337 | ||
338 | c = strrchr(buf, '.'); | |
339 | if (unlikely(c == NULL)) | |
340 | return 0; | |
341 | if (unlikely(strict_strtoul(c + 1, 10, &porthi) != 0)) | |
342 | return 0; | |
343 | if (unlikely(porthi > 255)) | |
344 | return 0; | |
345 | ||
346 | port = (unsigned short)((porthi << 8) | portlo); | |
347 | ||
348 | c[0] = '\0'; | |
349 | ||
350 | if (rpc_pton(buf, strlen(buf), sap, salen) == 0) | |
351 | return 0; | |
352 | ||
353 | switch (sap->sa_family) { | |
354 | case AF_INET: | |
355 | ((struct sockaddr_in *)sap)->sin_port = htons(port); | |
356 | return sizeof(struct sockaddr_in); | |
357 | case AF_INET6: | |
358 | ((struct sockaddr_in6 *)sap)->sin6_port = htons(port); | |
359 | return sizeof(struct sockaddr_in6); | |
360 | } | |
361 | ||
362 | return 0; | |
363 | } | |
364 | EXPORT_SYMBOL_GPL(rpc_uaddr2sockaddr); |