summaryrefslogtreecommitdiffstats
path: root/nsprpub/pr/src/io/pripv6.c
blob: 1c299652e6cf5ac62c943bdbac76ffd497afc3e0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
** File:        pripv6.c
** Description: Support for various functions unique to IPv6
*/
#include "primpl.h"
#include <string.h>

#if !defined(_PR_INET6) || defined(_PR_INET6_PROBE)

static PRIOMethods ipv6_to_v4_tcpMethods;
static PRIOMethods ipv6_to_v4_udpMethods;
static PRDescIdentity _pr_ipv6_to_ipv4_id;
extern PRBool IsValidNetAddr(const PRNetAddr *addr);
extern const PRIPv6Addr _pr_in6addr_any;
extern const PRIPv6Addr _pr_in6addr_loopback;

/*
 * convert an IPv4-mapped IPv6 addr to an IPv4 addr
 */
static void _PR_ConvertToIpv4NetAddr(const PRNetAddr *src_v6addr,
                                     PRNetAddr *dst_v4addr)
{
    const PRUint8 *srcp;

    PR_ASSERT(PR_AF_INET6 == src_v6addr->ipv6.family);

    if (PR_IsNetAddrType(src_v6addr, PR_IpAddrV4Mapped)) {
        srcp = src_v6addr->ipv6.ip.pr_s6_addr;
        memcpy((char *) &dst_v4addr->inet.ip, srcp + 12, 4);
    } else if (PR_IsNetAddrType(src_v6addr, PR_IpAddrAny)) {
        dst_v4addr->inet.ip = htonl(INADDR_ANY);
    } else if (PR_IsNetAddrType(src_v6addr, PR_IpAddrLoopback)) {
        dst_v4addr->inet.ip = htonl(INADDR_LOOPBACK);
    }
    dst_v4addr->inet.family = PR_AF_INET;
    dst_v4addr->inet.port = src_v6addr->ipv6.port;
}

/*
 * convert an IPv4 addr to an IPv4-mapped IPv6 addr
 */
static void _PR_ConvertToIpv6NetAddr(const PRNetAddr *src_v4addr,
                                     PRNetAddr *dst_v6addr)
{
    PRUint8 *dstp;

    PR_ASSERT(PR_AF_INET == src_v4addr->inet.family);
    dst_v6addr->ipv6.family = PR_AF_INET6;
    dst_v6addr->ipv6.port = src_v4addr->inet.port;

    if (htonl(INADDR_ANY) == src_v4addr->inet.ip) {
        dst_v6addr->ipv6.ip = _pr_in6addr_any;
    } else {
        dstp = dst_v6addr->ipv6.ip.pr_s6_addr;
        memset(dstp, 0, 10);
        memset(dstp + 10, 0xff, 2);
        memcpy(dstp + 12,(char *) &src_v4addr->inet.ip, 4);
    }
}

static PRStatus PR_CALLBACK Ipv6ToIpv4SocketBind(PRFileDesc *fd,
        const PRNetAddr *addr)
{
    PRNetAddr tmp_ipv4addr;
    const PRNetAddr *tmp_addrp;
    PRFileDesc *lo = fd->lower;

    if (PR_AF_INET6 != addr->raw.family) {
        PR_SetError(PR_ADDRESS_NOT_SUPPORTED_ERROR, 0);
        return PR_FAILURE;
    }
    if (PR_IsNetAddrType(addr, PR_IpAddrV4Mapped) ||
        PR_IsNetAddrType(addr, PR_IpAddrAny)) {
        _PR_ConvertToIpv4NetAddr(addr, &tmp_ipv4addr);
        tmp_addrp = &tmp_ipv4addr;
    } else {
        PR_SetError(PR_NETWORK_UNREACHABLE_ERROR, 0);
        return PR_FAILURE;
    }
    return((lo->methods->bind)(lo,tmp_addrp));
}

