[svn-commits] mmichelson: branch mmichelson/acl-v6 r276203 - /team/mmichelson/acl-v6/main/

SVN commits to the Digium repositories svn-commits at lists.digium.com
Tue Jul 13 16:17:12 CDT 2010


Author: mmichelson
Date: Tue Jul 13 16:17:01 2010
New Revision: 276203

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=276203
Log:
Changes to acl.c to deal with both IPv4 and IPv6 host access rules.

Since when applying an HA, it is not clear if the address has been
given by the kernel or parsed from something like a SIP packet, care
needs to be taken to be sure rules are applied accurately. The way I've
done this is to internally store all host access rules as IPv6 addresses.
If a rule is written to apply to an IPv4 address, we store it as an
IPv4-mapped address.

The code does not compile at the moment. While there are likely some errors
in acl.c, the bigger problem is that there is code which has not been
changed to use the new ast_append_ha and ast_apply_ha calls.


Modified:
    team/mmichelson/acl-v6/main/acl.c

Modified: team/mmichelson/acl-v6/main/acl.c
URL: http://svnview.digium.com/svn/asterisk/team/mmichelson/acl-v6/main/acl.c?view=diff&rev=276203&r1=276202&r2=276203
==============================================================================
--- team/mmichelson/acl-v6/main/acl.c (original)
+++ team/mmichelson/acl-v6/main/acl.c Tue Jul 13 16:17:01 2010
@@ -229,8 +229,8 @@
 /* Copy HA structure */
 void ast_copy_ha(const struct ast_ha *from, struct ast_ha *to)
 {
-	memcpy(&to->netaddr, &from->netaddr, sizeof(from->netaddr));
-	memcpy(&to->netmask, &from->netmask, sizeof(from->netmask));
+	ast_sockaddr_copy(&to->addr, &from->addr);
+	ast_sockaddr_copy(&to->netmask, &from->netmask);
 	to->sense = from->sense;
 }
 
@@ -269,6 +269,148 @@
 		prev = current;                     /* Save pointer to this object */
 	}
 	return ret;                             /* Return start of list */
