blob: 7d9a68cd655c3ae408bd413cc745b56f4f2ce834 [file] [log] [blame]
/** @file
A brief file description
@section license License
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include "libts.h"
#if defined(darwin)
extern "C"
{
struct hostent *gethostbyname_r(const char *name, struct hostent *result, char *buffer, int buflen, int *h_errnop);
struct hostent *gethostbyaddr_r(const char *name, size_t size, int type,
struct hostent *result, char *buffer, int buflen, int *h_errnop);
}
#endif
IpAddr const IpAddr::INVALID;
struct hostent *
ink_gethostbyname_r(char *hostname, ink_gethostbyname_r_data * data)
{
#ifdef RENTRENT_GETHOSTBYNAME
struct hostent *r = gethostbyname(hostname);
if (r)
data->ent = *r;
data->herrno = errno;
#else //RENTRENT_GETHOSTBYNAME
#if GETHOSTBYNAME_R_GLIBC2
struct hostent *addrp = NULL;
int res = gethostbyname_r(hostname, &data->ent, data->buf,
INK_GETHOSTBYNAME_R_DATA_SIZE, &addrp,
&data->herrno);
struct hostent *r = NULL;
if (!res && addrp)
r = addrp;
#else
struct hostent *r = gethostbyname_r(hostname, &data->ent, data->buf,
INK_GETHOSTBYNAME_R_DATA_SIZE,
&data->herrno);
#endif
#endif
return r;
}
struct hostent *
ink_gethostbyaddr_r(char *ip, int len, int type, ink_gethostbyaddr_r_data * data)
{
#if GETHOSTBYNAME_R_GLIBC2
struct hostent *r = NULL;
struct hostent *addrp = NULL;
int res = gethostbyaddr_r((char *) ip, len, type, &data->ent, data->buf,
INK_GETHOSTBYNAME_R_DATA_SIZE, &addrp,
&data->herrno);
if (!res && addrp)
r = addrp;
#else
#ifdef RENTRENT_GETHOSTBYADDR
struct hostent *r = gethostbyaddr((const void *) ip, len, type);
#else
struct hostent *r = gethostbyaddr_r((char *) ip, len, type, &data->ent,
data->buf,
INK_GETHOSTBYNAME_R_DATA_SIZE,
&data->herrno);
#endif
#endif //LINUX
return r;
}
uint32_t
ink_inet_addr(const char *s)
{
uint32_t u[4];
uint8_t *pc = (uint8_t *) s;
int n = 0;
uint32_t base = 10;
while (n < 4) {
u[n] = 0;
base = 10;
// handle hex, octal
if (*pc == '0') {
if (*++pc == 'x' || *pc == 'X')
base = 16, pc++;
else
base = 8;
}
// handle hex, octal, decimal
while (*pc) {
if (ParseRules::is_digit(*pc)) {
u[n] = u[n] * base + (*pc++ - '0');
continue;
}
if (base == 16 && ParseRules::is_hex(*pc)) {
u[n] = u[n] * 16 + ParseRules::ink_tolower(*pc++) - 'a' + 10;
continue;
}
break;
}
n++;
if (*pc == '.')
pc++;
else
break;
}
if (*pc && !ParseRules::is_wslfcr(*pc))
return htonl((uint32_t) - 1);
switch (n) {
case 1:
return htonl(u[0]);
case 2:
if (u[0] > 0xff || u[1] > 0xffffff)
return htonl((uint32_t) - 1);
return htonl((u[0] << 24) | u[1]);
case 3:
if (u[0] > 0xff || u[1] > 0xff || u[2] > 0xffff)
return htonl((uint32_t) - 1);
return htonl((u[0] << 24) | (u[1] << 16) | u[2]);
case 4:
if (u[0] > 0xff || u[1] > 0xff || u[2] > 0xff || u[3] > 0xff)
return htonl((uint32_t) - 1);
return htonl((u[0] << 24) | (u[1] << 16) | (u[2] << 8) | u[3]);
}
return htonl((uint32_t) - 1);
}
const char *ats_ip_ntop(const struct sockaddr *addr, char *dst, size_t size)
{
char const* zret = 0;
switch (addr->sa_family) {
case AF_INET:
zret = inet_ntop(AF_INET, &ats_ip4_addr_cast(addr), dst, size);
break;
case AF_INET6:
zret = inet_ntop(AF_INET6, &ats_ip6_addr_cast(addr), dst, size);
break;
default:
zret = dst;
snprintf(dst, size, "*Not IP address [%u]*", addr->sa_family);
break;
}
return zret;
}
char const*
ats_ip_family_name(int family) {
return AF_INET == family ? "IPv4"
: AF_INET6 == family ? "IPv6"
: "Unspec"
;
}
char const* ats_ip_nptop(
sockaddr const* addr,
char* dst, size_t size
) {
char buff[INET6_ADDRSTRLEN];
snprintf(dst, size, "%s:%u",
ats_ip_ntop(addr, buff, sizeof(buff)),
ats_ip_port_host_order(addr)
);
return dst;
}
int
ats_ip_parse(ts::ConstBuffer src, ts::ConstBuffer* addr, ts::ConstBuffer* port) {
addr->reset();
port->reset();
// Let's see if we can find out what's in the address string.
if (src) {
while (src && isspace(*src)) ++src;
// Check for brackets.
if ('[' == *src) {
/* Ugly. In a number of places we must use bracket notation
to support port numbers. Rather than mucking with that
everywhere, we'll tweak it here. Experimentally we can't
depend on getaddrinfo to handle it. Note that the text
buffer size includes space for the nul, so a bracketed
address is at most that size - 1 + 2 -> size+1.
It just gets better. In order to bind link local addresses
the scope_id must be set to the interface index. That's
most easily done by appending a %intf (where "intf" is the
name of the interface) to the address. Which makes
the address potentially larger than the standard maximum.
So we can't depend on that sizing.
*/
++src; // skip bracket.
*addr = src.splitOn(']');
if (*addr && ':' == *src) { // found the closing bracket and port colon
++src; // skip colon.
*port = src;
} // else it's a fail for unclosed brackets.
} else {
// See if there's exactly 1 colon
ts::ConstBuffer tmp = src.after(':');
if (tmp && ! tmp.find(':')) { // 1 colon and no others
src.clip(tmp.data() - 1); // drop port from address.
*port = tmp;
} // else 0 or > 1 colon and no brackets means no port.
*addr = src;
}
// clip port down to digits.
if (*port) {
char const* spot = port->data();
while (isdigit(*spot)) ++spot;
port->clip(spot);
}
}
return *addr ? 0 : -1; // true if we found an address.
}
int
ats_ip_pton(char const* text, sockaddr* ip) {
int zret = -1;
ts::ConstBuffer addr, port;
ts::ConstBuffer src(text, strlen(text)+1);
ats_ip_invalidate(ip);
if (0 == ats_ip_parse(src, &addr, &port)) {
// Copy if not terminated.
if (0 != addr[addr.size()-1]) {
char* tmp = static_cast<char*>(alloca(addr.size()+1));
memcpy(tmp, addr.data(), addr.size());
tmp[addr.size()] = 0;
addr.set(tmp, addr.size());
}
if (addr.find(':')) { // colon -> IPv6
in6_addr addr6;
if (inet_pton(AF_INET6, addr.data(), &addr6)) {
zret = 0;
ats_ip6_set(ip, addr6);
}
} else { // no colon -> must be IPv4
in_addr addr4;
if (inet_aton(addr.data(), &addr4)) {
zret = 0;
ats_ip4_set(ip, addr4.s_addr);
}
}
// If we had a successful conversion, set the port.
if (ats_is_ip(ip))
ats_ip_port_cast(ip) = port ? htons(atoi(port.data())) : 0;
}
return zret;
}
uint32_t ats_ip_hash(sockaddr const* addr) {
union md5sum {
unsigned char c[16];
uint32_t i;
} zret;
zret.i = 0;
if (ats_is_ip4(addr)) {
zret.i = ats_ip4_addr_cast(addr);
} else if (ats_is_ip6(addr)) {
ink_code_md5(const_cast<uint8_t*>(ats_ip_addr8_cast(addr)), INK_IP6_SIZE, zret.c);
}
return zret.i;
}
int
ats_ip_to_hex(sockaddr const* src, char* dst, size_t len) {
int zret = 0;
ink_assert(len);
char const* dst_limit = dst + len - 1; // reserve null space.
if (ats_is_ip(src)) {
uint8_t const* data = ats_ip_addr8_cast(src);
for ( uint8_t const* src_limit = data + ats_ip_addr_size(src)
; data < src_limit && dst+1 < dst_limit
; ++data, zret += 2
) {
uint8_t n1 = (*data >> 4) & 0xF; // high nybble.
uint8_t n0 = *data & 0xF; // low nybble.
*dst++ = n1 > 9 ? n1 + 'A' - 10 : n1 + '0';
*dst++ = n0 > 9 ? n0 + 'A' - 10 : n0 + '0';
}
}
*dst = 0; // terminate but don't include that in the length.
return zret;
}
sockaddr* ats_ip_set(
sockaddr* dst,
IpAddr const& addr,
uint16_t port
) {
if (AF_INET == addr._family) ats_ip4_set(dst, addr._addr._ip4, port);
else if (AF_INET6 == addr._family) ats_ip6_set(dst, addr._addr._ip6, port);
else ats_ip_invalidate(dst);
return dst;
}
int
IpAddr::load(char const* text) {
IpEndpoint ip;
int zret = ats_ip_pton(text, &ip);
*this = ip;
return zret;
}
char*
IpAddr::toString(char* dest, size_t len) const {
IpEndpoint ip;
ip.assign(*this);
ats_ip_ntop(&ip, dest, len);
return dest;
}
bool
IpAddr::isMulticast() const {
return (AF_INET == _family && 0xe == _addr._byte[0]) ||
(AF_INET6 == _family && IN6_IS_ADDR_MULTICAST(&_addr._ip6))
;
}
bool
operator == (IpAddr const& lhs, sockaddr const* rhs) {
bool zret = false;
if (lhs._family == rhs->sa_family) {
if (AF_INET == lhs._family) {
zret = lhs._addr._ip4 == ats_ip4_addr_cast(rhs);
} else if (AF_INET6 == lhs._family) {
zret = 0 == memcmp(&lhs._addr._ip6, &ats_ip6_addr_cast(rhs), sizeof(in6_addr));
} else { // map all non-IP to the same thing.
zret = true;
}
} // else different families, not equal.
return zret;
}
int
ats_ip_getbestaddrinfo(char const* host,
IpEndpoint* ip4,
IpEndpoint* ip6
) {
int zret = -1;
int port = 0; // port value to assign if we find an address.
addrinfo ai_hints;
addrinfo* ai_result;
ts::ConstBuffer addr_text, port_text;
ts::ConstBuffer src(host, strlen(host)+1);
if (ip4) ats_ip_invalidate(ip4);
if (ip6) ats_ip_invalidate(ip6);
if (0 == ats_ip_parse(src, &addr_text, &port_text)) {
// Copy if not terminated.
if (0 != addr_text[addr_text.size()-1]) {
char* tmp = static_cast<char*>(alloca(addr_text.size()+1));
memcpy(tmp, addr_text.data(), addr_text.size());
tmp[addr_text.size()] = 0;
addr_text.set(tmp, addr_text.size());
}
ink_zero(ai_hints);
ai_hints.ai_family = AF_UNSPEC;
ai_hints.ai_flags = AI_ADDRCONFIG;
zret = getaddrinfo(addr_text.data(), 0, &ai_hints, &ai_result);
if (0 == zret) {
// Walk the returned addresses and pick the "best".
enum {
NA, // Not an (IP) Address.
LO, // Loopback.
MC, // Multicast.
NR, // Non-Routable.
GA // Globally unique Address.
} spot_type = NA, ip4_type = NA, ip6_type = NA;
sockaddr const* ip4_src = 0;
sockaddr const* ip6_src = 0;
for ( addrinfo* ai_spot = ai_result
; ai_spot
; ai_spot = ai_spot->ai_next
) {
sockaddr const* ai_ip = ai_spot->ai_addr;
if (!ats_is_ip(ai_ip)) spot_type = NA;
else if (ats_is_ip_loopback(ai_ip)) spot_type = LO;
else if (ats_is_ip_nonroutable(ai_ip)) spot_type = NR;
else if (ats_is_ip_multicast(ai_ip)) spot_type = MC;
else spot_type = GA;
if (spot_type == NA) continue; // Next!
if (ats_is_ip4(ai_ip)) {
if (spot_type > ip4_type) {
ip4_src = ai_ip;
ip4_type = spot_type;
}
} else if (ats_is_ip6(ai_ip)) {
if (spot_type > ip6_type) {
ip6_src = ai_ip;
ip6_type = spot_type;
}
}
}
if (ip4_type > NA) ats_ip_copy(ip4, ip4_src);
if (ip6_type > NA) ats_ip_copy(ip6, ip6_src);
freeaddrinfo(ai_result); // free *after* the copy.
}
}
// We don't really care if the port is null terminated - the parser
// would get all the digits so the next character is a non-digit (null or
// not) and atoi will do the right thing in either case.
if (port_text.size()) port = htons(atoi(port_text.data()));
if (ats_is_ip(ip4)) ats_ip_port_cast(ip4) = port;
if (ats_is_ip(ip6)) ats_ip_port_cast(ip6) = port;
if (!ats_is_ip(ip4) && !ats_is_ip(ip6)) zret = -1;
return zret;
}