From: Tommi Virtanen Date: Wed, 16 Nov 2011 21:39:29 +0000 (-0800) Subject: common/ipaddr: Find a configured IP address in given subnet. X-Git-Tag: v0.39~17^2~11^2~3 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=0f748d4c9e67cc3fbf13b43bd1c85127f67ef09c;p=ceph-ci.git common/ipaddr: Find a configured IP address in given subnet. Signed-off-by: Tommi Virtanen --- diff --git a/src/Makefile.am b/src/Makefile.am index 2800ff13eb6..011ac58bb80 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -618,6 +618,11 @@ unittest_interval_tree_LDADD = ${UNITTEST_LDADD} $(LIBGLOBAL_LDA) unittest_interval_tree_CXXFLAGS = ${AM_CXXFLAGS} ${UNITTEST_CXXFLAGS} check_PROGRAMS += unittest_interval_tree +unittest_ipaddr_SOURCES = test/test_ipaddr.cc +unittest_ipaddr_LDADD = ${UNITTEST_LDADD} $(LIBGLOBAL_LDA) +unittest_ipaddr_CXXFLAGS = ${AM_CXXFLAGS} ${UNITTEST_CXXFLAGS} +check_PROGRAMS += unittest_ipaddr + test_librbd_SOURCES = test/test_librbd.cc test_librbd_LDADD = librbd.la librados.la ${UNITTEST_STATIC_LDADD} test_librbd_CXXFLAGS = ${AM_CXXFLAGS} ${UNITTEST_CXXFLAGS} @@ -910,6 +915,7 @@ libcommon_files = \ common/hex.cc \ common/entity_name.cc \ common/ceph_crypto.cc \ + common/ipaddr.cc \ include/addr_parsing.c \ $(crush_files) diff --git a/src/common/ipaddr.cc b/src/common/ipaddr.cc new file mode 100644 index 00000000000..a2cdc922e68 --- /dev/null +++ b/src/common/ipaddr.cc @@ -0,0 +1,100 @@ +#include "include/ipaddr.h" + +#include + + +static void netmask_ipv4(const struct in_addr *addr, + unsigned int prefix_len, + struct in_addr *out) { + uint32_t mask; + + if (prefix_len >= 32) { + // also handle 32 in this branch, because >>32 is not defined by + // the C standards + mask = ~uint32_t(0); + } else { + mask = htonl(~(~uint32_t(0) >> prefix_len)); + } + out->s_addr = addr->s_addr & mask; +} + + +const struct sockaddr *find_ipv4_in_subnet(const struct ifaddrs *addrs, + const struct sockaddr_in *net, + unsigned int prefix_len) { + struct in_addr want, temp; + + netmask_ipv4(&net->sin_addr, prefix_len, &want); + + for (; addrs != NULL; addrs = addrs->ifa_next) { + + if (addrs->ifa_addr == NULL) + continue; + + if (addrs->ifa_addr->sa_family != net->sin_family) + continue; + + struct in_addr *cur = &((struct sockaddr_in*)addrs->ifa_addr)->sin_addr; + netmask_ipv4(cur, prefix_len, &temp); + + if (temp.s_addr == want.s_addr) { + return addrs->ifa_addr; + } + } + + return NULL; +} + + +static void netmask_ipv6(const struct in6_addr *addr, + unsigned int prefix_len, + struct in6_addr *out) { + if (prefix_len > 128) + prefix_len = 128; + + memcpy(out->s6_addr, addr->s6_addr, prefix_len/8); + out->s6_addr[prefix_len/8] = addr->s6_addr[prefix_len/8] & ~( 0xFF >> (prefix_len % 8) ); + if (prefix_len/8 < 15) + memset(out->s6_addr+prefix_len/8+1, 0, 16-prefix_len/8-1); +} + + +const struct sockaddr *find_ipv6_in_subnet(const struct ifaddrs *addrs, + const struct sockaddr_in6 *net, + unsigned int prefix_len) { + struct in6_addr want, temp; + + netmask_ipv6(&net->sin6_addr, prefix_len, &want); + + for (; addrs != NULL; addrs = addrs->ifa_next) { + + if (addrs->ifa_addr == NULL) + continue; + + if (addrs->ifa_addr->sa_family != net->sin6_family) + continue; + + struct in6_addr *cur = &((struct sockaddr_in6*)addrs->ifa_addr)->sin6_addr; + netmask_ipv6(cur, prefix_len, &temp); + + if (memcmp(temp.s6_addr32, want.s6_addr32, sizeof(temp)) == 0) + return addrs->ifa_addr; + } + + return NULL; +} + + +const struct sockaddr *find_ip_in_subnet(const struct ifaddrs *addrs, + const struct sockaddr *net, + unsigned int prefix_len) { + switch (net->sa_family) { + case AF_INET: + return find_ipv4_in_subnet(addrs, (struct sockaddr_in*)net, prefix_len); + + case AF_INET6: + return find_ipv6_in_subnet(addrs, (struct sockaddr_in6*)net, prefix_len); + } + + return NULL; +} diff --git a/src/include/ipaddr.h b/src/include/ipaddr.h new file mode 100644 index 00000000000..a255b3821dd --- /dev/null +++ b/src/include/ipaddr.h @@ -0,0 +1,18 @@ +#ifndef CEPH_IPADDR_H +#define CEPH_IPADDR_H + +#include +#include +#include + +/* + Find an IP address that is in the wanted subnet. + + If there are multiple matches, the first one is returned; this order + is system-dependent and should not be relied on. + */ +const struct sockaddr *find_ip_in_subnet(const struct ifaddrs *addrs, + const struct sockaddr *net, + unsigned int prefix_len); + +#endif diff --git a/src/test/test_ipaddr.cc b/src/test/test_ipaddr.cc new file mode 100644 index 00000000000..907e3a51500 --- /dev/null +++ b/src/test/test_ipaddr.cc @@ -0,0 +1,208 @@ +#include "include/ipaddr.h" +#include "gtest/gtest.h" + +#include + +static void ipv4(struct sockaddr_in *addr, const char *s) { + int err; + + addr->sin_family = AF_INET; + err = inet_pton(AF_INET, s, &addr->sin_addr); + ASSERT_EQ(1, err); +} + +static void ipv6(struct sockaddr_in6 *addr, const char *s) { + int err; + + addr->sin6_family = AF_INET6; + err = inet_pton(AF_INET6, s, &addr->sin6_addr); + ASSERT_EQ(1, err); +} + +TEST(CommonIPAddr, TestNotFound) +{ + struct ifaddrs one, two; + struct sockaddr_in a_one; + struct sockaddr_in6 a_two; + struct sockaddr_in net; + const struct sockaddr *result; + + one.ifa_next = &two; + one.ifa_addr = (struct sockaddr*)&a_one; + + two.ifa_next = NULL; + two.ifa_addr = (struct sockaddr*)&a_two; + + ipv4(&a_one, "10.11.12.13"); + ipv6(&a_two, "2001:1234:5678:90ab::cdef"); + ipv4(&net, "10.11.234.56"); + + result = find_ip_in_subnet(&one, (struct sockaddr*)&net, 24); + ASSERT_EQ(0, result); +} + +TEST(CommonIPAddr, TestV4_Simple) +{ + struct ifaddrs one, two; + struct sockaddr_in a_one; + struct sockaddr_in6 a_two; + struct sockaddr_in net; + const struct sockaddr *result; + + one.ifa_next = &two; + one.ifa_addr = (struct sockaddr*)&a_one; + + two.ifa_next = NULL; + two.ifa_addr = (struct sockaddr*)&a_two; + + ipv4(&a_one, "10.11.12.13"); + ipv6(&a_two, "2001:1234:5678:90ab::cdef"); + ipv4(&net, "10.11.12.42"); + + result = find_ip_in_subnet(&one, (struct sockaddr*)&net, 24); + ASSERT_EQ((struct sockaddr*)&a_one, result); +} + +TEST(CommonIPAddr, TestV4_Prefix25) +{ + struct ifaddrs one, two; + struct sockaddr_in a_one; + struct sockaddr_in a_two; + struct sockaddr_in net; + const struct sockaddr *result; + + one.ifa_next = &two; + one.ifa_addr = (struct sockaddr*)&a_one; + + two.ifa_next = NULL; + two.ifa_addr = (struct sockaddr*)&a_two; + + ipv4(&a_one, "10.11.12.13"); + ipv4(&a_two, "10.11.12.129"); + ipv4(&net, "10.11.12.128"); + + result = find_ip_in_subnet(&one, (struct sockaddr*)&net, 25); + ASSERT_EQ((struct sockaddr*)&a_two, result); +} + +TEST(CommonIPAddr, TestV4_PrefixTooLong) +{ + struct ifaddrs one; + struct sockaddr_in a_one; + struct sockaddr_in net; + const struct sockaddr *result; + + one.ifa_next = NULL; + one.ifa_addr = (struct sockaddr*)&a_one; + + ipv4(&a_one, "10.11.12.13"); + ipv4(&net, "10.11.12.12"); + + result = find_ip_in_subnet(&one, (struct sockaddr*)&net, 42); + ASSERT_EQ(0, result); +} + +TEST(CommonIPAddr, TestV4_PrefixZero) +{ + struct ifaddrs one, two; + struct sockaddr_in6 a_one; + struct sockaddr_in a_two; + struct sockaddr_in net; + const struct sockaddr *result; + + one.ifa_next = &two; + one.ifa_addr = (struct sockaddr*)&a_one; + + two.ifa_next = NULL; + two.ifa_addr = (struct sockaddr*)&a_two; + + ipv6(&a_one, "2001:1234:5678:900F::cdef"); + ipv4(&a_two, "10.1.2.3"); + ipv4(&net, "255.0.1.2"); + + result = find_ip_in_subnet(&one, (struct sockaddr*)&net, 0); + ASSERT_EQ((struct sockaddr*)&a_two, result); +} + +TEST(CommonIPAddr, TestV6_Simple) +{ + struct ifaddrs one, two; + struct sockaddr_in a_one; + struct sockaddr_in6 a_two; + struct sockaddr_in6 net; + const struct sockaddr *result; + + one.ifa_next = &two; + one.ifa_addr = (struct sockaddr*)&a_one; + + two.ifa_next = NULL; + two.ifa_addr = (struct sockaddr*)&a_two; + + ipv4(&a_one, "10.11.12.13"); + ipv6(&a_two, "2001:1234:5678:90ab::cdef"); + ipv6(&net, "2001:1234:5678:90ab::dead:beef"); + + result = find_ip_in_subnet(&one, (struct sockaddr*)&net, 64); + ASSERT_EQ((struct sockaddr*)&a_two, result); +} + +TEST(CommonIPAddr, TestV6_Prefix57) +{ + struct ifaddrs one, two; + struct sockaddr_in6 a_one; + struct sockaddr_in6 a_two; + struct sockaddr_in6 net; + const struct sockaddr *result; + + one.ifa_next = &two; + one.ifa_addr = (struct sockaddr*)&a_one; + + two.ifa_next = NULL; + two.ifa_addr = (struct sockaddr*)&a_two; + + ipv6(&a_one, "2001:1234:5678:900F::cdef"); + ipv6(&a_two, "2001:1234:5678:90ab::cdef"); + ipv6(&net, "2001:1234:5678:90ab::dead:beef"); + + result = find_ip_in_subnet(&one, (struct sockaddr*)&net, 57); + ASSERT_EQ((struct sockaddr*)&a_two, result); +} + +TEST(CommonIPAddr, TestV6_PrefixTooLong) +{ + struct ifaddrs one; + struct sockaddr_in6 a_one; + struct sockaddr_in6 net; + const struct sockaddr *result; + + one.ifa_next = NULL; + one.ifa_addr = (struct sockaddr*)&a_one; + + ipv6(&a_one, "2001:1234:5678:900F::cdef"); + ipv6(&net, "2001:1234:5678:900F::cdee"); + + result = find_ip_in_subnet(&one, (struct sockaddr*)&net, 9000); + ASSERT_EQ(0, result); +} + +TEST(CommonIPAddr, TestV6_PrefixZero) +{ + struct ifaddrs one, two; + struct sockaddr_in a_one; + struct sockaddr_in6 a_two; + struct sockaddr_in6 net; + const struct sockaddr *result; + + one.ifa_next = &two; + one.ifa_addr = (struct sockaddr*)&a_one; + + two.ifa_next = NULL; + two.ifa_addr = (struct sockaddr*)&a_two; + + ipv4(&a_one, "10.2.3.4"); + ipv6(&a_two, "2001:f00b::1"); + ipv6(&net, "ff00::1"); + + result = find_ip_in_subnet(&one, (struct sockaddr*)&net, 0); + ASSERT_EQ((struct sockaddr*)&a_two, result); +}