tcp_address.cpp 19.8 KB
Newer Older
1
/*
2
    Copyright (c) 2007-2015 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 30

    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/>.
*/

#include <string>
31
#include <sstream>
32

33
#include "macros.hpp"
34 35
#include "tcp_address.hpp"
#include "platform.hpp"
Martin Sustrik's avatar
Martin Sustrik committed
36
#include "stdint.hpp"
37
#include "err.hpp"
38
#include "ip.hpp"
39 40 41 42 43 44 45 46 47 48

#ifdef ZMQ_HAVE_WINDOWS
#include "windows.hpp"
#else
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include <netdb.h>
#endif

Martin Hurton's avatar
Martin Hurton committed
49
#ifdef ZMQ_HAVE_SOLARIS
50 51 52 53

#include <sys/sockio.h>
#include <net/if.h>
#include <unistd.h>
54
#include <stdlib.h>
55 56

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

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

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

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

78 79 80 81 82 83 84
    //  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);
85
    errno_assert (rc != -1);
86 87 88 89

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

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

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

117
#elif defined ZMQ_HAVE_AIX || defined ZMQ_HAVE_HPUX || defined ZMQ_HAVE_ANDROID
118 119 120 121 122 123

#include <sys/types.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <net/if.h>

124
int zmq::tcp_address_t::resolve_nic_name (const char *nic_, bool ipv6_, bool is_src_)
125 126
{
    //  TODO: Unused parameter, IPv6 support not implemented for AIX or HP/UX.
127
    LIBZMQ_UNUSED(ipv6_);
128 129

    //  Create a socket.
Martin Hurton's avatar
Martin Hurton committed
130
    const int sd = open_socket (AF_INET, SOCK_DGRAM, 0);
131
    errno_assert (sd != -1);
132

133
    struct ifreq ifr;
134 135

    //  Copy interface name for ioctl get.
Martin Hurton's avatar
Martin Hurton committed
136
    strncpy (ifr.ifr_name, nic_, sizeof ifr.ifr_name);
137 138

    //  Fetch interface address.
Martin Hurton's avatar
Martin Hurton committed
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;
    }
Martin Hurton's avatar
Martin Hurton committed
148
    if (is_src_)
Martin Hurton's avatar
Martin Hurton committed
149
        memcpy (&source_address.ipv4.sin_addr,
150
            &((sockaddr_in*) &ifr.ifr_addr)->sin_addr, sizeof (struct in_addr));
Martin Hurton's avatar
Martin Hurton committed
151
    else
Martin Hurton's avatar
Martin Hurton committed
152
       memcpy (&address.ipv4.sin_addr,
153
            &((sockaddr_in*) &ifr.ifr_addr)->sin_addr, sizeof (struct in_addr));
154

155
    return 0;
156 157 158 159 160 161 162 163 164 165 166
}

#elif ((defined ZMQ_HAVE_LINUX || defined ZMQ_HAVE_FREEBSD ||\
    defined ZMQ_HAVE_OSX || defined ZMQ_HAVE_OPENBSD ||\
    defined ZMQ_HAVE_QNXNTO || defined ZMQ_HAVE_NETBSD)\
    && defined ZMQ_HAVE_IFADDRS)

#include <ifaddrs.h>

