Line data Source code
1 : /*
2 : *
3 : * Copyright (c) 2020 Project CHIP Authors
4 : * Copyright (c) 2019 Google LLC.
5 : * Copyright (c) 2013-2018 Nest Labs, Inc.
6 : *
7 : * Licensed under the Apache License, Version 2.0 (the "License");
8 : * you may not use this file except in compliance with the License.
9 : * You may obtain a copy of the License at
10 : *
11 : * http://www.apache.org/licenses/LICENSE-2.0
12 : *
13 : * Unless required by applicable law or agreed to in writing, software
14 : * distributed under the License is distributed on an "AS IS" BASIS,
15 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 : * See the License for the specific language governing permissions and
17 : * limitations under the License.
18 : */
19 :
20 : /**
21 : * @file
22 : * This file implements the class <tt>Inet::IPAddress</tt> and
23 : * related enumerated constants. The CHIP Inet Layer uses objects
24 : * of this class to represent Internet protocol addresses of both
25 : * IPv4 and IPv6 address families. (IPv4 addresses are stored
26 : * internally as IPv4-Mapped IPv6 addresses.)
27 : *
28 : */
29 :
30 : #include <inet/IPAddress.h>
31 :
32 : #include <inet/InetError.h>
33 : #include <lib/core/CHIPEncoding.h>
34 : #include <lib/support/CodeUtils.h>
35 :
36 : #include "arpa-inet-compatibility.h"
37 :
38 : #include <stdint.h>
39 : #include <string.h>
40 :
41 : namespace chip {
42 : namespace Inet {
43 :
44 : IPAddress IPAddress::Any;
45 :
46 13046 : bool IPAddress::operator==(const IPAddress & other) const
47 : {
48 13046 : return Addr[0] == other.Addr[0] && Addr[1] == other.Addr[1] && Addr[2] == other.Addr[2] && Addr[3] == other.Addr[3];
49 : }
50 :
51 3692 : bool IPAddress::operator!=(const IPAddress & other) const
52 : {
53 3692 : return Addr[0] != other.Addr[0] || Addr[1] != other.Addr[1] || Addr[2] != other.Addr[2] || Addr[3] != other.Addr[3];
54 : }
55 :
56 : #if CHIP_SYSTEM_CONFIG_USE_LWIP && !CHIP_SYSTEM_CONFIG_USE_OPEN_THREAD_ENDPOINT
57 :
58 : IPAddress::IPAddress(const ip6_addr_t & ipv6Addr)
59 : {
60 : static_assert(sizeof(ipv6Addr.addr) == sizeof(Addr), "ip6_addr_t size mismatch");
61 : memcpy(Addr, &ipv6Addr.addr, sizeof(ipv6Addr.addr));
62 : }
63 :
64 : #if INET_CONFIG_ENABLE_IPV4 || LWIP_IPV4
65 :
66 : IPAddress::IPAddress(const ip4_addr_t & ipv4Addr)
67 : {
68 : Addr[0] = 0;
69 : Addr[1] = 0;
70 : Addr[2] = htonl(0xFFFF);
71 : Addr[3] = ipv4Addr.addr;
72 : }
73 :
74 : IPAddress::IPAddress(const ip_addr_t & addr)
75 : {
76 : switch (IP_GET_TYPE(&addr))
77 : {
78 : #if INET_CONFIG_ENABLE_IPV4
79 : case IPADDR_TYPE_V4:
80 : *this = IPAddress(*ip_2_ip4(&addr));
81 : break;
82 : #endif // INET_CONFIG_ENABLE_IPV4
83 :
84 : case IPADDR_TYPE_V6:
85 : *this = IPAddress(*ip_2_ip6(&addr));
86 : break;
87 :
88 : default:
89 : *this = Any;
90 : break;
91 : }
92 : }
93 :
94 : #endif // INET_CONFIG_ENABLE_IPV4 || LWIP_IPV4
95 :
96 : #if INET_CONFIG_ENABLE_IPV4
97 :
98 : ip4_addr_t IPAddress::ToIPv4() const
99 : {
100 : ip4_addr_t ipAddr;
101 : memcpy(&ipAddr, &Addr[3], sizeof(ipAddr));
102 : return ipAddr;
103 : }
104 :
105 : #endif // INET_CONFIG_ENABLE_IPV4
106 :
107 : ip_addr_t IPAddress::ToLwIPAddr() const
108 : {
109 : ip_addr_t ret;
110 :
111 : switch (Type())
112 : {
113 : #if INET_CONFIG_ENABLE_IPV4
114 : case IPAddressType::kIPv4:
115 : ip_addr_copy_from_ip4(ret, IPAddress::ToIPv4());
116 : break;
117 : #endif // INET_CONFIG_ENABLE_IPV4
118 :
119 : case IPAddressType::kIPv6:
120 : ip_addr_copy_from_ip6(ret, IPAddress::ToIPv6());
121 : break;
122 :
123 : default:
124 : ret = *IP6_ADDR_ANY;
125 : break;
126 : }
127 :
128 : return ret;
129 : }
130 :
131 : CHIP_ERROR IPAddress::ToLwIPAddr(IPAddressType addressType, ip_addr_t & outAddress) const
132 : {
133 : VerifyOrReturnError(addressType != IPAddressType::kUnknown, CHIP_ERROR_INVALID_ARGUMENT);
134 :
135 : switch (Type())
136 : {
137 : #if INET_CONFIG_ENABLE_IPV4
138 : case IPAddressType::kIPv4:
139 : ip_addr_copy_from_ip4(outAddress, IPAddress::ToIPv4());
140 : return (addressType == IPAddressType::kIPv6) ? INET_ERROR_WRONG_ADDRESS_TYPE : CHIP_NO_ERROR;
141 : #endif // INET_CONFIG_ENABLE_IPV4
142 :
143 : case IPAddressType::kIPv6:
144 : ip_addr_copy_from_ip6(outAddress, IPAddress::ToIPv6());
145 : #if INET_CONFIG_ENABLE_IPV4
146 : return (addressType == IPAddressType::kIPv4) ? INET_ERROR_WRONG_ADDRESS_TYPE : CHIP_NO_ERROR;
147 : #else
148 : return CHIP_NO_ERROR;
149 : #endif // INET_CONFIG_ENABLE_IPV4
150 :
151 : case IPAddressType::kAny:
152 : #if INET_CONFIG_ENABLE_IPV4
153 : if (addressType == IPAddressType::kIPv4)
154 : {
155 : outAddress = *IP4_ADDR_ANY;
156 : return CHIP_NO_ERROR;
157 : }
158 : #endif // INET_CONFIG_ENABLE_IPV4
159 : outAddress = *IP6_ADDR_ANY;
160 : return CHIP_NO_ERROR;
161 :
162 : default:
163 : return INET_ERROR_WRONG_ADDRESS_TYPE;
164 : }
165 : }
166 :
167 : lwip_ip_addr_type IPAddress::ToLwIPAddrType(IPAddressType typ)
168 : {
169 : lwip_ip_addr_type ret;
170 :
171 : switch (typ)
172 : {
173 : #if INET_CONFIG_ENABLE_IPV4
174 : case IPAddressType::kIPv4:
175 : ret = IPADDR_TYPE_V4;
176 : break;
177 : #endif // INET_CONFIG_ENABLE_IPV4
178 :
179 : case IPAddressType::kIPv6:
180 : ret = IPADDR_TYPE_V6;
181 : break;
182 :
183 : default:
184 : ret = IPADDR_TYPE_ANY;
185 : break;
186 : }
187 :
188 : return ret;
189 : }
190 :
191 : ip6_addr_t IPAddress::ToIPv6() const
192 : {
193 : ip6_addr_t ipAddr = {};
194 : static_assert(sizeof(ipAddr.addr) == sizeof(Addr), "ip6_addr_t size mismatch");
195 : memcpy(&ipAddr.addr, Addr, sizeof(ipAddr.addr));
196 : return ipAddr;
197 : }
198 :
199 : #endif // CHIP_SYSTEM_CONFIG_USE_LWIP
200 :
201 : #if CHIP_SYSTEM_CONFIG_USE_SOCKETS || CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK
202 :
203 : #if INET_CONFIG_ENABLE_IPV4
204 867 : IPAddress::IPAddress(const struct in_addr & ipv4Addr)
205 : {
206 867 : Addr[0] = 0;
207 867 : Addr[1] = 0;
208 867 : Addr[2] = htonl(0xFFFF);
209 867 : Addr[3] = ipv4Addr.s_addr;
210 867 : }
211 : #endif // INET_CONFIG_ENABLE_IPV4
212 :
213 1139 : IPAddress::IPAddress(const struct in6_addr & ipv6Addr)
214 : {
215 : static_assert(sizeof(*this) == sizeof(ipv6Addr), "in6_addr size mismatch");
216 1139 : memcpy(Addr, &ipv6Addr, sizeof(ipv6Addr));
217 1139 : }
218 :
219 : #if INET_CONFIG_ENABLE_IPV4
220 168 : struct in_addr IPAddress::ToIPv4() const
221 : {
222 : struct in_addr ipv4Addr;
223 168 : ipv4Addr.s_addr = Addr[3];
224 168 : return ipv4Addr;
225 : }
226 : #endif // INET_CONFIG_ENABLE_IPV4
227 :
228 161 : struct in6_addr IPAddress::ToIPv6() const
229 : {
230 : in6_addr ipAddr;
231 : static_assert(sizeof(ipAddr) == sizeof(Addr), "in6_addr size mismatch");
232 161 : memcpy(&ipAddr, Addr, sizeof(ipAddr));
233 161 : return ipAddr;
234 : }
235 :
236 890 : CHIP_ERROR IPAddress::GetIPAddressFromSockAddr(const SockAddrWithoutStorage & sockaddr, IPAddress & outIPAddress)
237 : {
238 : #if INET_CONFIG_ENABLE_IPV4
239 890 : if (sockaddr.any.sa_family == AF_INET)
240 : {
241 484 : outIPAddress = FromSockAddr(sockaddr.in);
242 484 : return CHIP_NO_ERROR;
243 : }
244 : #endif // INET_CONFIG_ENABLE_IPV4
245 406 : if (sockaddr.any.sa_family == AF_INET6)
246 : {
247 406 : outIPAddress = FromSockAddr(sockaddr.in6);
248 406 : return CHIP_NO_ERROR;
249 : }
250 0 : return INET_ERROR_WRONG_ADDRESS_TYPE;
251 : }
252 :
253 : #endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS || CHIP_SYSTEM_CONFIG_USE_NETWORK_FRAMEWORK
254 :
255 : #if CHIP_SYSTEM_CONFIG_USE_OPEN_THREAD_ENDPOINT
256 : IPAddress::IPAddress(const otIp6Address & ipv6Addr)
257 : {
258 : static_assert(sizeof(ipv6Addr.mFields.m32) == sizeof(Addr), "otIp6Address size mismatch");
259 : memcpy(Addr, ipv6Addr.mFields.m32, sizeof(Addr));
260 : }
261 : otIp6Address IPAddress::ToIPv6() const
262 : {
263 : otIp6Address otAddr;
264 : static_assert(sizeof(otAddr.mFields.m32) == sizeof(Addr), "otIp6Address size mismatch");
265 : memcpy(otAddr.mFields.m32, Addr, sizeof(otAddr.mFields.m32));
266 : return otAddr;
267 : }
268 :
269 : IPAddress IPAddress::FromOtAddr(const otIp6Address & address)
270 : {
271 : IPAddress addr;
272 : static_assert(sizeof(address.mFields.m32) == sizeof(addr), "otIp6Address size mismatch");
273 : memcpy(addr.Addr, address.mFields.m32, sizeof(addr.Addr));
274 : return addr;
275 : }
276 : #endif // CHIP_SYSTEM_CONFIG_USE_OPEN_THREAD_ENDPOINT
277 :
278 : // Is address an IPv4 address encoded in IPv6 format?
279 21132 : bool IPAddress::IsIPv4() const
280 : {
281 21132 : return Addr[0] == 0 && Addr[1] == 0 && Addr[2] == htonl(0xFFFF);
282 : }
283 :
284 : // Is address a IPv4 multicast address?
285 123 : bool IPAddress::IsIPv4Multicast() const
286 : {
287 123 : return (IsIPv4() && ((ntohl(Addr[3]) & 0xF0000000U) == 0xE0000000U));
288 : }
289 :
290 : // Is address the IPv4 broadcast address?
291 55 : bool IPAddress::IsIPv4Broadcast() const
292 : {
293 55 : return (IsIPv4() && (Addr[3] == 0xFFFFFFFFU));
294 : }
295 :
296 : // Is address an IPv4 or IPv6 multicast address?
297 83 : bool IPAddress::IsMulticast() const
298 : {
299 83 : return (IsIPv6Multicast() || IsIPv4Multicast());
300 : }
301 :
302 487 : bool IPAddress::IsIPv6() const
303 : {
304 487 : return *this != Any && !IsIPv4();
305 : }
306 :
307 : // Is address an IPv6 multicast address?
308 140 : bool IPAddress::IsIPv6Multicast() const
309 : {
310 140 : return (ntohl(Addr[0]) & 0xFF000000U) == 0xFF000000U;
311 : }
312 :
313 : // Is address an IPv6 Global Unicast Address?
314 98 : bool IPAddress::IsIPv6GlobalUnicast() const
315 : {
316 98 : return (ntohl(Addr[0]) & 0xE0000000U) == 0x20000000U;
317 : }
318 :
319 : // Is address an IPv6 Unique Local Address?
320 314 : bool IPAddress::IsIPv6ULA() const
321 : {
322 314 : return (ntohl(Addr[0]) & 0xFE000000U) == 0xFC000000U;
323 : }
324 :
325 : // Is address an IPv6 Link-local Address?
326 10246 : bool IPAddress::IsIPv6LinkLocal() const
327 : {
328 10246 : return (Addr[0] == htonl(0xFE800000U) && Addr[1] == 0);
329 : }
330 :
331 : // Extract the interface id from a IPv6 ULA address. Returns 0 if the address
332 : // is not a ULA.
333 55 : uint64_t IPAddress::InterfaceId() const
334 : {
335 55 : if (IsIPv6ULA())
336 3 : return ((static_cast<uint64_t>(ntohl(Addr[2]))) << 32) | (static_cast<uint64_t>(ntohl(Addr[3])));
337 52 : return 0;
338 : }
339 :
340 : // Extract the subnet id from a IPv6 ULA address. Returns 0 if the address
341 : // is not a ULA.
342 55 : uint16_t IPAddress::Subnet() const
343 : {
344 55 : if (IsIPv6ULA())
345 3 : return static_cast<uint16_t>(ntohl(Addr[1]));
346 52 : return 0;
347 : }
348 :
349 : // Extract the global id from a IPv6 ULA address. Returns 0 if the address
350 : // is not a ULA.
351 55 : uint64_t IPAddress::GlobalId() const
352 : {
353 55 : if (IsIPv6ULA())
354 3 : return ((static_cast<uint64_t>(ntohl(Addr[0]) & 0xFFFFFF)) << 16) |
355 3 : (static_cast<uint64_t>(ntohl(Addr[1])) & 0xFFFF0000) >> 16;
356 52 : return 0;
357 : }
358 :
359 757 : IPAddressType IPAddress::Type() const
360 : {
361 757 : if (Addr[0] == 0 && Addr[1] == 0 && Addr[2] == 0 && Addr[3] == 0)
362 6 : return IPAddressType::kAny;
363 : #if INET_CONFIG_ENABLE_IPV4
364 751 : if (Addr[0] == 0 && Addr[1] == 0 && Addr[2] == htonl(0xFFFF))
365 421 : return IPAddressType::kIPv4;
366 : #endif // INET_CONFIG_ENABLE_IPV4
367 330 : return IPAddressType::kIPv6;
368 : }
369 :
370 : // Encode IPAddress to buffer in network byte order. Buffer must have at least 128 bits of available space.
371 : // Decoder must infer IP address type from context.
372 111 : void IPAddress::WriteAddress(uint8_t *& p) const
373 : {
374 : // Since each of the 32bit values in the Addr array is in network byte order, a simple
375 : // memcpy of the entire array is sufficient while copying the address.
376 :
377 111 : memcpy(p, &Addr[0], NL_INET_IPV6_ADDR_LEN_IN_BYTES);
378 :
379 111 : p += NL_INET_IPV6_ADDR_LEN_IN_BYTES;
380 111 : }
381 :
382 : // Decode IPAddress from buffer in network byte order. Must infer IP address type from context.
383 126 : void IPAddress::ReadAddress(const uint8_t *& p, IPAddress & output)
384 : {
385 : // Since we want to store the address in the output array in network byte order, a simple
386 : // memcpy of the entire array is used to retrieve from the buffer.
387 :
388 126 : memcpy(&output.Addr[0], p, NL_INET_IPV6_ADDR_LEN_IN_BYTES);
389 :
390 126 : p += NL_INET_IPV6_ADDR_LEN_IN_BYTES;
391 126 : }
392 :
393 : // Construct an IPv6 unique local address.
394 55 : IPAddress IPAddress::MakeULA(uint64_t globalId, uint16_t subnet, uint64_t interfaceId)
395 : {
396 : IPAddress addr;
397 :
398 55 : addr.Addr[0] = 0xFD000000 | static_cast<uint32_t>((globalId & 0xFFFFFF0000ULL) >> 16);
399 55 : addr.Addr[0] = htonl(addr.Addr[0]);
400 :
401 55 : addr.Addr[1] = static_cast<uint32_t>((globalId & 0x000000FFFFULL) << 16) | subnet;
402 55 : addr.Addr[1] = htonl(addr.Addr[1]);
403 :
404 55 : addr.Addr[2] = htonl(static_cast<uint32_t>(interfaceId >> 32));
405 55 : addr.Addr[3] = htonl(static_cast<uint32_t>(interfaceId));
406 :
407 55 : return addr;
408 : }
409 :
410 55 : IPAddress IPAddress::MakeLLA(uint64_t interfaceId)
411 : {
412 : IPAddress addr;
413 :
414 55 : addr.Addr[0] = htonl(0xFE800000);
415 55 : addr.Addr[1] = 0;
416 :
417 55 : addr.Addr[2] = htonl(static_cast<uint32_t>(interfaceId >> 32));
418 55 : addr.Addr[3] = htonl(static_cast<uint32_t>(interfaceId));
419 :
420 55 : return addr;
421 : }
422 :
423 59 : IPAddress IPAddress::MakeIPv6Multicast(IPv6MulticastFlags aFlags, uint8_t aScope,
424 : const uint8_t aGroupId[NL_INET_IPV6_MCAST_GROUP_LEN_IN_BYTES])
425 : {
426 : const uint32_t lFlagsAndScope =
427 59 : (((static_cast<uint32_t>(aFlags.Raw()) & 0xF) << 20) | ((static_cast<uint32_t>(aScope) & 0xF) << 16));
428 : IPAddress addr;
429 :
430 59 : addr.Addr[0] = htonl((0xFF000000U | lFlagsAndScope) | (uint32_t(aGroupId[0]) << 8) | (uint32_t(aGroupId[1]) << 0));
431 59 : addr.Addr[1] = htonl((uint32_t(aGroupId[2]) << 24) | (uint32_t(aGroupId[3]) << 16) | (uint32_t(aGroupId[4]) << 8) |
432 59 : (uint32_t(aGroupId[5]) << 0));
433 59 : addr.Addr[2] = htonl((uint32_t(aGroupId[6]) << 24) | (uint32_t(aGroupId[7]) << 16) | (uint32_t(aGroupId[8]) << 8) |
434 59 : (uint32_t(aGroupId[9]) << 0));
435 59 : addr.Addr[3] = htonl((uint32_t(aGroupId[10]) << 24) | (uint32_t(aGroupId[11]) << 16) | (uint32_t(aGroupId[12]) << 8) |
436 59 : (uint32_t(aGroupId[13]) << 0));
437 :
438 59 : return addr;
439 : }
440 :
441 14 : IPAddress IPAddress::MakeIPv6Multicast(IPv6MulticastFlags aFlags, uint8_t aScope, uint32_t aGroupId)
442 : {
443 14 : const uint8_t lGroupId[NL_INET_IPV6_MCAST_GROUP_LEN_IN_BYTES] = { 0,
444 : 0,
445 : 0,
446 : 0,
447 : 0,
448 : 0,
449 : 0,
450 : 0,
451 : 0,
452 : 0,
453 14 : static_cast<uint8_t>((aGroupId & 0xFF000000U) >> 24),
454 14 : static_cast<uint8_t>((aGroupId & 0x00FF0000U) >> 16),
455 14 : static_cast<uint8_t>((aGroupId & 0x0000FF00U) >> 8),
456 14 : static_cast<uint8_t>((aGroupId & 0x000000FFU) >> 0) };
457 :
458 14 : return (MakeIPv6Multicast(aFlags, aScope, lGroupId));
459 : }
460 :
461 14 : IPAddress IPAddress::MakeIPv6WellKnownMulticast(uint8_t aScope, uint32_t aGroupId)
462 : {
463 14 : constexpr IPv6MulticastFlags lFlags;
464 :
465 14 : return (MakeIPv6Multicast(lFlags, aScope, aGroupId));
466 : }
467 :
468 45 : IPAddress IPAddress::MakeIPv6TransientMulticast(IPv6MulticastFlags aFlags, uint8_t aScope,
469 : const uint8_t aGroupId[NL_INET_IPV6_MCAST_GROUP_LEN_IN_BYTES])
470 : {
471 45 : aFlags.Set(IPv6MulticastFlag::kTransient);
472 45 : return (MakeIPv6Multicast(aFlags, aScope, aGroupId));
473 : }
474 :
475 31 : IPAddress IPAddress::MakeIPv6PrefixMulticast(uint8_t aScope, uint8_t aPrefixLength, const uint64_t & aPrefix, uint32_t aGroupId)
476 : {
477 31 : const uint8_t lReserved = 0;
478 31 : const IPv6MulticastFlags lFlags = IPv6MulticastFlag::kPrefix;
479 31 : const uint8_t lGroupId[NL_INET_IPV6_MCAST_GROUP_LEN_IN_BYTES] = { lReserved,
480 : aPrefixLength,
481 31 : static_cast<uint8_t>((aPrefix & 0xFF00000000000000ULL) >> 56),
482 31 : static_cast<uint8_t>((aPrefix & 0x00FF000000000000ULL) >> 48),
483 31 : static_cast<uint8_t>((aPrefix & 0x0000FF0000000000ULL) >> 40),
484 31 : static_cast<uint8_t>((aPrefix & 0x000000FF00000000ULL) >> 32),
485 31 : static_cast<uint8_t>((aPrefix & 0x00000000FF000000ULL) >> 24),
486 31 : static_cast<uint8_t>((aPrefix & 0x0000000000FF0000ULL) >> 16),
487 31 : static_cast<uint8_t>((aPrefix & 0x000000000000FF00ULL) >> 8),
488 31 : static_cast<uint8_t>((aPrefix & 0x00000000000000FFULL) >> 0),
489 31 : static_cast<uint8_t>((aGroupId & 0xFF000000U) >> 24),
490 31 : static_cast<uint8_t>((aGroupId & 0x00FF0000U) >> 16),
491 31 : static_cast<uint8_t>((aGroupId & 0x0000FF00U) >> 8),
492 31 : static_cast<uint8_t>((aGroupId & 0x000000FFU) >> 0) };
493 :
494 31 : return (MakeIPv6TransientMulticast(lFlags, aScope, lGroupId));
495 : }
496 :
497 0 : IPAddress IPAddress::MakeIPv4Broadcast()
498 : {
499 : IPAddress ipAddr;
500 0 : ipAddr.Addr[0] = 0;
501 0 : ipAddr.Addr[1] = 0;
502 0 : ipAddr.Addr[2] = htonl(0xFFFF);
503 0 : ipAddr.Addr[3] = 0xFFFFFFFF;
504 0 : return ipAddr;
505 : }
506 :
507 100 : IPAddress IPAddress::Loopback(IPAddressType type)
508 : {
509 : IPAddress address;
510 : #if INET_CONFIG_ENABLE_IPV4
511 100 : if (type == IPAddressType::kIPv4)
512 : {
513 50 : address.Addr[0] = 0;
514 50 : address.Addr[1] = 0;
515 50 : address.Addr[2] = htonl(0xFFFF);
516 50 : address.Addr[3] = htonl(0x7F000001);
517 : }
518 : else
519 : #endif
520 : {
521 50 : address.Addr[0] = 0;
522 50 : address.Addr[1] = 0;
523 50 : address.Addr[2] = 0;
524 50 : address.Addr[3] = htonl(1);
525 : }
526 :
527 100 : return address;
528 : }
529 :
530 : } // namespace Inet
531 : } // namespace chip
|