aboutsummaryrefslogtreecommitdiffstats
path: root/main/netsock2.c
diff options
context:
space:
mode:
authormmichelson <mmichelson@f38db490-d61c-443f-a65b-d21fe96a405b>2010-07-08 22:08:07 +0000
committermmichelson <mmichelson@f38db490-d61c-443f-a65b-d21fe96a405b>2010-07-08 22:08:07 +0000
commitc3c2e5edfd715b3a99aac1567623a0b1b7d49de0 (patch)
treec05335b563c3f7cb9a3edbf3e101d8e1b80e0be4 /main/netsock2.c
parentaf344f1a5be5b43f1d10b95ea6e57ebfa761cf50 (diff)
Add IPv6 to Asterisk.
This adds a generic API for accommodating IPv6 and IPv4 addresses within Asterisk. While many files have been updated to make use of the API, chan_sip and the RTP code are the files which actually support IPv6 addresses at the time of this commit. The way has been paved for easier upgrading for other files in the near future, though. Big thanks go to Simon Perrault, Marc Blanchet, and Jean-Philippe Dionne for their hard work on this. (closes issue #17565) Reported by: russell Patches: asteriskv6-test-report.pdf uploaded by russell (license 2) Review: https://reviewboard.asterisk.org/r/743 git-svn-id: http://svn.digium.com/svn/asterisk/trunk@274783 f38db490-d61c-443f-a65b-d21fe96a405b
Diffstat (limited to 'main/netsock2.c')
-rw-r--r--main/netsock2.c499
1 files changed, 499 insertions, 0 deletions
diff --git a/main/netsock2.c b/main/netsock2.c
new file mode 100644
index 000000000..4d93a911b
--- /dev/null
+++ b/main/netsock2.c
@@ -0,0 +1,499 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2010, Digium, Inc.
+ *
+ * Viagénie <asteriskv6@viagenie.ca>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Network socket handling
+ *
+ * \author Viagénie <asteriskv6@viagenie.ca>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/config.h"
+#include "asterisk/netsock2.h"
+#include "asterisk/utils.h"
+#include "asterisk/threadstorage.h"
+
+static int ast_sockaddr_ipv4_mapped(const struct ast_sockaddr *addr, struct ast_sockaddr *ast_mapped)
+{
+ const struct sockaddr_in6 *sin6;
+ struct sockaddr_in sin4;
+
+ if (!ast_sockaddr_is_ipv6(addr)) {
+ return 0;
+ }
+
+ if (!ast_sockaddr_is_ipv4_mapped(addr)) {
+ return 0;
+ }
+
+ sin6 = (const struct sockaddr_in6*)&addr->ss;
+
+ memset(&sin4, 0, sizeof(sin4));
+ sin4.sin_family = AF_INET;
+ sin4.sin_port = sin6->sin6_port;
+ sin4.sin_addr.s_addr = ((uint32_t *)&sin6->sin6_addr)[3];
+
+ ast_sockaddr_from_sin(ast_mapped, &sin4);
+
+ return 1;
+}
+
+
+AST_THREADSTORAGE(ast_sockaddr_stringify_buf);
+
+char *ast_sockaddr_stringify_fmt(const struct ast_sockaddr *sa, int format)
+{
+ struct ast_sockaddr sa_ipv4;
+ const struct ast_sockaddr *sa_tmp;
+ char host[NI_MAXHOST];
+ char port[NI_MAXSERV];
+ struct ast_str *str;
+ int e;
+ static const size_t size = sizeof(host) - 1 + sizeof(port) - 1 + 4;
+
+
+ if (ast_sockaddr_isnull(sa)) {
+ return "(null)";
+ }
+
+ if (!(str = ast_str_thread_get(&ast_sockaddr_stringify_buf, size))) {
+ return "";
+ }
+
+ if (ast_sockaddr_ipv4_mapped(sa, &sa_ipv4)) {
+ sa_tmp = &sa_ipv4;
+ } else {
+ sa_tmp = sa;
+ }
+
+ if ((e = getnameinfo((struct sockaddr *)&sa_tmp->ss, sa->len,
+ format & AST_SOCKADDR_STR_ADDR ? host : NULL,
+ format & AST_SOCKADDR_STR_ADDR ? sizeof(host) : 0,
+ format & AST_SOCKADDR_STR_PORT ? port : 0,
+ format & AST_SOCKADDR_STR_PORT ? sizeof(port): 0,
+ NI_NUMERICHOST | NI_NUMERICSERV))) {
+ ast_log(LOG_ERROR, "getnameinfo(): %s\n", gai_strerror(e));
+ return "";
+ }
+
+ switch (format) {
+ case AST_SOCKADDR_STR_DEFAULT:
+ ast_str_set(&str, 0, sa_tmp->ss.ss_family == AF_INET6 ?
+ "[%s]:%s" : "%s:%s", host, port);
+ break;
+ case AST_SOCKADDR_STR_ADDR:
+ ast_str_set(&str, 0, "%s", host);
+ break;
+ case AST_SOCKADDR_STR_HOST:
+ ast_str_set(&str, 0,
+ sa_tmp->ss.ss_family == AF_INET6 ? "[%s]" : "%s", host);
+ break;
+ case AST_SOCKADDR_STR_PORT:
+ ast_str_set(&str, 0, "%s", port);
+ break;
+ default:
+ ast_log(LOG_ERROR, "Invalid format\n");
+ return "";
+ }
+
+ return ast_str_buffer(str);
+}
+
+int static _ast_sockaddr_parse(char *str, char **host, char **port, int flags)
+{
+ char *s = str;
+
+ ast_debug(5, "Splitting '%s' gives...\n", str);
+ *host = NULL;
+ *port = NULL;
+ if (*s == '[') {
+ *host = ++s;
+ for (; *s && *s != ']'; ++s) {
+ }
+ if (*s == ']') {
+ *s++ = '\0';
+ }
+ if (*s == ':') {
+ *port = s + 1;
+ }
+ } else {
+ *host = s;
+ for (; *s; ++s) {
+ if (*s == ':') {
+ if (*port) {
+ *port = NULL;
+ break;
+ } else {
+ *port = s;
+ }
+ }
+ }
+ if (*port) {
+ **port = '\0';
+ ++*port;
+ }
+ }
+ ast_debug(5, "...host '%s' and port '%s'.\n", *host, *port);
+
+ switch (flags & PARSE_PORT_MASK) {
+ case PARSE_PORT_IGNORE:
+ *port = NULL;
+ break;
+ case PARSE_PORT_REQUIRE:
+ if (*port == NULL) {
+ ast_log(LOG_WARNING, "missing port\n");
+ return 0;
+ }
+ break;
+ case PARSE_PORT_FORBID:
+ if (*port != NULL) {
+ ast_log(LOG_WARNING, "port disallowed\n");
+ return 0;
+ }
+ break;
+ }
+
+ return 1;
+}
+
+
+
+int ast_sockaddr_parse(struct ast_sockaddr *addr, const char *str, int flags)
+{
+ struct addrinfo hints;
+ struct addrinfo *res;
+ char *s;
+ char *host;
+ char *port;
+ int e;
+
+ s = ast_strdupa(str);
+ if (!_ast_sockaddr_parse(s, &host, &port, flags)) {
+ return 0;
+ }
+
+ memset(&hints, 0, sizeof(hints));
+ /* Hint to get only one entry from getaddrinfo */
+ hints.ai_socktype = SOCK_DGRAM;
+
+ hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
+ if ((e = getaddrinfo(host, port, &hints, &res))) {
+ ast_log(LOG_ERROR, "getaddrinfo(\"%s\", \"%s\", ...): %s\n",
+ host, S_OR(port, "(null)"), gai_strerror(e));
+ return 0;
+ }
+
+ /*
+ * I don't see how this could be possible since we're not resolving host
+ * names. But let's be careful...
+ */
+ if (res->ai_next != NULL) {
+ ast_log(LOG_WARNING, "getaddrinfo() returned multiple "
+ "addresses. Ignoring all but the first.\n");
+ }
+
+ addr->len = res->ai_addrlen;
+ memcpy(&addr->ss, res->ai_addr, addr->len);
+
+ freeaddrinfo(res);
+
+ return 1;
+}
+
+int ast_sockaddr_resolve(struct ast_sockaddr **addrs, const char *str,
+ int flags, int family)
+{
+ struct addrinfo hints, *res, *ai;
+ char *s, *host, *port;
+ int e, i, res_cnt;
+
+ s = ast_strdupa(str);
+ if (!_ast_sockaddr_parse(s, &host, &port, flags)) {
+ return 0;
+ }
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = family;
+ hints.ai_socktype = SOCK_DGRAM;
+
+ if ((e = getaddrinfo(host, port, &hints, &res))) {
+ ast_log(LOG_ERROR, "getaddrinfo(\"%s\", \"%s\", ...): %s\n",
+ host, S_OR(port, "(null)"), gai_strerror(e));
+ return 0;
+ }
+
+ res_cnt = 0;
+ for (ai = res; ai; ai = ai->ai_next) {
+ res_cnt++;
+ }
+
+ if ((*addrs = ast_malloc(res_cnt * sizeof(struct ast_sockaddr))) == NULL) {
+ res_cnt = 0;
+ goto cleanup;
+ }
+
+ i = 0;
+ for (ai = res; ai; ai = ai->ai_next) {
+ (*addrs)[i].len = ai->ai_addrlen;
+ memcpy(&(*addrs)[i].ss, ai->ai_addr, ai->ai_addrlen);
+ ++i;
+ }
+
+cleanup:
+ freeaddrinfo(res);
+ return res_cnt;
+}
+
+int ast_sockaddr_cmp(const struct ast_sockaddr *a, const struct ast_sockaddr *b)
+{
+ const struct ast_sockaddr *a_tmp, *b_tmp;
+ struct ast_sockaddr ipv4_mapped;
+
+ a_tmp = a;
+ b_tmp = b;
+
+ if (a_tmp->len != b_tmp->len) {
+ if (ast_sockaddr_ipv4_mapped(a, &ipv4_mapped)) {
+ a_tmp = &ipv4_mapped;
+ } else if (ast_sockaddr_ipv4_mapped(b, &ipv4_mapped)) {
+ b_tmp = &ipv4_mapped;
+ }
+ }
+
+ if (a_tmp->len < b_tmp->len) {
+ return -1;
+ } else if (a_tmp->len > b_tmp->len) {
+ return 1;
+ }
+
+ return memcmp(&a_tmp->ss, &b_tmp->ss, a_tmp->len);
+}
+
+int ast_sockaddr_cmp_addr(const struct ast_sockaddr *a, const struct ast_sockaddr *b)
+{
+ const struct ast_sockaddr *a_tmp, *b_tmp;
+ struct ast_sockaddr ipv4_mapped;
+ const struct in_addr *ip4a, *ip4b;
+ const struct in6_addr *ip6a, *ip6b;
+ int ret = -1;
+
+ a_tmp = a;
+ b_tmp = b;
+
+ if (a_tmp->len != b_tmp->len) {
+ if (ast_sockaddr_ipv4_mapped(a, &ipv4_mapped)) {
+ a_tmp = &ipv4_mapped;
+ } else if (ast_sockaddr_ipv4_mapped(b, &ipv4_mapped)) {
+ b_tmp = &ipv4_mapped;
+ }
+ }
+
+ if (a->len < b->len) {
+ ret = -1;
+ } else if (a->len > b->len) {
+ ret = 1;
+ }
+
+ switch (a_tmp->ss.ss_family) {
+ case AF_INET:
+ ip4a = &((const struct sockaddr_in*)&a_tmp->ss)->sin_addr;
+ ip4b = &((const struct sockaddr_in*)&b_tmp->ss)->sin_addr;
+ ret = memcmp(ip4a, ip4b, sizeof(*ip4a));
+ break;
+ case AF_INET6:
+ ip6a = &((const struct sockaddr_in6*)&a_tmp->ss)->sin6_addr;
+ ip6b = &((const struct sockaddr_in6*)&b_tmp->ss)->sin6_addr;
+ ret = memcmp(ip6a, ip6b, sizeof(*ip6a));
+ break;
+ }
+ return ret;
+}
+
+uint16_t ast_sockaddr_port(const struct ast_sockaddr *addr)
+{
+ if (addr->ss.ss_family == AF_INET &&
+ addr->len == sizeof(struct sockaddr_in)) {
+ return ntohs(((struct sockaddr_in *)&addr->ss)->sin_port);
+ } else if (addr->ss.ss_family == AF_INET6 &&
+ addr->len == sizeof(struct sockaddr_in6)) {
+ return ntohs(((struct sockaddr_in6 *)&addr->ss)->sin6_port);
+ }
+ ast_log(LOG_ERROR, "Not an IPv4 nor IPv6 address, cannot get port.\n");
+ return 0;
+}
+
+void ast_sockaddr_set_port(struct ast_sockaddr *addr, uint16_t port)
+{
+ if (addr->ss.ss_family == AF_INET &&
+ addr->len == sizeof(struct sockaddr_in)) {
+ ((struct sockaddr_in *)&addr->ss)->sin_port = htons(port);
+ } else if (addr->ss.ss_family == AF_INET6 &&
+ addr->len == sizeof(struct sockaddr_in6)) {
+ ((struct sockaddr_in6 *)&addr->ss)->sin6_port = htons(port);
+ } else {
+ ast_log(LOG_ERROR,
+ "Not an IPv4 nor IPv6 address, cannot set port.\n");
+ }
+}
+
+uint32_t ast_sockaddr_ipv4(const struct ast_sockaddr *addr)
+{
+ const struct sockaddr_in *sin = (struct sockaddr_in *)&addr->ss;
+ return ntohl(sin->sin_addr.s_addr);
+}
+
+int ast_sockaddr_is_ipv4(const struct ast_sockaddr *addr)
+{
+ return addr->ss.ss_family == AF_INET &&
+ addr->len == sizeof(struct sockaddr_in);
+}
+
+int ast_sockaddr_is_ipv4_mapped(const struct ast_sockaddr *addr)
+{
+ const struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&addr->ss;
+ return addr->len && IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr);
+}
+
+int ast_sockaddr_is_ipv6(const struct ast_sockaddr *addr)
+{
+ return addr->ss.ss_family == AF_INET6 &&
+ addr->len == sizeof(struct sockaddr_in6);
+}
+
+int ast_sockaddr_is_any(const struct ast_sockaddr *addr)
+{
+ return (ast_sockaddr_is_ipv4(addr) &&
+ ((const struct sockaddr_in *)&addr->ss)->sin_addr.s_addr ==
+ INADDR_ANY) ||
+ (ast_sockaddr_is_ipv6(addr) &&
+ IN6_IS_ADDR_UNSPECIFIED(&((const struct sockaddr_in6 *)&addr->ss)->sin6_addr));
+}
+
+int ast_sockaddr_hash(const struct ast_sockaddr *addr)
+{
+ /*
+ * For IPv4, return the IP address as-is. For IPv6, return the last 32
+ * bits.
+ */
+ switch (addr->ss.ss_family) {
+ case AF_INET:
+ return ((const struct sockaddr_in *)&addr->ss)->sin_addr.s_addr;
+ case AF_INET6:
+ return ((uint32_t *)&((const struct sockaddr_in6 *)&addr->ss)->sin6_addr)[3];
+ default:
+ ast_log(LOG_ERROR, "Unknown address family '%d'.\n",
+ addr->ss.ss_family);
+ return 0;
+ }
+}
+
+int ast_accept(int sockfd, struct ast_sockaddr *addr)
+{
+ addr->len = sizeof(addr->ss);
+ return accept(sockfd, (struct sockaddr *)&addr->ss, &addr->len);
+}
+
+int ast_bind(int sockfd, const struct ast_sockaddr *addr)
+{
+ return bind(sockfd, (const struct sockaddr *)&addr->ss, addr->len);
+}
+
+int ast_connect(int sockfd, const struct ast_sockaddr *addr)
+{
+ return connect(sockfd, (const struct sockaddr *)&addr->ss, addr->len);
+}
+
+int ast_getsockname(int sockfd, struct ast_sockaddr *addr)
+{
+ addr->len = sizeof(addr->ss);
+ return getsockname(sockfd, (struct sockaddr *)&addr->ss, &addr->len);
+}
+
+ssize_t ast_recvfrom(int sockfd, void *buf, size_t len, int flags,
+ struct ast_sockaddr *src_addr)
+{
+ src_addr->len = sizeof(src_addr->ss);
+ return recvfrom(sockfd, buf, len, flags,
+ (struct sockaddr *)&src_addr->ss, &src_addr->len);
+}
+
+ssize_t ast_sendto(int sockfd, const void *buf, size_t len, int flags,
+ const struct ast_sockaddr *dest_addr)
+{
+ return sendto(sockfd, buf, len, flags,
+ (const struct sockaddr *)&dest_addr->ss, dest_addr->len);
+}
+
+int ast_set_qos(int sockfd, int tos, int cos, const char *desc)
+{
+ int res;
+
+ if ((res = setsockopt(sockfd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)))) {
+ ast_log(LOG_WARNING, "Unable to set %s TOS to %d (may be you have no "
+ "root privileges): %s\n", desc, tos, strerror(errno));
+ } else if (tos) {
+ ast_verb(2, "Using %s TOS bits %d\n", desc, tos);
+ }
+
+#ifdef linux
+ if (setsockopt(sockfd, SOL_SOCKET, SO_PRIORITY, &cos, sizeof(cos))) {
+ ast_log(LOG_WARNING, "Unable to set %s CoS to %d: %s\n", desc, cos,
+ strerror(errno));
+ } else if (cos) {
+ ast_verb(2, "Using %s CoS mark %d\n", desc, cos);
+ }
+#endif
+
+ return res;
+}
+
+int ast_sockaddr_to_sin(const struct ast_sockaddr *addr,
+ struct sockaddr_in *sin)
+{
+ if (ast_sockaddr_isnull(addr)) {
+ memset(sin, 0, sizeof(*sin));
+ return 1;
+ }
+
+ if (addr->len != sizeof(*sin)) {
+ ast_log(LOG_ERROR, "Bad address cast to IPv4\n");
+ return 0;
+ }
+
+ if (addr->ss.ss_family != AF_INET) {
+ ast_log(LOG_DEBUG, "Address family is not AF_INET\n");
+ }
+
+ *sin = *(struct sockaddr_in *)&addr->ss;
+ return 1;
+}
+
+void ast_sockaddr_from_sin(struct ast_sockaddr *addr, const struct sockaddr_in *sin)
+{
+ *((struct sockaddr_in *)&addr->ss) = *sin;
+
+ if (addr->ss.ss_family != AF_INET) {
+ ast_log(LOG_DEBUG, "Address family is not AF_INET\n");
+ }
+
+ addr->len = sizeof(*sin);
+}