//  On these platforms, network interface name can be queried
//  using getifaddrs function.
167
int zmq::tcp_address_t::resolve_nic_name (const char *nic_, bool ipv6_, bool is_src_)
168 169
{
    //  Get the addresses.
Pieter Hintjens's avatar
Pieter Hintjens committed
170
    ifaddrs *ifa = NULL;
Martin Hurton's avatar
Martin Hurton committed
171
    const int rc = getifaddrs (&ifa);
172
    errno_assert (rc == 0);
173 174 175 176
    zmq_assert (ifa != NULL);

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

Martin Hurton's avatar
Martin Hurton committed
181
        const int family = ifp->ifa_addr->sa_family;
Pieter Hintjens's avatar
Pieter Hintjens committed
182 183
        if ((family == AF_INET || (ipv6_ && family == AF_INET6))
        && !strcmp (nic_, ifp->ifa_name)) {
Martin Hurton's avatar
Martin Hurton committed
184
            if (is_src_)
185 186 187
                memcpy (&source_address, ifp->ifa_addr,
                        (family == AF_INET) ? sizeof (struct sockaddr_in)
                                            : sizeof (struct sockaddr_in6));
Martin Hurton's avatar
Martin Hurton committed
188
            else
189 190 191
                memcpy (&address, ifp->ifa_addr,
                        (family == AF_INET) ? sizeof (struct sockaddr_in)
                                            : sizeof (struct sockaddr_in6));
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
            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.
211
int zmq::tcp_address_t::resolve_nic_name (const char *nic_, bool ipv6_, bool is_src_)
212
{
213 214
	LIBZMQ_UNUSED(nic_);
	LIBZMQ_UNUSED(ipv6_);
215 216 217 218 219 220 221

    errno = ENODEV;
    return -1;
}

#endif

222
int zmq::tcp_address_t::resolve_interface (const char *interface_, bool ipv6_, bool is_src_)
223 224 225
{
    //  Initialize temporary output pointers with storage address.
    sockaddr_storage ss;
226 227
    sockaddr *out_addr = (sockaddr*) &ss;
    size_t out_addrlen;
228 229 230

    //  Initialise IP-format family/port and populate temporary output pointers
    //  with the address.
Pieter Hintjens's avatar
Pieter Hintjens committed
231
    if (ipv6_) {
232
        sockaddr_in6 ip6_addr;
Martin Hurton's avatar
Martin Hurton committed
233
        memset (&ip6_addr, 0, sizeof ip6_addr);
234
        ip6_addr.sin6_family = AF_INET6;
Martin Hurton's avatar
Martin Hurton committed
235
        memcpy (&ip6_addr.sin6_addr, &in6addr_any, sizeof in6addr_any);
236
        out_addrlen = sizeof ip6_addr;
237 238
        memcpy (out_addr, &ip6_addr, out_addrlen);
    }
Pieter Hintjens's avatar
Pieter Hintjens committed
239 240
    else {
        sockaddr_in ip4_addr;
Martin Hurton's avatar
Martin Hurton committed
241
        memset (&ip4_addr, 0, sizeof ip4_addr);
Pieter Hintjens's avatar
Pieter Hintjens committed
242 243 244 245 246 247
        ip4_addr.sin_family = AF_INET;
        ip4_addr.sin_addr.s_addr = htonl (INADDR_ANY);
        out_addrlen = sizeof ip4_addr;
        memcpy (out_addr, &ip4_addr, out_addrlen);
    }
    //  "*" resolves to INADDR_ANY or in6addr_any.
248
    if (strcmp (interface_, "*") == 0) {
249
        zmq_assert (out_addrlen <= sizeof address);
Martin Hurton's avatar
Martin Hurton committed
250
        if (is_src_)
251
            memcpy (&source_address, out_addr, out_addrlen);
Martin Hurton's avatar
Martin Hurton committed
252
        else
253
            memcpy (&address, out_addr, out_addrlen);
254 255 256 257
        return 0;
    }

    //  Try to resolve the string as a NIC name.
258
    int rc = resolve_nic_name (interface_, ipv6_, is_src_);
Martin Hurton's avatar
Martin Hurton committed
259
    if (rc == 0 || errno != ENODEV)
260 261 262 263 264 265 266 267 268 269
        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
Martin Hurton's avatar
Martin Hurton committed
270
    memset (&req, 0, sizeof req);
271 272 273

    //  Choose IPv4 or IPv6 protocol family. Note that IPv6 allows for
    //  IPv4-in-IPv6 addresses.
Pieter Hintjens's avatar
Pieter Hintjens committed
274
    req.ai_family = ipv6_? AF_INET6: AF_INET;
275 276 277 278 279 280 281 282

    //  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;

283 284 285 286 287 288
#if defined AI_V4MAPPED && !defined ZMQ_HAVE_FREEBSD
    //  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.
289 290 291 292 293 294 295 296 297 298 299 300 301
    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.
302
    zmq_assert (res != NULL);
Martin Hurton's avatar
Martin Hurton committed
303 304
    zmq_assert ((size_t) res->ai_addrlen <= sizeof address);
    if (is_src_)
305
        memcpy (&source_address, res->ai_addr, res->ai_addrlen);
Martin Hurton's avatar
Martin Hurton committed
306
    else
307
        memcpy (&address, res->ai_addr, res->ai_addrlen);
308 309

    //  Cleanup getaddrinfo after copying the possibly referenced result.
310
    freeaddrinfo (res);
311 312 313 314

    return 0;
}

315
int zmq::tcp_address_t::resolve_hostname (const char *hostname_, bool ipv6_, bool is_src_)
316 317
{
    //  Set up the query.
318 319 320
#if defined ZMQ_HAVE_OPENVMS && defined __ia64 && __INITIAL_POINTER_SIZE == 64
    __addrinfo64 req;
#else
321
    addrinfo req;
322
#endif
Martin Hurton's avatar
Martin Hurton committed
323
    memset (&req, 0, sizeof req);
324 325 326

    //  Choose IPv4 or IPv6 protocol family. Note that IPv6 allows for
    //  IPv4-in-IPv6 addresses.
Pieter Hintjens's avatar
Pieter Hintjens committed
327
    req.ai_family = ipv6_? AF_INET6: AF_INET;
328 329 330 331

    //  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;
332 333 334 335 336 337 338

#if defined AI_V4MAPPED && !defined ZMQ_HAVE_FREEBSD
    //  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.
339 340 341 342 343 344
    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.
345 346 347
#if defined ZMQ_HAVE_OPENVMS && defined __ia64 && __INITIAL_POINTER_SIZE == 64
    __addrinfo64 *res;
#else
348
    addrinfo *res;
349
#endif
Martin Hurton's avatar
Martin Hurton committed
350
    const int rc = getaddrinfo (hostname_, NULL, &req, &res);
351 352 353 354 355 356 357 358 359 360 361 362 363
    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.
Martin Hurton's avatar
Martin Hurton committed
364 365
    zmq_assert ((size_t) res->ai_addrlen <= sizeof address);
    if (is_src_)
366
        memcpy (&source_address, res->ai_addr, res->ai_addrlen);
Martin Hurton's avatar
Martin Hurton committed
367
    else
368
        memcpy (&address, res->ai_addr, res->ai_addrlen);
369

370
    freeaddrinfo (res);
371

372 373 374
    return 0;
}

375 376
zmq::tcp_address_t::tcp_address_t () :
    _has_src_addr (false)
377
{
Martin Hurton's avatar
Martin Hurton committed
378 379
    memset (&address, 0, sizeof address);
    memset (&source_address, 0, sizeof source_address);
380 381
}

382 383
zmq::tcp_address_t::tcp_address_t (const sockaddr *sa, socklen_t sa_len) :
    _has_src_addr (false)
384
{
Martin Hurton's avatar
Martin Hurton committed
385 386 387 388 389 390 391 392 393
    zmq_assert (sa && sa_len > 0);

    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);
    else
    if (sa->sa_family == AF_INET6 && sa_len >= (socklen_t) sizeof address.ipv6)
        memcpy (&address.ipv6, sa, sizeof address.ipv6);
394 395
}

396 397 398 399
zmq::tcp_address_t::~tcp_address_t ()
{
}

400
int zmq::tcp_address_t::resolve (const char *name_, bool local_, bool ipv6_, bool is_src_)
401
{
402 403 404 405 406
    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
407
            const int rc = resolve (src_name.c_str (), local_, ipv6_, true);
408 409 410 411 412 413 414
            if (rc != 0)
                return -1;
            name_ = src_delimiter + 1;
            _has_src_addr = true;
        }
    }