static PRStatus PR_CALLBACK Ipv6ToIpv4SocketConnect(
    PRFileDesc *fd, const PRNetAddr *addr, PRIntervalTime timeout)
{
    PRNetAddr tmp_ipv4addr;
    const PRNetAddr *tmp_addrp;

    if (PR_AF_INET6 != addr->raw.family) {
        PR_SetError(PR_ADDRESS_NOT_SUPPORTED_ERROR, 0);
        return PR_FAILURE;
    }
    if (PR_IsNetAddrType(addr, PR_IpAddrV4Mapped) ||
        PR_IsNetAddrType(addr, PR_IpAddrLoopback)) {
        _PR_ConvertToIpv4NetAddr(addr, &tmp_ipv4addr);
        tmp_addrp = &tmp_ipv4addr;
    } else {
        PR_SetError(PR_NETWORK_UNREACHABLE_ERROR, 0);
        return PR_FAILURE;
    }
    return (fd->lower->methods->connect)(fd->lower, tmp_addrp, timeout);
}

static PRInt32 PR_CALLBACK Ipv6ToIpv4SocketSendTo(
    PRFileDesc *fd, const void *buf, PRInt32 amount,
    PRIntn flags, const PRNetAddr *addr, PRIntervalTime timeout)
{
    PRNetAddr tmp_ipv4addr;
    const PRNetAddr *tmp_addrp;

    if (PR_AF_INET6 != addr->raw.family) {
        PR_SetError(PR_ADDRESS_NOT_SUPPORTED_ERROR, 0);
        return PR_FAILURE;
    }
    if (PR_IsNetAddrType(addr, PR_IpAddrV4Mapped) ||
        PR_IsNetAddrType(addr, PR_IpAddrLoopback)) {
        _PR_ConvertToIpv4NetAddr(addr, &tmp_ipv4addr);
        tmp_addrp = &tmp_ipv4addr;
    } else {
        PR_SetError(PR_NETWORK_UNREACHABLE_ERROR, 0);
        return PR_FAILURE;
    }
    return (fd->lower->methods->sendto)(
               fd->lower, buf, amount, flags, tmp_addrp, timeout);
}

static PRFileDesc* PR_CALLBACK Ipv6ToIpv4SocketAccept (
    PRFileDesc *fd, PRNetAddr *addr, PRIntervalTime timeout)
{
    PRStatus rv;
    PRFileDesc *newfd;
    PRFileDesc *newstack;
    PRNetAddr tmp_ipv4addr;
    PRNetAddr *addrlower = NULL;

    PR_ASSERT(fd != NULL);
    PR_ASSERT(fd->lower != NULL);

    newstack = PR_NEW(PRFileDesc);
    if (NULL == newstack)
    {
        PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
        return NULL;
    }
    *newstack = *fd;  /* make a copy of the accepting layer */

    if (addr) {
        addrlower = &tmp_ipv4addr;
    }
    newfd = (fd->lower->methods->accept)(fd->lower, addrlower, timeout);
    if (NULL == newfd)
    {
        PR_DELETE(newstack);
        return NULL;
    }
    if (addr) {
        _PR_ConvertToIpv6NetAddr(&tmp_ipv4addr, addr);
    }

    rv = PR_PushIOLayer(newfd, PR_TOP_IO_LAYER, newstack);
    PR_ASSERT(PR_SUCCESS == rv);
    return newfd;  /* that's it */
}

static PRInt32 PR_CALLBACK Ipv6ToIpv4SocketAcceptRead(PRFileDesc *sd,
        PRFileDesc **nd, PRNetAddr **ipv6_raddr, void *buf, PRInt32 amount,
        PRIntervalTime timeout)
{
    PRInt32 nbytes;
    PRStatus rv;
    PRNetAddr tmp_ipv4addr;
    PRFileDesc *newstack;

    PR_ASSERT(sd != NULL);
    PR_ASSERT(sd->lower != NULL);

    newstack = PR_NEW(PRFileDesc);
    if (NULL == newstack)
    {
        PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
        return -1;
    }
    *newstack = *sd;  /* make a copy of the accepting layer */

    nbytes = sd->lower->methods->acceptread(
                 sd->lower, nd, ipv6_raddr, buf, amount, timeout);
    if (-1 == nbytes)
    {
        PR_DELETE(newstack);
        return nbytes;
    }
    tmp_ipv4addr = **ipv6_raddr;    /* copy */
    _PR_ConvertToIpv6NetAddr(&tmp_ipv4addr, *ipv6_raddr);

    /* this PR_PushIOLayer call cannot fail */
    rv = PR_PushIOLayer(*nd, PR_TOP_IO_LAYER, newstack);
    PR_ASSERT(PR_SUCCESS == rv);
    return nbytes;
}

