diff --git a/configure.in b/configure.in index 0db49744..86fb25ee 100644 --- a/configure.in +++ b/configure.in @@ -279,6 +279,8 @@ NETDB_FLAGS="" AC_CHECK_FUNC([inet_ntop],[NETDB_FLAGS="$NETDB_FLAGS -DHAVE_NTOP"]) AC_CHECK_FUNC([inet_pton],[NETDB_FLAGS="$NETDB_FLAGS -DHAVE_PTON"]) AC_CHECK_FUNC([gethostbyname_r],[NETDB_FLAGS="$NETDB_FLAGS -DHAVE_GHBN_R"]) +AC_CHECK_FUNC([gethostbyname2_r],[NETDB_FLAGS="$NETDB_FLAGS -DHAVE_GHBN2_R"]) +AC_CHECK_FUNC([gethostbyname2],[NETDB_FLAGS="$NETDB_FLAGS -DHAVE_GHBN2"]) AC_SUBST(NETDB_FLAGS) THREAD_KILL="" diff --git a/engine/Socket.cpp b/engine/Socket.cpp index 2f16142f..a22e1c49 100644 --- a/engine/Socket.cpp +++ b/engine/Socket.cpp @@ -43,6 +43,7 @@ #ifndef _WINDOWS +#include #include #include #include @@ -106,6 +107,98 @@ static void epochToFt(unsigned int secEpoch, FILETIME& ft) #endif +// +// IPv6 module functions +// +#ifdef AF_INET6 + +#if defined(HAVE_GHBN2_R) || defined(HAVE_GHBN2) +#define YATE_SOCKET_GHBN2_AVAILABLE + +static inline bool ghbn2Set(struct sockaddr* addr, hostent* he, int family) +{ + if (!(he && he->h_addrtype == family && he->h_addr_list)) + return false; + char* val = he->h_addr_list[0]; + if (!val) + return false; + if (family == AF_INET6) { + ((struct sockaddr_in6*)addr)->sin6_addr = *(in6_addr*)val; + return true; + } + return false; +} + +// Resolve an address using gethostbyname2_r or gethostbyname2 +// Return 1 on success, 0 on failure, -1 if not available +static int resolveGHBN2(struct sockaddr* addr, const char* name) +{ + if (!addr || TelEngine::null(name)) + return 0; + int family = AF_INET6; +#ifdef HAVE_GHBN2_R + char buf[576]; + struct hostent h; + struct hostent* hr = 0; + int errn = 0; + int r = gethostbyname2_r(name,family,&h,buf,sizeof(buf),&hr,&errn); + if (r != ERANGE) { + if (!r && ghbn2Set(addr,hr,family)) + return 1; + return 0; + } + // Buffer too short: fallback to gethostbyname2 if available +#endif +#ifdef HAVE_GHBN2 + Lock lck(s_mutex,MAX_RESWAIT); + if (lck.locked()) { + if (ghbn2Set(addr,gethostbyname2(name,family),family)) + return 1; + } + else + Alarm("engine","socket",DebugWarn,"Resolver was busy, failing '%s'",name); +#else +#ifndef HAVE_GHBN2_R + return -1; +#endif +#endif + return 0; +} + +#endif // defined(HAVE_GHBN2_R) || defined(HAVE_GHBN2) + +// Resolve a domain to IPv6 address +static inline bool resolveIPv6(struct sockaddr* addr, const char* name) +{ + static bool s_noIPv6 = true; +#ifdef YATE_SOCKET_GHBN2_AVAILABLE + int res = resolveGHBN2(addr,name); + if (res >= 0) + return res > 0; +#endif + // TODO: implement AF_INET6 resolving + if (s_noIPv6) { + s_noIPv6 = false; + Alarm("engine","socket",DebugWarn,"Resolver for %s is not available", + SocketAddr::lookupFamily(SocketAddr::IPv6)); + } + return false; +} + +#endif // AF_INET6 + + +const String s_ipv4NullAddr = "0.0.0.0"; +const String s_ipv6NullAddr = "::"; + +const TokenDict SocketAddr::s_familyName[] = { + {"Unknown", Unknown}, + {"IPv4", IPv4}, + {"IPv6", IPv6}, + {"Unix", Unix}, + {0,0}, +}; + SocketAddr::SocketAddr(const struct sockaddr* addr, socklen_t len) : m_address(0), m_length(0) { @@ -127,6 +220,7 @@ void SocketAddr::clear() { m_length = 0; m_host.clear(); + m_addr.clear(); void* tmp = m_address; m_address = 0; if (tmp) @@ -221,6 +315,34 @@ bool SocketAddr::host(const String& name) return false; if (name == m_host) return true; + if (!m_address) { + int f = family(name); + switch (f) { + case Unix: +#ifdef HAS_AF_UNIX + if (assign(AF_UNIX) && host(name)) + return true; +#endif + break; + case Unknown: + // fall through to set IP host + case IPv6: +#ifdef AF_INET6 + if (assign(AF_INET6) && host(name)) + return true; +#endif + if (f == IPv6) + break; + // fall through to IPv4 + case IPv4: + if (assign(AF_INET) && host(name)) + return true; + break; + } + // Restore data + clear(); + return false; + } switch (family()) { case AF_INET: { @@ -256,13 +378,29 @@ bool SocketAddr::host(const String& name) break; #ifdef AF_INET6 case AF_INET6: + if (name.find('%') >= 0) { + String tmp, iface; + splitIface(name,tmp,&iface); + if (!host(tmp)) + return false; + if (iface) +#ifndef _WINDOWS + scopeId(if_nametoindex(iface)); +#else + scopeId(iface.toInteger(0,0,0)); +#endif + return true; + } #ifdef HAVE_PTON if (inet_pton(family(),name,&((struct sockaddr_in6*)m_address)->sin6_addr) > 0) { stringify(); return true; } #endif - // TODO: implement AF_INET6 resolving + if (resolveIPv6(m_address,name)) { + stringify(); + return true; + } break; #endif // AF_INET6 #ifdef HAS_AF_UNIX @@ -277,44 +415,185 @@ bool SocketAddr::host(const String& name) return false; } -void SocketAddr::stringify() +// Retrieve the family of an address +int SocketAddr::family(const String& addr) { - m_host.clear(); - if (!(m_length && m_address)) - return; - switch (family()) { + if (!addr) + return Unknown; + bool ipv6 = false; + for (unsigned int i = 0; i < addr.length(); i++) { + if (addr[i] == '/') + return Unix; + if (addr[i] == ':') + ipv6 = true; + } + if (ipv6) + return IPv6; + in_addr_t a = inet_addr(addr); + if (a != INADDR_NONE || addr == YSTRING("255.255.255.255")) + return IPv4; + return Unknown; +} + +// Convert the host address to a String +bool SocketAddr::stringify(String& s, struct sockaddr* addr) +{ + if (!addr) + return false; + switch (addr->sa_family) { case AF_INET: #ifdef HAVE_NTOP { char buf[16]; buf[0] = '\0'; - m_host = inet_ntop(family(),&((struct sockaddr_in*)m_address)->sin_addr, + s = inet_ntop(addr->sa_family,&((struct sockaddr_in*)addr)->sin_addr, buf,sizeof(buf)); } #else s_mutex.lock(); - m_host = inet_ntoa(((struct sockaddr_in*)m_address)->sin_addr); + s = inet_ntoa(((struct sockaddr_in*)addr)->sin_addr); s_mutex.unlock(); #endif - break; + return true; #ifdef AF_INET6 case AF_INET6: #ifdef HAVE_NTOP { char buf[48]; buf[0] = '\0'; - m_host = inet_ntop(family(),&((struct sockaddr_in6*)m_address)->sin6_addr, + s = inet_ntop(addr->sa_family,&((struct sockaddr_in6*)addr)->sin6_addr, buf,sizeof(buf)); } + return true; #endif break; #endif // AF_INET6 #ifdef HAS_AF_UNIX case AF_UNIX: - m_host = ((struct sockaddr_un*)m_address)->sun_path; - break; + s = ((struct sockaddr_un*)addr)->sun_path; + return true; #endif } + return false; +} + +// Append an address to a buffer +String& SocketAddr::appendAddr(String& buf, const String& addr, int family) +{ + if (!addr) + return buf; + // Address already starts with [ + if (addr[0] == '[') { + buf << addr; + return buf; + } + if (family == Unknown && addr.find(':') >= 0) + family = IPv6; + if (family != IPv6) + buf << addr; + else + buf << "[" << addr << "]"; + return buf; +} + +// Check if an address is empty or null +bool SocketAddr::isNullAddr(const String& addr, int family) +{ + if (!addr) + return true; + switch (family) { + case IPv4: + return addr == s_ipv4NullAddr; + case IPv6: + return addr == s_ipv6NullAddr; + } + return addr == s_ipv4NullAddr || addr == s_ipv6NullAddr; +} + +// Split an interface from address +// An interface may be present in addr after a percent char (e.g. fe80::23%eth0) +void SocketAddr::splitIface(const String& buf, String& addr, String* iface) +{ + if (!buf) { + addr.clear(); + if (iface) + iface->clear(); + return; + } + int pos = buf.find('%'); + if (pos < 0) { + if (iface) + iface->clear(); + addr = buf; + } + else { + if (iface) + *iface = buf.substr(pos + 1); + addr = buf.substr(0,pos); + } +} + +// Split an address into ip/port +// Handle addr, addr:port, [addr], [addr]:port +void SocketAddr::split(const String& buf, String& addr, int& port, bool portPresent) +{ + if (!buf) { + addr.clear(); + return; + } + if (buf[0] == '[') { + int p = buf.find(']',1); + if (p >= 1) { + if (p < ((int)buf.length() - 1) && buf[p + 1] == ':') + port = buf.substr(p + 2).toInteger(); + addr.assign(buf.c_str() + 1,p - 1); + return; + } + } + int p = buf.find(':'); + if (p >= 0) { + // Check for a second ':': it may be an IPv6 address + // or we expect a port at the end of an IPv6 address + int p2 = buf.rfind(':'); + if (p == p2 || portPresent) { + port = buf.substr(p2 + 1).toInteger(); + addr.assign(buf.c_str(),p2); + } + else + addr = buf; + } + else + addr = buf; +} + +const String& SocketAddr::ipv4NullAddr() +{ + return s_ipv4NullAddr; +} + +const String& SocketAddr::ipv6NullAddr() +{ + return s_ipv6NullAddr; +} + +const TokenDict* SocketAddr::dictFamilyName() +{ + return s_familyName; +} + +void SocketAddr::stringify() +{ + m_host.clear(); + m_addr.clear(); + if (m_length && m_address) + stringify(m_host,m_address); +} + +// Store host:port in m_addr +void SocketAddr::updateAddr() const +{ + m_addr.clear(); + appendTo(m_addr,host(),port(),family()); } int SocketAddr::port() const @@ -348,6 +627,7 @@ bool SocketAddr::port(int newport) default: return false; } + m_addr.clear(); return true; } diff --git a/yateclass.h b/yateclass.h index b24ad3f3..571879a1 100644 --- a/yateclass.h +++ b/yateclass.h @@ -5451,6 +5451,26 @@ class Socket; class YATE_API SocketAddr : public GenObject { public: + /** + * Known address families + */ + enum Family { + Unknown = AF_UNSPEC, + IPv4 = AF_INET, + AfMax = AF_MAX, + AfUnsupported = AfMax, +#ifdef AF_INET6 + IPv6 = AF_INET6, +#else + IPv6 = AfUnsupported + 1, +#endif +#ifdef HAS_AF_UNIX + Unix = AF_UNIX, +#else + Unix = AfUnsupported + 2, +#endif + }; + /** * Default constructor of an empty address */ @@ -5554,6 +5574,28 @@ public: inline int family() const { return m_address ? m_address->sa_family : 0; } + /** + * Retrieve address family name + * @return Address family name + */ + inline const char* familyName() + { return lookupFamily(family()); } + + /** + * Retrieve the sin6_scope_id value of an IPv6 address + * @return The requested value (it may be 0), 0 if not available + */ + inline unsigned int scopeId() const + { return scopeId(address()); } + + /** + * Set the sin6_scope_id value of an IPv6 address + * @param val Value to set + * @return True on success, false if not available + */ + inline bool scopeId(unsigned int val) + { return scopeId(address(),val); } + /** * Get the host of this address * @return Host name as String @@ -5562,7 +5604,19 @@ public: { return m_host; } /** - * Set the hostname of this address + * Get the host and port of this address + * @return Address String (host:port) + */ + inline const String& addr() const { + if (!m_addr) + updateAddr(); + return m_addr; + } + + /** + * Set the hostname of this address. + * Guess address family if not initialized + * @param name Host to set * @return True if new host set, false if name could not be parsed */ virtual bool host(const String& name); @@ -5594,6 +5648,13 @@ public: inline socklen_t length() const { return m_length; } + /** + * Check if this address is empty or null + * @return True if the address is empty or '0.0.0.0' (IPv4) or '::' IPv6 + */ + inline bool isNullAddr() const + { return isNullAddr(m_host,family()); } + /** * Check if an address family is supported by the library * @param family Family of the address to check @@ -5601,15 +5662,161 @@ public: */ static bool supports(int family); + /** + * Retrieve the family of an address + * @param addr The address to check + * @return Address family + */ + static int family(const String& addr); + + /** + * Convert the host address to a String + * @param buf Destination buffer + * @param addr Socket address + * @return True on success, false if address family is not supported + */ + static bool stringify(String& buf, struct sockaddr* addr); + + /** + * Retrieve the scope id value of an IPv6 address + * @param addr The address + * @return The requested value (it may be 0), 0 if not available + */ + static inline unsigned int scopeId(struct sockaddr* addr) { +#ifdef AF_INET6 + if (addr && addr->sa_family == AF_INET6) + return ((struct sockaddr_in6*)addr)->sin6_scope_id; +#endif + return 0; + } + + /** + * Set the scope id value of an IPv6 address + * @param addr Address to set + * @param val Value to set + * @return True on success, false if not available + */ + static inline bool scopeId(struct sockaddr* addr, unsigned int val) { +#ifdef AF_INET6 + if (addr && addr->sa_family == AF_INET6) { + ((struct sockaddr_in6*)addr)->sin6_scope_id = val; + return true; + } +#endif + return false; + } + + /** + * Append an address to a buffer + * @param buf Destination buffer + * @param addr Address to append + * @param family Address family, set it to Unknown to detect + * @return Buffer address + */ + static String& appendAddr(String& buf, const String& addr, int family = Unknown); + + /** + * Append an address to a buffer in the form addr:port + * @param buf Destination buffer + * @param addr Address to append + * @param port Port to append + * @param family Address family, set it to Unknown to detect + * @return Buffer address + */ + static inline String& appendTo(String& buf, const String& addr, int port, + int family = Unknown) { + appendAddr(buf,addr,family) << ":" << port; + return buf; + } + + /** + * Append an address to a buffer in the form addr:port + * @param addr Address to append + * @param port Port to append + * @param family Address family, set it to Unknown to detect + * @return A String with concatenated address and port + */ + static inline String appendTo(const String& addr, int port, int family = Unknown) { + String buf; + appendTo(buf,addr,port,family); + return buf; + } + + /** + * Check if an address is empty or null + * @param addr Address to check + * @param family Address family, set it to Unknown to detect + * @return True if the address is empty or '0.0.0.0' (IPv4) or '::' IPv6 + */ + static bool isNullAddr(const String& addr, int family = Unknown); + + /** + * Split an interface from address + * An interface may be present in addr after a percent char (e.g. fe80::23%eth0) + * It is safe call this method with the same destination and source string + * @param buf Source buffer + * @param addr Destination buffer for address + * @param iface Optional pointer to be filled with interface name + */ + static void splitIface(const String& buf, String& addr, String* iface = 0); + + /** + * Split an address into ip/port. + * Handled formats: addr, addr:port, [addr], [addr]:port + * It is safe call this method with the same destination and source string + * @param buf Source buffer + * @param addr Destination buffer for address + * @param port Destination port + * @param portPresent Set it to true if the port is always present after the last ':'. + * This will handle IPv6 addresses without square brackets and port present + * (e.g. fe80::23:5060 will split into addr=fe80::23 and port=5060) + */ + static void split(const String& buf, String& addr, int& port, bool portPresent = false); + + /** + * Retrieve address family name + * @param family Address family to retrieve + * @return Address family name + */ + static inline const char* lookupFamily(int family) + { return lookup(family,s_familyName); } + + /** + * Retrieve IPv4 null address + * @return IPv4 null address (0.0.0.0) + */ + static const String& ipv4NullAddr(); + + /** + * Retrieve IPv6 null address + * @return IPv6 null address (::) + */ + static const String& ipv6NullAddr(); + + /** + * Retrieve the family name dictionary + * @return Pointer to family name dictionary + */ + static const TokenDict* dictFamilyName(); + protected: /** * Convert the host address to a String stored in m_host */ virtual void stringify(); + /** + * Store host:port in m_addr + */ + virtual void updateAddr() const; + struct sockaddr* m_address; socklen_t m_length; String m_host; + mutable String m_addr; + +private: + static const TokenDict s_familyName[]; }; /** @@ -6274,6 +6481,23 @@ public: */ virtual bool setOption(int level, int name, const void* value = 0, socklen_t length = 0); + /** + * Set or reset socket IPv6 only option. + * This option will tell to an IPv6 socket to accept only IPv6 packets. + * IPv4 packets will be accepted if disabled. + * This method will fail for non PF_INET6 sockets + * @param on True to set, false to reset it + * @return True if operation was successfull, false if an error occured + */ + inline bool setIpv6OnlyOption(bool on) { +#if defined(IPPROTO_IPV6) && defined(IPV6_V6ONLY) + int value = on ? 1 : 0; + return setOption(IPPROTO_IPV6,IPV6_V6ONLY,&value,sizeof(value)); +#else + return false; +#endif + } + /** * Get socket options * @param level Level of the option to set