415 416 417 418 419 420
    //  Find the ':' at end that separates address from the port number.
    const char *delimiter = strrchr (name_, ':');
    if (!delimiter) {
        errno = EINVAL;
        return -1;
    }
421

422 423 424 425
    //  Separate the address/port.
    std::string addr_str (name_, delimiter - name_);
    std::string port_str (delimiter + 1);

426
    //  Remove square brackets around the address, if any, as used in IPv6
427 428 429 430
    if (addr_str.size () >= 2 && addr_str [0] == '[' &&
          addr_str [addr_str.size () - 1] == ']')
        addr_str = addr_str.substr (1, addr_str.size () - 2);

431
    //  Allow 0 specifically, to detect invalid port error in atoi if not
Pieter Hintjens's avatar
Pieter Hintjens committed
432
    uint16_t port;
433
    if (port_str == "*" || port_str == "0")
434
        //  Resolve wildcard to 0 to allow autoselection of port
435
        port = 0;
436
    else {
437
        //  Parse the port number (0 is not a valid port).
438
        port = (uint16_t) atoi (port_str.c_str ());
439 440 441 442
        if (port == 0) {
            errno = EINVAL;
            return -1;
        }
443 444 445 446 447
    }

    //  Resolve the IP address.
    int rc;
    if (local_)