static PRStatus PR_CALLBACK Ipv6ToIpv4SocketGetName(PRFileDesc *fd,
        PRNetAddr *ipv6addr)
{
    PRStatus result;
    PRNetAddr tmp_ipv4addr;

    result = (fd->lower->methods->getsockname)(fd->lower, &tmp_ipv4addr);
    if (PR_SUCCESS == result) {
        _PR_ConvertToIpv6NetAddr(&tmp_ipv4addr, ipv6addr);
        PR_ASSERT(IsValidNetAddr(ipv6addr) == PR_TRUE);
    }
    return result;
}

static PRStatus PR_CALLBACK Ipv6ToIpv4SocketGetPeerName(PRFileDesc *fd,
        PRNetAddr *ipv6addr)
{
    PRStatus result;
    PRNetAddr tmp_ipv4addr;

    result = (fd->lower->methods->getpeername)(fd->lower, &tmp_ipv4addr);
    if (PR_SUCCESS == result) {
        _PR_ConvertToIpv6NetAddr(&tmp_ipv4addr, ipv6addr);
        PR_ASSERT(IsValidNetAddr(ipv6addr) == PR_TRUE);
    }
    return result;
}

static PRInt32 PR_CALLBACK Ipv6ToIpv4SocketRecvFrom(PRFileDesc *fd, void *buf,
        PRInt32 amount, PRIntn flags, PRNetAddr *ipv6addr,
        PRIntervalTime timeout)
{
    PRNetAddr tmp_ipv4addr;
    PRInt32 result;

    result = (fd->lower->methods->recvfrom)(
                 fd->lower, buf, amount, flags, &tmp_ipv4addr, timeout);
    if (-1 != result) {
        _PR_ConvertToIpv6NetAddr(&tmp_ipv4addr, ipv6addr);
        PR_ASSERT(IsValidNetAddr(ipv6addr) == PR_TRUE);
    }
    return result;
}

#if defined(_PR_INET6_PROBE)
static PRBool ipv6_is_present;
PR_EXTERN(PRBool) _pr_test_ipv6_socket(void);

#if !defined(_PR_INET6) && defined(_PR_HAVE_GETIPNODEBYNAME)
extern PRStatus _pr_find_getipnodebyname(void);
#endif

#if !defined(_PR_INET6) && defined(_PR_HAVE_GETADDRINFO)
extern PRStatus _pr_find_getaddrinfo(void);
#endif

static PRBool
_pr_probe_ipv6_presence(void)
{
#if !defined(_PR_INET6) && defined(_PR_HAVE_GETIPNODEBYNAME)
    if (_pr_find_getipnodebyname() != PR_SUCCESS) {
        return PR_FALSE;
    }
#endif

#if !defined(_PR_INET6) && defined(_PR_HAVE_GETADDRINFO)
    if (_pr_find_getaddrinfo() != PR_SUCCESS) {
        return PR_FALSE;
    }
#endif

    return _pr_test_ipv6_socket();
}
#endif  /* _PR_INET6_PROBE */

static PRCallOnceType _pr_init_ipv6_once;

