tcp_address.cpp 19.9 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
}

#elif ((defined ZMQ_HAVE_LINUX || defined ZMQ_HAVE_FREEBSD ||\
    defined ZMQ_HAVE_OSX || defined ZMQ_HAVE_OPENBSD ||\
160 161
    defined ZMQ_HAVE_QNXNTO || defined ZMQ_HAVE_NETBSD ||\
    defined ZMQ_HAVE_DRAGONFLY)\
162 163 164 165 166 167
    && defined ZMQ_HAVE_IFADDRS)

#include <ifaddrs.h>

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

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

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

    errno = ENODEV;
    return -1;
}

#endif

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

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

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

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

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

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

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

    return 0;
}

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

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

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

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

371
    freeaddrinfo (res);
372

373 374 375
    return 0;
}

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

383 384
zmq::tcp_address_t::tcp_address_t (const sockaddr *sa, socklen_t sa_len) :
    _has_src_addr (false)
385
{
Martin Hurton's avatar
Martin Hurton committed
386 387 388 389 390 391 392 393 394
    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);
395 396
}

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

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

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

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

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

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

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

    //  Set the port into the address structure.
456 457 458 459 460
    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
461 462
    }
    else {
463 464 465 466 467 468
        if (address.generic.sa_family == AF_INET6)
            address.ipv6.sin6_port = htons (port);
        else
            address.ipv4.sin_port = htons (port);
    }

469 470 471
    return 0;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return true;
}