448
        rc = resolve_interface (addr_str.c_str (), ipv6_, is_src_);
449
    else
450
        rc = resolve_hostname (addr_str.c_str (), ipv6_, is_src_);
451 452 453 454
    if (rc != 0)
        return -1;

    //  Set the port into the address structure.
455 456 457 458 459
    if (is_src_) {
        if (source_address.generic.sa_family == AF_INET6)
            source_address.ipv6.sin6_port = htons (port);
        else
            source_address.ipv4.sin_port = htons (port);
Martin Hurton's avatar
Martin Hurton committed
460 461
    }
    else {
462 463 464 465 466 467
        if (address.generic.sa_family == AF_INET6)
            address.ipv6.sin6_port = htons (port);
        else
            address.ipv4.sin_port = htons (port);
    }

468 469 470
    return 0;
}

471 472
int zmq::tcp_address_t::to_string (std::string &addr_)
{
Pieter Hintjens's avatar
Pieter Hintjens committed
473 474
    if (address.generic.sa_family != AF_INET
    &&  address.generic.sa_family != AF_INET6) {
475 476 477 478
        addr_.clear ();
        return -1;
    }

479 480
    //  Not using service resolv because of
    //  https://github.com/zeromq/libzmq/commit/1824574f9b5a8ce786853320e3ea09fe1f822bc4
Martin Hurton's avatar
Martin Hurton committed
481 482
    char hbuf [NI_MAXHOST];
    int rc = getnameinfo (addr (), addrlen (), hbuf, sizeof hbuf, NULL, 0, NI_NUMERICHOST);
483 484 485 486 487 488 489 490 491 492 493 494 495 496
    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
497
    }
498 499 500
    return 0;
}

501
const sockaddr *zmq::tcp_address_t::addr () const
502 503 504 505
{
    return &address.generic;
}

506
socklen_t zmq::tcp_address_t::addrlen () const
507 508
{
    if (address.generic.sa_family == AF_INET6)
Martin Hurton's avatar
Martin Hurton committed
509
        return (socklen_t) sizeof address.ipv6;
510
    else
Martin Hurton's avatar
Martin Hurton committed
511
        return (socklen_t) sizeof address.ipv4;
512 513
}

514 515 516 517 518 519 520 521
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)
Martin Hurton's avatar
Martin Hurton committed
522
        return (socklen_t) sizeof source_address.ipv6;
523
    else
Martin Hurton's avatar
Martin Hurton committed
524
        return (socklen_t) sizeof source_address.ipv4;
525 526
}

Martin Hurton's avatar
Martin Hurton committed
527
bool zmq::tcp_address_t::has_src_addr () const
528 529 530 531
{
    return _has_src_addr;
}

Martin Sustrik's avatar
Martin Sustrik committed
532
#if defined ZMQ_HAVE_WINDOWS
533
unsigned short zmq::tcp_address_t::family () const
Martin Sustrik's avatar
Martin Sustrik committed
534
#else
535
sa_family_t zmq::tcp_address_t::family () const
Martin Sustrik's avatar
Martin Sustrik committed
536
#endif
537 538 539 540
{
    return address.generic.sa_family;
}

541
zmq::tcp_address_mask_t::tcp_address_mask_t () :
Martin Hurton's avatar
Martin Hurton committed
542 543
    tcp_address_t (),
    address_mask (-1)
