tcp_address.cpp 21.6 KB
Newer Older
1
/*
2
    Copyright (c) 2007-2016 Contributors as noted in the AUTHORS file
3

4
    This file is part of libzmq, the ZeroMQ core engine in C++.
5

6 7 8
    libzmq is free software; you can redistribute it and/or modify it under
    the terms of the GNU Lesser General Public License (LGPL) as published
    by the Free Software Foundation; either version 3 of the License, or
9 10
    (at your option) any later version.

11 12 13 14 15 16 17 18 19 20 21 22 23 24
    As a special exception, the Contributors give you permission to link
    this library with independent modules to produce an executable,
    regardless of the license terms of these independent modules, and to
    copy and distribute the resulting executable under terms of your choice,
    provided that you also meet, for each linked independent module, the
    terms and conditions of the license of that module. An independent
    module is a module which is not derived from or based on this library.
    If you modify this library, you must extend this exception to your
    version of the library.

    libzmq is distributed in the hope that it will be useful, but WITHOUT
    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
    License for more details.
25 26 27 28 29

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

30
#include "precompiled.hpp"
31
#include <string>
32
#include <sstream>
33

34
#include "macros.hpp"
35
#include "tcp_address.hpp"
Martin Sustrik's avatar
Martin Sustrik committed
36
#include "stdint.hpp"
37
#include "err.hpp"
38
#include "ip.hpp"
39

40
#ifndef ZMQ_HAVE_WINDOWS
41 42 43
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
44
#include <net/if.h>
45
#include <netdb.h>
46
#include <ctype.h>
47 48
#include <unistd.h>
#include <stdlib.h>
49 50
#endif

Martin Hurton's avatar
Martin Hurton committed
51
#ifdef ZMQ_HAVE_SOLARIS
52 53 54
#include <sys/sockio.h>

//  On Solaris platform, network interface name can be queried by ioctl.
55
int zmq::tcp_address_t::resolve_nic_name (const char *nic_, bool ipv6_, bool is_src_)
56 57
{
    //  TODO: Unused parameter, IPv6 support not implemented for Solaris.
58
    LIBZMQ_UNUSED (ipv6_);
59 60

    //  Create a socket.
Martin Hurton's avatar
Martin Hurton committed
61
    const int fd = open_socket (AF_INET, SOCK_DGRAM, 0);
62
    errno_assert (fd != -1);
63 64 65 66 67 68

    //  Retrieve number of interfaces.
    lifnum ifn;
    ifn.lifn_family = AF_INET;
    ifn.lifn_flags = 0;
    int rc = ioctl (fd, SIOCGLIFNUM, (char*) &ifn);
69
    errno_assert (rc != -1);
70 71

    //  Allocate memory to get interface names.
Martin Hurton's avatar
Martin Hurton committed
72
    const size_t ifr_size = sizeof (struct lifreq) * ifn.lifn_count;
73 74
    char *ifr = (char*) malloc (ifr_size);
    alloc_assert (ifr);
75

76 77 78 79 80 81 82
    //  Retrieve interface names.
    lifconf ifc;
    ifc.lifc_family = AF_INET;
    ifc.lifc_flags = 0;
    ifc.lifc_len = ifr_size;
    ifc.lifc_buf = ifr;
    rc = ioctl (fd, SIOCGLIFCONF, (char*) &ifc);
83
    errno_assert (rc != -1);
84 85 86 87

    //  Find the interface with the specified name and AF_INET family.
    bool found = false;
    lifreq *ifrp = ifc.lifc_req;
88
    for (int n = 0; n < (int) (ifc.lifc_len / sizeof (lifreq));
89
          n ++, ifrp ++) {
90
        if (!strcmp (nic_, ifrp->lifr_name)) {
91
            rc = ioctl (fd, SIOCGLIFADDR, (char*) ifrp);
92
            errno_assert (rc != -1);
93
            if (ifrp->lifr_addr.ss_family == AF_INET) {
Martin Hurton's avatar
Martin Hurton committed
94
                if (is_src_)
95
                    source_address.ipv4 = *(sockaddr_in*) &ifrp->lifr_addr;
Martin Hurton's avatar
Martin Hurton committed
96 97
                else
                    address.ipv4 = *(sockaddr_in*) &ifrp->lifr_addr;
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
                found = true;
                break;
            }
        }
    }

    //  Clean-up.
    free (ifr);
    close (fd);

    if (!found) {
        errno = ENODEV;
        return -1;
    }
    return 0;
}

115
#elif defined ZMQ_HAVE_AIX || defined ZMQ_HAVE_HPUX || defined ZMQ_HAVE_ANDROID
116 117
#include <sys/ioctl.h>

118
int zmq::tcp_address_t::resolve_nic_name (const char *nic_, bool ipv6_, bool is_src_)
119
{
120 121 122 123 124 125 126 127
#if defined ZMQ_HAVE_AIX || defined ZMQ_HAVE_HPUX
    // IPv6 support not implemented for AIX or HP/UX.
    if (ipv6_)
    {
        errno = ENODEV;
        return -1;
    }
#endif
128 129

    //  Create a socket.
130
    const int sd = open_socket (ipv6_ ? AF_INET6 : AF_INET, SOCK_DGRAM, 0);
131
    errno_assert (sd != -1);
132

133
    struct ifreq ifr;
134 135

    //  Copy interface name for ioctl get.
136
    strncpy (ifr.ifr_name, nic_, sizeof (ifr.ifr_name) );
137 138

    //  Fetch interface address.
139
    const int rc = ioctl (sd, SIOCGIFADDR, (caddr_t) &ifr, sizeof (ifr) );
140 141 142 143 144 145 146 147

    //  Clean up.
    close (sd);

    if (rc == -1) {
        errno = ENODEV;
        return -1;
    }
148 149 150 151 152 153 154 155 156 157 158 159 160 161

    const int family = ifr.ifr_addr.sa_family;
    if ((family == AF_INET || (ipv6_ && family == AF_INET6))
        && !strcmp (nic_, ifr.ifr_name))
    {
        if (is_src_)
            memcpy (&source_address, &ifr.ifr_addr,
                    (family == AF_INET) ? sizeof (struct sockaddr_in)
                                        : sizeof (struct sockaddr_in6));
        else
            memcpy (&address, &ifr.ifr_addr,
                    (family == AF_INET) ? sizeof (struct sockaddr_in)
                                        : sizeof (struct sockaddr_in6));
    }
Martin Hurton's avatar
Martin Hurton committed
162
    else
163 164 165 166
    {
        errno = ENODEV;
        return -1;
    }
167

168
    return 0;
169 170 171 172
}

#elif ((defined ZMQ_HAVE_LINUX || defined ZMQ_HAVE_FREEBSD ||\
    defined ZMQ_HAVE_OSX || defined ZMQ_HAVE_OPENBSD ||\
173
    defined ZMQ_HAVE_QNXNTO || defined ZMQ_HAVE_NETBSD ||\
174
    defined ZMQ_HAVE_DRAGONFLY || defined ZMQ_HAVE_GNU)\
175 176 177 178 179 180
    && defined ZMQ_HAVE_IFADDRS)

#include <ifaddrs.h>

//  On these platforms, network interface name can be queried
//  using getifaddrs function.
181
int zmq::tcp_address_t::resolve_nic_name (const char *nic_, bool ipv6_, bool is_src_)
182 183
{
    //  Get the addresses.
Pieter Hintjens's avatar
Pieter Hintjens committed
184
    ifaddrs *ifa = NULL;
Martin Hurton's avatar
Martin Hurton committed
185
    const int rc = getifaddrs (&ifa);
186
    errno_assert (rc == 0);
187 188 189 190
    zmq_assert (ifa != NULL);

    //  Find the corresponding network interface.
    bool found = false;
Martin Hurton's avatar
Martin Hurton committed
191
    for (ifaddrs *ifp = ifa; ifp != NULL; ifp = ifp->ifa_next) {
192 193 194
        if (ifp->ifa_addr == NULL)
            continue;

Martin Hurton's avatar
Martin Hurton committed
195
        const int family = ifp->ifa_addr->sa_family;
Pieter Hintjens's avatar
Pieter Hintjens committed
196 197
        if ((family == AF_INET || (ipv6_ && family == AF_INET6))
        && !strcmp (nic_, ifp->ifa_name)) {
Martin Hurton's avatar
Martin Hurton committed
198
            if (is_src_)
199 200 201
                memcpy (&source_address, ifp->ifa_addr,
                        (family == AF_INET) ? sizeof (struct sockaddr_in)
                                            : sizeof (struct sockaddr_in6));
Martin Hurton's avatar
Martin Hurton committed
202
            else
203 204 205
                memcpy (&address, ifp->ifa_addr,
                        (family == AF_INET) ? sizeof (struct sockaddr_in)
                                            : sizeof (struct sockaddr_in6));
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
            found = true;
            break;
        }
    }

    //  Clean-up;
    freeifaddrs (ifa);

    if (!found) {
        errno = ENODEV;
        return -1;
    }
    return 0;
}

#else

//  On other platforms we assume there are no sane interface names.
//  This is true especially of Windows.
225
int zmq::tcp_address_t::resolve_nic_name (const char *nic_, bool ipv6_, bool is_src_)
226
{
227 228
    LIBZMQ_UNUSED (nic_);
    LIBZMQ_UNUSED (ipv6_);
229 230 231 232 233 234 235

    errno = ENODEV;
    return -1;
}

#endif

236
int zmq::tcp_address_t::resolve_interface (const char *interface_, bool ipv6_, bool is_src_)
237 238 239
{
    //  Initialize temporary output pointers with storage address.
    sockaddr_storage ss;
240 241
    sockaddr *out_addr = (sockaddr*) &ss;
    size_t out_addrlen;
242 243 244

    //  Initialise IP-format family/port and populate temporary output pointers
    //  with the address.
Pieter Hintjens's avatar
Pieter Hintjens committed
245
    if (ipv6_) {
246
        sockaddr_in6 ip6_addr;
247
        memset (&ip6_addr, 0, sizeof (ip6_addr) );
248
        ip6_addr.sin6_family = AF_INET6;
249 250
        memcpy (&ip6_addr.sin6_addr, &in6addr_any, sizeof (in6addr_any) );
        out_addrlen = sizeof (ip6_addr);
251 252
        memcpy (out_addr, &ip6_addr, out_addrlen);
    }
Pieter Hintjens's avatar
Pieter Hintjens committed
253 254
    else {
        sockaddr_in ip4_addr;
255
        memset (&ip4_addr, 0, sizeof (ip4_addr) );
Pieter Hintjens's avatar
Pieter Hintjens committed
256 257
        ip4_addr.sin_family = AF_INET;
        ip4_addr.sin_addr.s_addr = htonl (INADDR_ANY);
258
        out_addrlen = sizeof (ip4_addr);
Pieter Hintjens's avatar
Pieter Hintjens committed
259 260 261
        memcpy (out_addr, &ip4_addr, out_addrlen);
    }
    //  "*" resolves to INADDR_ANY or in6addr_any.
262
    if (strcmp (interface_, "*") == 0) {
263
        zmq_assert (out_addrlen <= sizeof (address) );
Martin Hurton's avatar
Martin Hurton committed
264
        if (is_src_)
265
            memcpy (&source_address, out_addr, out_addrlen);
Martin Hurton's avatar
Martin Hurton committed
266
        else
267
            memcpy (&address, out_addr, out_addrlen);
268 269 270 271
        return 0;
    }

    //  Try to resolve the string as a NIC name.
272
    int rc = resolve_nic_name (interface_, ipv6_, is_src_);
Martin Hurton's avatar
Martin Hurton committed
273
    if (rc == 0 || errno != ENODEV)
274 275 276 277 278 279 280 281 282 283
        return rc;

    //  There's no such interface name. Assume literal address.
#if defined ZMQ_HAVE_OPENVMS && defined __ia64
    __addrinfo64 *res = NULL;
    __addrinfo64 req;
#else
    addrinfo *res = NULL;
    addrinfo req;
#endif
284
    memset (&req, 0, sizeof (req) );
285 286 287

    //  Choose IPv4 or IPv6 protocol family. Note that IPv6 allows for
    //  IPv4-in-IPv6 addresses.
Pieter Hintjens's avatar
Pieter Hintjens committed
288
    req.ai_family = ipv6_? AF_INET6: AF_INET;
289 290 291 292 293 294 295 296

    //  Arbitrary, not used in the output, but avoids duplicate results.
    req.ai_socktype = SOCK_STREAM;

    //  Restrict hostname/service to literals to avoid any DNS lookups or
    //  service-name irregularity due to indeterminate socktype.
    req.ai_flags = AI_PASSIVE | AI_NUMERICHOST;

297
#if defined AI_V4MAPPED && !defined ZMQ_HAVE_FREEBSD && !defined ZMQ_HAVE_DRAGONFLY
298 299 300 301 302
    //  In this API we only require IPv4-mapped addresses when
    //  no native IPv6 interfaces are available (~AI_ALL).
    //  This saves an additional DNS roundtrip for IPv4 addresses.
    //  Note: While the AI_V4MAPPED flag is defined on FreeBSD system,
    //  it is not supported here. See libzmq issue #331.
303 304 305 306 307 308 309 310 311 312 313 314 315
    if (req.ai_family == AF_INET6)
        req.ai_flags |= AI_V4MAPPED;
#endif

    //  Resolve the literal address. Some of the error info is lost in case
    //  of error, however, there's no way to report EAI errors via errno.
    rc = getaddrinfo (interface_, NULL, &req, &res);
    if (rc) {
        errno = ENODEV;
        return -1;
    }

    //  Use the first result.
316
    zmq_assert (res != NULL);
317
    zmq_assert ((size_t) res->ai_addrlen <= sizeof (address) );
Martin Hurton's avatar
Martin Hurton committed
318
    if (is_src_)
319
        memcpy (&source_address, res->ai_addr, res->ai_addrlen);
Martin Hurton's avatar
Martin Hurton committed
320
    else
321
        memcpy (&address, res->ai_addr, res->ai_addrlen);
322 323

    //  Cleanup getaddrinfo after copying the possibly referenced result.
324
    freeaddrinfo (res);
325 326 327 328

    return 0;
}

329
int zmq::tcp_address_t::resolve_hostname (const char *hostname_, bool ipv6_, bool is_src_)
330 331
{
    //  Set up the query.
332 333 334
#if defined ZMQ_HAVE_OPENVMS && defined __ia64 && __INITIAL_POINTER_SIZE == 64
    __addrinfo64 req;
#else
335
    addrinfo req;
336
#endif
337
    memset (&req, 0, sizeof (req) );
338 339 340

    //  Choose IPv4 or IPv6 protocol family. Note that IPv6 allows for
    //  IPv4-in-IPv6 addresses.
Pieter Hintjens's avatar
Pieter Hintjens committed
341
    req.ai_family = ipv6_? AF_INET6: AF_INET;
342 343 344 345

    //  Need to choose one to avoid duplicate results from getaddrinfo() - this
    //  doesn't really matter, since it's not included in the addr-output.
    req.ai_socktype = SOCK_STREAM;
346

347
#if defined AI_V4MAPPED && !defined ZMQ_HAVE_FREEBSD && !defined ZMQ_HAVE_DRAGONFLY
348 349 350 351 352
    //  In this API we only require IPv4-mapped addresses when
    //  no native IPv6 interfaces are available.
    //  This saves an additional DNS roundtrip for IPv4 addresses.
    //  Note: While the AI_V4MAPPED flag is defined on FreeBSD system,
    //  it is not supported here. See libzmq issue #331.
353 354 355 356 357 358
    if (req.ai_family == AF_INET6)
        req.ai_flags |= AI_V4MAPPED;
#endif

    //  Resolve host name. Some of the error info is lost in case of error,
    //  however, there's no way to report EAI errors via errno.
359 360 361
#if defined ZMQ_HAVE_OPENVMS && defined __ia64 && __INITIAL_POINTER_SIZE == 64
    __addrinfo64 *res;
#else
362
    addrinfo *res;
363
#endif
Martin Hurton's avatar
Martin Hurton committed
364
    const int rc = getaddrinfo (hostname_, NULL, &req, &res);
365 366 367 368 369 370 371 372 373 374 375 376 377
    if (rc) {
        switch (rc) {
        case EAI_MEMORY:
            errno = ENOMEM;
            break;
        default:
            errno = EINVAL;
            break;
        }
        return -1;
    }

    //  Copy first result to output addr with hostname and service.
378
    zmq_assert ((size_t) res->ai_addrlen <= sizeof (address) );
Martin Hurton's avatar
Martin Hurton committed
379
    if (is_src_)
380
        memcpy (&source_address, res->ai_addr, res->ai_addrlen);
Martin Hurton's avatar
Martin Hurton committed
381
    else
382
        memcpy (&address, res->ai_addr, res->ai_addrlen);
383

384
    freeaddrinfo (res);
385

386 387 388
    return 0;
}

389 390
zmq::tcp_address_t::tcp_address_t () :
    _has_src_addr (false)
391
{
392 393
    memset (&address, 0, sizeof (address) );
    memset (&source_address, 0, sizeof (source_address) );
394 395
}

396 397
zmq::tcp_address_t::tcp_address_t (const sockaddr *sa, socklen_t sa_len) :
    _has_src_addr (false)
398
{
Martin Hurton's avatar
Martin Hurton committed
399 400
    zmq_assert (sa && sa_len > 0);

401 402 403 404
    memset (&address, 0, sizeof (address) );
    memset (&source_address, 0, sizeof (source_address) );
    if (sa->sa_family == AF_INET && sa_len >= (socklen_t) sizeof (address.ipv4) )
        memcpy (&address.ipv4, sa, sizeof (address.ipv4) );
Martin Hurton's avatar
Martin Hurton committed
405
    else
406 407
    if (sa->sa_family == AF_INET6 && sa_len >= (socklen_t) sizeof (address.ipv6) )
        memcpy (&address.ipv6, sa, sizeof (address.ipv6) );
408 409
}

410 411 412 413
zmq::tcp_address_t::~tcp_address_t ()
{
}

414
int zmq::tcp_address_t::resolve (const char *name_, bool local_, bool ipv6_, bool is_src_)
415
{
416 417 418 419 420
    if (!is_src_) {
        // Test the ';' to know if we have a source address in name_
        const char *src_delimiter = strrchr (name_, ';');
        if (src_delimiter) {
            std::string src_name (name_, src_delimiter - name_);
Martin Hurton's avatar
Martin Hurton committed
421
            const int rc = resolve (src_name.c_str (), local_, ipv6_, true);
422 423 424 425 426 427 428
            if (rc != 0)
                return -1;
            name_ = src_delimiter + 1;
            _has_src_addr = true;
        }
    }

429 430 431 432 433 434
    //  Find the ':' at end that separates address from the port number.
    const char *delimiter = strrchr (name_, ':');
    if (!delimiter) {
        errno = EINVAL;
        return -1;
    }
435

436 437 438 439
    //  Separate the address/port.
    std::string addr_str (name_, delimiter - name_);
    std::string port_str (delimiter + 1);

440
    //  Remove square brackets around the address, if any, as used in IPv6
441 442 443 444
    if (addr_str.size () >= 2 && addr_str [0] == '[' &&
          addr_str [addr_str.size () - 1] == ']')
        addr_str = addr_str.substr (1, addr_str.size () - 2);

445 446 447 448 449 450 451 452
    // Test the '%' to know if we have an interface name / zone_id in the address
    // Reference: https://tools.ietf.org/html/rfc4007
    std::size_t pos = addr_str.rfind("%");
    uint32_t zone_id = 0;
    if (pos != std::string::npos) {
        std::string if_str = addr_str.substr(pos + 1);
        addr_str = addr_str.substr(0, pos);
        if (isalpha (if_str.at (0)))
453
#if !defined ZMQ_HAVE_WINDOWS_TARGET_XP
454
            zone_id = if_nametoindex(if_str.c_str());
455 456 457 458 459 460 461 462
#else
            // The function 'if_nametoindex' is not supported on Windows XP.
            // If we are targeting XP using a vxxx_xp toolset then fail.
            // This is brutal as this code could be run on later windows clients
            // meaning the IPv6 zone_id cannot have an interface name.
            // This could be fixed with a runtime check.
            zone_id = 0;
#endif
463 464 465 466 467 468
        else
            zone_id = (uint32_t) atoi (if_str.c_str ());
        if (zone_id == 0) {
            errno = EINVAL;
            return -1;
        }
469

470 471
    }

472
    //  Allow 0 specifically, to detect invalid port error in atoi if not
Pieter Hintjens's avatar
Pieter Hintjens committed
473
    uint16_t port;
474
    if (port_str == "*" || port_str == "0")
475
        //  Resolve wildcard to 0 to allow autoselection of port
476
        port = 0;
477
    else {
478
        //  Parse the port number (0 is not a valid port).
479
        port = (uint16_t) atoi (port_str.c_str ());
480 481 482 483
        if (port == 0) {
            errno = EINVAL;
            return -1;
        }
484 485 486 487
    }

    //  Resolve the IP address.
    int rc;
488
    if (local_ || is_src_)
489
        rc = resolve_interface (addr_str.c_str (), ipv6_, is_src_);
490
    else
491
        rc = resolve_hostname (addr_str.c_str (), ipv6_, is_src_);
492 493 494 495
    if (rc != 0)
        return -1;

    //  Set the port into the address structure.
496
    if (is_src_) {
497
        if (source_address.generic.sa_family == AF_INET6) {
498
            source_address.ipv6.sin6_port = htons (port);
499 500
            source_address.ipv6.sin6_scope_id = zone_id;
        }
501 502
        else
            source_address.ipv4.sin_port = htons (port);
Martin Hurton's avatar
Martin Hurton committed
503 504
    }
    else {
505
        if (address.generic.sa_family == AF_INET6) {
506
            address.ipv6.sin6_port = htons (port);
507 508
            address.ipv6.sin6_scope_id = zone_id;
        }
509 510 511 512
        else
            address.ipv4.sin_port = htons (port);
    }

513 514 515
    return 0;
}

516 517
int zmq::tcp_address_t::to_string (std::string &addr_)
{
Pieter Hintjens's avatar
Pieter Hintjens committed
518 519
    if (address.generic.sa_family != AF_INET
    &&  address.generic.sa_family != AF_INET6) {
520 521 522 523
        addr_.clear ();
        return -1;
    }

524
    //  Not using service resolving because of
525
    //  https://github.com/zeromq/libzmq/commit/1824574f9b5a8ce786853320e3ea09fe1f822bc4
Martin Hurton's avatar
Martin Hurton committed
526
    char hbuf [NI_MAXHOST];
527
    int rc = getnameinfo (addr (), addrlen (), hbuf, sizeof (hbuf), NULL, 0, NI_NUMERICHOST);
528 529 530 531 532 533 534 535 536 537 538 539 540 541
    if (rc != 0) {
        addr_.clear ();
        return rc;
    }

    if (address.generic.sa_family == AF_INET6) {
        std::stringstream s;
        s << "tcp://[" << hbuf << "]:" << ntohs (address.ipv6.sin6_port);
        addr_ = s.str ();
    }
    else {
        std::stringstream s;
        s << "tcp://" << hbuf << ":" << ntohs (address.ipv4.sin_port);
        addr_ = s.str ();
Martin Hurton's avatar
Martin Hurton committed
542
    }
543 544 545
    return 0;
}

546
const sockaddr *zmq::tcp_address_t::addr () const
547 548 549 550
{
    return &address.generic;
}

551
socklen_t zmq::tcp_address_t::addrlen () const
552 553
{
    if (address.generic.sa_family == AF_INET6)
554
        return (socklen_t) sizeof (address.ipv6);
555
    else
556
        return (socklen_t) sizeof (address.ipv4);
557 558
}

559 560 561 562 563 564 565 566
const sockaddr *zmq::tcp_address_t::src_addr () const
{
    return &source_address.generic;
}

socklen_t zmq::tcp_address_t::src_addrlen () const
{
    if (address.generic.sa_family == AF_INET6)
567
        return (socklen_t) sizeof (source_address.ipv6);
568
    else
569
        return (socklen_t) sizeof (source_address.ipv4);
570 571
}

Martin Hurton's avatar
Martin Hurton committed
572
bool zmq::tcp_address_t::has_src_addr () const
573 574 575 576
{
    return _has_src_addr;
}

Martin Sustrik's avatar
Martin Sustrik committed
577
#if defined ZMQ_HAVE_WINDOWS
578
unsigned short zmq::tcp_address_t::family () const
Martin Sustrik's avatar
Martin Sustrik committed
579
#else
580
sa_family_t zmq::tcp_address_t::family () const
Martin Sustrik's avatar
Martin Sustrik committed
581
#endif
582 583 584 585
{
    return address.generic.sa_family;
}

586
zmq::tcp_address_mask_t::tcp_address_mask_t () :
Martin Hurton's avatar
Martin Hurton committed
587 588
    tcp_address_t (),
    address_mask (-1)
589 590 591
{
}

592
int zmq::tcp_address_mask_t::mask () const
593 594 595 596
{
    return address_mask;
}

Pieter Hintjens's avatar
Pieter Hintjens committed
597
int zmq::tcp_address_mask_t::resolve (const char *name_, bool ipv6_)
598 599
{
    // Find '/' at the end that separates address from the cidr mask number.
Martin Hurton's avatar
Martin Hurton committed
600
    // Allow empty mask clause and treat it like '/32' for ipv4 or '/128' for ipv6.
601
    std::string addr_str, mask_str;
602 603 604 605 606 607 608 609 610
    const char *delimiter = strrchr (name_, '/');
    if (delimiter != NULL) {
        addr_str.assign (name_, delimiter - name_);
        mask_str.assign (delimiter + 1);
        if (mask_str.empty ()) {
            errno = EINVAL;
            return -1;
        }
    }
Pieter Hintjens's avatar
Pieter Hintjens committed
611
    else
612 613 614
        addr_str.assign (name_);

    // Parse address part using standard routines.
Martin Hurton's avatar
Martin Hurton committed
615 616
    const int rc =
        tcp_address_t::resolve_hostname (addr_str.c_str (), ipv6_);
617 618 619 620 621 622 623 624 625 626 627
    if (rc != 0)
        return rc;

    // Parse the cidr mask number.
    if (mask_str.empty ()) {
        if (address.generic.sa_family == AF_INET6)
            address_mask = 128;
        else
            address_mask = 32;
    }
    else
Martin Hurton's avatar
Martin Hurton committed
628
    if (mask_str == "0")
629 630
        address_mask = 0;
    else {
Martin Hurton's avatar
Martin Hurton committed
631
        const int mask = atoi (mask_str.c_str ());
632 633 634 635 636 637 638 639 640 641 642 643 644 645
        if (
            (mask < 1) ||
            (address.generic.sa_family == AF_INET6 && mask > 128) ||
            (address.generic.sa_family != AF_INET6 && mask > 32)
        ) {
            errno = EINVAL;
            return -1;
        }
        address_mask = mask;
    }

    return 0;
}

646 647
int zmq::tcp_address_mask_t::to_string (std::string &addr_)
{
Martin Hurton's avatar
Martin Hurton committed
648 649
    if (address.generic.sa_family != AF_INET
    &&  address.generic.sa_family != AF_INET6) {
650 651 652 653 654 655 656 657
        addr_.clear ();
        return -1;
    }
    if (address_mask == -1) {
        addr_.clear ();
        return -1;
    }

Martin Hurton's avatar
Martin Hurton committed
658
    char hbuf [NI_MAXHOST];
659
    int rc = getnameinfo (addr (), addrlen (), hbuf, sizeof (hbuf), NULL, 0, NI_NUMERICHOST);
660 661 662 663 664 665 666 667 668 669 670 671 672 673
    if (rc != 0) {
        addr_.clear ();
        return rc;
    }

    if (address.generic.sa_family == AF_INET6) {
        std::stringstream s;
        s << "[" << hbuf << "]/" << address_mask;
        addr_ = s.str ();
    }
    else {
        std::stringstream s;
        s << hbuf << "/" << address_mask;
        addr_ = s.str ();
Martin Hurton's avatar
Martin Hurton committed
674
    }
675 676 677
    return 0;
}

678
bool zmq::tcp_address_mask_t::match_address (const struct sockaddr *ss, const socklen_t ss_len) const
679
{
Martin Hurton's avatar
Martin Hurton committed
680 681 682
    zmq_assert (address_mask != -1
             && ss != NULL
             && ss_len >= (socklen_t) sizeof (struct sockaddr));
683 684 685 686 687 688

    if (ss->sa_family != address.generic.sa_family)
        return false;

    if (address_mask > 0) {
        int mask;
689
        const uint8_t *our_bytes, *their_bytes;
690 691
        if (ss->sa_family == AF_INET6) {
            zmq_assert (ss_len == sizeof (struct sockaddr_in6));
692 693
            their_bytes = (const uint8_t *) &(((const struct sockaddr_in6 *) ss)->sin6_addr);
            our_bytes = (const uint8_t *) &address.ipv6.sin6_addr;
694 695 696 697
            mask = sizeof (struct in6_addr) * 8;
        }
        else {
            zmq_assert (ss_len == sizeof (struct sockaddr_in));
698 699
            their_bytes = (const uint8_t *) &(((const struct sockaddr_in *) ss)->sin_addr);
            our_bytes = (const uint8_t *) &address.ipv4.sin_addr;
700 701
            mask = sizeof (struct in_addr) * 8;
        }
Martin Hurton's avatar
Martin Hurton committed
702 703
        if (address_mask < mask)
            mask = address_mask;
704

Martin Hurton's avatar
Martin Hurton committed
705 706
        const size_t full_bytes = mask / 8;
        if (memcmp (our_bytes, their_bytes, full_bytes))
707
            return false;
708

Martin Hurton's avatar
Martin Hurton committed
709
        const uint8_t last_byte_bits = 0xffU << (8 - mask % 8);
710
        if (last_byte_bits) {
Martin Hurton's avatar
Martin Hurton committed
711
            if ((their_bytes [full_bytes] & last_byte_bits) != (our_bytes [full_bytes] & last_byte_bits))
712 713 714 715 716 717
                return false;
        }
    }

    return true;
}