static PRStatus PR_CALLBACK _pr_init_ipv6(void)
{
    const PRIOMethods *stubMethods;

#if defined(_PR_INET6_PROBE)
    ipv6_is_present = _pr_probe_ipv6_presence();
    if (ipv6_is_present) {
        return PR_SUCCESS;
    }
#endif

    _pr_ipv6_to_ipv4_id = PR_GetUniqueIdentity("Ipv6_to_Ipv4 layer");
    PR_ASSERT(PR_INVALID_IO_LAYER != _pr_ipv6_to_ipv4_id);

    stubMethods = PR_GetDefaultIOMethods();

    ipv6_to_v4_tcpMethods = *stubMethods;  /* first get the entire batch */
    /* then override the ones we care about */
    ipv6_to_v4_tcpMethods.connect = Ipv6ToIpv4SocketConnect;
    ipv6_to_v4_tcpMethods.bind = Ipv6ToIpv4SocketBind;
    ipv6_to_v4_tcpMethods.accept = Ipv6ToIpv4SocketAccept;
    ipv6_to_v4_tcpMethods.acceptread = Ipv6ToIpv4SocketAcceptRead;
    ipv6_to_v4_tcpMethods.getsockname = Ipv6ToIpv4SocketGetName;
    ipv6_to_v4_tcpMethods.getpeername = Ipv6ToIpv4SocketGetPeerName;
    /*
        ipv6_to_v4_tcpMethods.getsocketoption = Ipv6ToIpv4GetSocketOption;
        ipv6_to_v4_tcpMethods.setsocketoption = Ipv6ToIpv4SetSocketOption;
    */
    ipv6_to_v4_udpMethods = *stubMethods;  /* first get the entire batch */
    /* then override the ones we care about */
    ipv6_to_v4_udpMethods.connect = Ipv6ToIpv4SocketConnect;
    ipv6_to_v4_udpMethods.bind = Ipv6ToIpv4SocketBind;
    ipv6_to_v4_udpMethods.sendto = Ipv6ToIpv4SocketSendTo;
    ipv6_to_v4_udpMethods.recvfrom = Ipv6ToIpv4SocketRecvFrom;
    ipv6_to_v4_udpMethods.getsockname = Ipv6ToIpv4SocketGetName;
    ipv6_to_v4_udpMethods.getpeername = Ipv6ToIpv4SocketGetPeerName;
    /*
        ipv6_to_v4_udpMethods.getsocketoption = Ipv6ToIpv4GetSocketOption;
        ipv6_to_v4_udpMethods.setsocketoption = Ipv6ToIpv4SetSocketOption;
    */
    return PR_SUCCESS;
}

#if defined(_PR_INET6_PROBE)
PRBool _pr_ipv6_is_present(void)
{
    if (PR_CallOnce(&_pr_init_ipv6_once, _pr_init_ipv6) != PR_SUCCESS) {
        return PR_FALSE;
    }
    return ipv6_is_present;
}
#endif

PR_IMPLEMENT(PRStatus) _pr_push_ipv6toipv4_layer(PRFileDesc *fd)
{
    PRFileDesc *ipv6_fd = NULL;

    if (PR_CallOnce(&_pr_init_ipv6_once, _pr_init_ipv6) != PR_SUCCESS) {
        return PR_FAILURE;
    }

    /*
     * For platforms with no support for IPv6
     * create layered socket for IPv4-mapped IPv6 addresses
     */
    if (fd->methods->file_type == PR_DESC_SOCKET_TCP)
        ipv6_fd = PR_CreateIOLayerStub(_pr_ipv6_to_ipv4_id,
                                       &ipv6_to_v4_tcpMethods);
    else
        ipv6_fd = PR_CreateIOLayerStub(_pr_ipv6_to_ipv4_id,
                                       &ipv6_to_v4_udpMethods);
    if (NULL == ipv6_fd) {
        goto errorExit;
    }
    ipv6_fd->secret = NULL;

    if (PR_PushIOLayer(fd, PR_TOP_IO_LAYER, ipv6_fd) == PR_FAILURE) {
        goto errorExit;
    }

    return PR_SUCCESS;
errorExit:

    if (ipv6_fd) {
        ipv6_fd->dtor(ipv6_fd);
    }
    return PR_FAILURE;
}

#endif /* !defined(_PR_INET6) || defined(_PR_INET6_PROBE) */