544 545 546
{
}

547
int zmq::tcp_address_mask_t::mask () const
548 549 550 551
{
    return address_mask;
}

Pieter Hintjens's avatar
Pieter Hintjens committed
552
int zmq::tcp_address_mask_t::resolve (const char *name_, bool ipv6_)
553 554
{
    // Find '/' at the end that separates address from the cidr mask number.
Martin Hurton's avatar
Martin Hurton committed
555
    // Allow empty mask clause and treat it like '/32' for ipv4 or '/128' for ipv6.
556
    std::string addr_str, mask_str;
557 558 559 560 561 562 563 564 565
    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
566
    else
567 568 569
        addr_str.assign (name_);

    // Parse address part using standard routines.
Martin Hurton's avatar
Martin Hurton committed
570 571
    const int rc =
        tcp_address_t::resolve_hostname (addr_str.c_str (), ipv6_);
572 573 574 575 576 577 578 579 580 581 582
    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
583
    if (mask_str == "0")
584 585
        address_mask = 0;
    else {
Martin Hurton's avatar
Martin Hurton committed
586
        const int mask = atoi (mask_str.c_str ());
587 588 589 590 591 592 593 594 595 596 597 598 599 600
        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;
}

601 602
int zmq::tcp_address_mask_t::to_string (std::string &addr_)
{
Martin Hurton's avatar
Martin Hurton committed
603 604
    if (address.generic.sa_family != AF_INET
    &&  address.generic.sa_family != AF_INET6) {
605 606 607 608 609 610 611 612
        addr_.clear ();
        return -1;
    }
    if (address_mask == -1) {
        addr_.clear ();
        return -1;
    }

Martin Hurton's avatar
Martin Hurton committed
613 614
    char hbuf [NI_MAXHOST];
    int rc = getnameinfo (addr (), addrlen (), hbuf, sizeof hbuf, NULL, 0, NI_NUMERICHOST);
615 616 617 618 619 620 621 622 623 624 625 626 627 628
    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
629
    }
630 631 632
    return 0;
}

633
bool zmq::tcp_address_mask_t::match_address (const struct sockaddr *ss, const socklen_t ss_len) const
634
{
Martin Hurton's avatar
Martin Hurton committed
635 636 637
    zmq_assert (address_mask != -1
             && ss != NULL
             && ss_len >= (socklen_t) sizeof (struct sockaddr));
638 639 640 641 642 643

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

    if (address_mask > 0) {
        int mask;
644
        const uint8_t *our_bytes, *their_bytes;
645 646
        if (ss->sa_family == AF_INET6) {
            zmq_assert (ss_len == sizeof (struct sockaddr_in6));
647 648
            their_bytes = (const uint8_t *) &(((const struct sockaddr_in6 *) ss)->sin6_addr);
            our_bytes = (const uint8_t *) &address.ipv6.sin6_addr;
649 650 651 652
            mask = sizeof (struct in6_addr) * 8;
        }
        else {
            zmq_assert (ss_len == sizeof (struct sockaddr_in));
653 654
            their_bytes = (const uint8_t *) &(((const struct sockaddr_in *) ss)->sin_addr);
            our_bytes = (const uint8_t *) &address.ipv4.sin_addr;
655 656
            mask = sizeof (struct in_addr) * 8;
        }
Martin Hurton's avatar
Martin Hurton committed
657 658
        if (address_mask < mask)
            mask = address_mask;
659

Martin Hurton's avatar
Martin Hurton committed
660 661
        const size_t full_bytes = mask / 8;
        if (memcmp (our_bytes, their_bytes, full_bytes))
662
            return false;
663

Martin Hurton's avatar
Martin Hurton committed
664
        const uint8_t last_byte_bits = 0xffU << (8 - mask % 8);
665
        if (last_byte_bits) {
Martin Hurton's avatar
Martin Hurton committed
666
            if ((their_bytes [full_bytes] & last_byte_bits) != (our_bytes [full_bytes] & last_byte_bits))
667 668 669 670 671 672
                return false;
        }
    }

    return true;
}