+}
+
+#define V6_WORD(sin6, index) ((uint32_t *)&((sin6)->sin_addr))[(index)]
+
+/*!
+ * \brief
+ * Make an IPv4 address into an IPv4-mapped IPv6 address
+ *
+ * \details
+ * To simplify application of ACLs, we store all rules internally
+ * as IPv6 addresses. If a rule is based on an IPv4 address, then we
+ * will use this function to store the representation as an IPv4-mapped
+ * IPv6 address.
+ *
+ * \param input An IPv4 ast_sockaddr to convert.
+ * \param[out] output The resulting IPv4-mapped IPv6 address.
+ * \retval void
+ */
+void make_v4_mapped(struct ast_sockaddr *input, struct ast_sockaddr *output)
+{
+	struct sockaddr_in6 sin6;
+	static const uint32_t prefix = 0x0000FFFF;
+
+	sin6.sin6_family = AF_INET6;
+	V6_WORD(&sin6, 0) = htonl(0);
+	V6_WORD(&sin6, 1) = htonl(0);
+	V6_WORD(&sin6, 2) = htonl(prefix);
+	V6_WORD(&sin6, 3) = htonl(ast_sockaddr_ipv4(input));
+
+	memcpy(&output->ss, &sin6, sizeof(sin6));
+	output->len = sizeof(sin6);
+}
+
+/*!
+ * \brief
+ * Convert an IPv4 netmask into an IPv6 netmask
+ *
+ * \details
+ * ast_ha uses IPv6 internally for its rules, even though IPv4
+ * addresses can be specified in a rule. With netmasks, we need
+ * to pad the first 96 bits of the IPv6 address with all ones to
+ * achieve our goal.
+ *
+ * \param input The IPv4 netmask to convert
+ * \param output The resulting IPv6 netmask
+ * \retval void
+ */ 
+void map_mask(struct ast_sockaddr *input, struct ast_sockaddr *output)
+{
+	struct sockaddr_in6 sin6;
+
+	sin6.sin6_family = AF_INET6;
+	memset(&sin6->s6_addr, 96, 0xFF);
+	V6_WORD(&sin6, 3) = htonl(ast_sockaddr_ipv4(input));
+
+	memcpy(&output->ss, &sin6, sizeof(sin6));
+	output->len = sizeof(sin6);
+}
+
+/*!
+ * \brief
+ * Apply a netmask to an address and store the result in a separate structure.
+ *
+ * When dealing with IPv6 addresses, one cannot apply a netmask with a simple
+ * logical and operation. Furthermore, the incoming address may be an IPv4 address
+ * and need to be mapped properly before attempting to apply a rule.
+ */
+void apply_netmask(struct ast_sockaddr *addr, struct ast_sockaddr *netmask,
+		struct ast_sockaddr *result)
+{
+	struct sockaddr_in6 addr6;
+	struct sockaddr_in6 mask6 = (struct sockaddr_in6)addr->ss;
+	struct sockaddr_in6 result6;
+
+	if (ast_sockaddr_is_ipv4(addr)) {
+		make_v4_mapped(addr, addr);
+	}
+
+	addr6 = (struct sockaddr_in6)addr->ss;
+
+	result6.sin6_family = AF_INET6;
+	for (i = 0; i < 4; ++i) {
+		V6_WORD(&result6, i) = V6_WORD(&addr6, i) & V6_WORD(&mask6, i);
+	}
+	memcpy(&result->ss, &result6, sizeof(result6));
+	result->len = sizeof(result6);
+}
+
+/*!
+ * \brief
+ * Parse a netmask in CIDR notation
+ *
+ * \details
+ * For a mask of an IPv4 address, this should be a number between 0 and 32. For
+ * a mask of an IPv6 address, this should be a number between 0 and 128. This
+ * function creates an IPv6 ast_sockaddr from the given netmask. For masks of
+ * IPv4 addresses, this is accomplished by adding 96 to the original netmask.
+ *
+ * \param[out] addr The ast_sockaddr produced from the CIDR netmask
+ * \param is_v4 Tells if the address we are masking is IPv4.
+ * \param mask_str The CIDR mask to convert
+ */
+int parse_cidr_mask(struct ast_sockaddr *addr, int is_v4, const char *mask_str)
+{
+	int mask;
+	struct sockaddr_in6 sin6;
+
+	if (sscanf(mask_str, "%30d", &mask) != 1) {
+		return -1;
+	}
+
+	if (is_v4) {
+		if (mask < 0 || mask > 32) {
+			return -1;
+		} else {
+			mask += 96;
+		}
+	} else if (mask < 0 || mask > 128) {
+		return -1;
+	}
+
+	sin6.sin6_family = AF_INET6;
+	for (i = 0; i < 4; ++i) {
+		if (mask >= 32) {
+			V6_WORD(&sin6, i) = htonl(0xFFFFFFFF);
+			mask -= 32;
+		else if (mask > 0) {
+			V6_WORD(&sin6, i) = htonl(0xFFFFFFFF << (32 - mask));
+			/* Set mask to 0 so the remaining parts of the address
+			 * Get filled in properly with zeros
+			 */
+			mask = 0;
+		} else {
+			/* Mask is 0. Special case to deal with unpredictable
+			 * behavior when trying to shift more than 31 bits
+			 */
+			V6_WORD(&sin6, i) = htonl(0);
+		}
+	}
+	memcpy(&addr->ss, &sin6, sizeof(sin6));
+	addr->len = sizeof(sin6);
+	return 0;
 }
 
 struct ast_ha *ast_append_ha(const char *sense, const char *stuff, struct ast_ha *path, int *error)
@@ -279,6 +421,10 @@
 	struct ast_ha *ret;
 	int x;
 	char *tmp = ast_strdupa(stuff);
+	char *address = NULL, *mask = NULL;
+	struct ast_sockaddr addr_sock, mask_sock;
+	int addr_is_v4;
+	int mask_is_v4;
 
 	ret = path;
 	while (path) {
@@ -290,53 +436,52 @@
 		return ret;
 	}
 
-	if (!(nm = strchr(tmp, '/'))) {
-		/* assume /32. Yes, htonl does not do anything for this particular mask
-		   but we better use it to show we remember about byte order */
-		ha->netmask.s_addr = htonl(0xFFFFFFFF);
+	address = strsep(&tmp, "/");
+	if (!address) {
+		address = tmp;
 	} else {
-		*nm = '\0';
-		nm++;
-
-		if (!strchr(nm, '.')) {
-			if ((sscanf(nm, "%30d", &x) == 1) && (x >= 0) && (x <= 32)) {
-				if (x == 0) {
-					/* This is special-cased to prevent unpredictable
-					 * behavior of shifting left 32 bits
-					 */
-					ha->netmask.s_addr = 0;
-				} else {
-					ha->netmask.s_addr = htonl(0xFFFFFFFF << (32 - x));
-				}
-			} else {
-				ast_log(LOG_WARNING, "Invalid CIDR in %s\n", stuff);
-				ast_free(ha);
-				if (error) {
-					*error = 1;
-				}
-				return ret;
-			}
-		} else if (!inet_aton(nm, &ha->netmask)) {
-			ast_log(LOG_WARNING, "Invalid mask in %s\n", stuff);
-			ast_free(ha);
-			if (error) {
-				*error = 1;
-			}
+		mask = tmp;
+	}
+
+	if (!ast_sockaddr_parse(&addr_sock, address, PARSE_PORT_FORBID)) {
+		ast_log(LOG_WARNING, "Invalid IP address: %s\n", address);
+		ast_free_ha(ha);
+		return ret;
+	}
+
+	addr_is_v4 = ast_sockaddr_is_ipv4(&addr_sock);
+	if (addr_is_v4) {
+		make_v4_mapped(&addr_sock, &ha->addr);
+	}
+
+	if (!mask) {
+		/* No need to check return value for hard-coded address */
+		ast_sockaddr_parse(&ha->netmask, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", PARSE_PORT_FORBID);
+	} else if (strchr(mask, ':') || strchr(mask, '.')) {
+		int mask_is_v4;
+		struct ast_sockaddr mask_sock;
+		/* Mask is of x.x.x.x or x:x:x:x:x:x:x:x variety */
+		if (ast_sockaddr_parse(&mask_sock, mask, PARSE_PORT_FORBID)) {
+			ast_log(LOG_WARNING, "Invalid netmask: %s\n", mask);
+			ast_free_ha(ha);
 			return ret;
 		}
-	}
-
-	if (!inet_aton(tmp, &ha->netaddr)) {
-		ast_log(LOG_WARNING, "Invalid IP address in %s\n", stuff);
-		ast_free(ha);
-		if (error) {
-			*error = 1;
-		}
+		mask_is_v4 = ast_sockadddr_is_ipv4(&mask_sock);
+		if (addr_is_v4 ^ mask_is_v4) {
+			ast_log(LOG_WARNING, "Address and mask are not using same address scheme.\n");
+			ast_free_ha(ha);
+			return ret;
+		}
+		if (mask_is_v4) {
+			map_mask(&mask_sock, &ha->netmask);
+		}
+	} else if (parse_cidr_mask(&ha->netmask, addr_is_v4, mask)) {
+		ast_log(LOG_WARNING, "Invalid CIDR netmask: %s", mask);
+		ast_free_ha(ha);
 		return ret;
 	}
 
-	ha->netaddr.s_addr &= ha->netmask.s_addr;
-
+	apply_netmask(&ha->addr, &ha->netmask, &ha->addr);
 	ha->sense = strncasecmp(sense, "p", 1) ? AST_SENSE_DENY : AST_SENSE_ALLOW;
 
 	ha->next = NULL;
@@ -351,11 +496,12 @@
 	return ret;
 }
 
-int ast_apply_ha(struct ast_ha *ha, struct sockaddr_in *sin)
+int ast_apply_ha(struct ast_ha *ha, struct ast_sockaddr *addr)
 {
 	/* Start optimistic */
 	int res = AST_SENSE_ALLOW;
 	while (ha) {
+		struct ast_sockaddr result;
 #if 0	/* debugging code */
 		char iabuf[INET_ADDRSTRLEN];
 		char iabuf2[INET_ADDRSTRLEN];
@@ -366,7 +512,8 @@
 #endif
 		/* For each rule, if this address and the netmask = the net address
 		   apply the current rule */
-		if ((sin->sin_addr.s_addr & ha->netmask.s_addr) == ha->netaddr.s_addr) {
+		apply_netmask(addr, &ha->netmask, &result);
+		if (!ast_sockaddr_cmp_addr(&result, &ha->addr)) {
 			res = ha->sense;
 		}
 		ha = ha->next;




More information about the svn-commits mailing list