From cacc1311626aa8b2dfe9f937cf1b14bb534a8937 Mon Sep 17 00:00:00 2001 From: Niels de Vos Date: Sun, 29 Jun 2014 16:30:30 +0200 Subject: gNFS: Fix multi-homed m/c issue in NFS subdir auth NFS subdir authentication doesn't correctly handle multi-homed (host with multiple NIC having multiple IP addr) OR multi-protocol (IPv4 and IPv6) network addresses. When user/admin sets HOSTNAME in gluster CLI for NFS subdir auth, mnt3_verify_auth() routine does not iterate over all the resolved n/w addrs returned by getaddrinfo() n/w API. Instead, it just tests with the one returned first. 1. Iterate over all the n/w addrs (linked list) returned by getaddrinfo(). 2. Move the n/w mask calculation part to mnt3_export_fill_hostspec() instead of doing it in mnt3_verify_auth() i.e. calculating for each mount request. It does not change for MOUNT req. 3. Integrate "subnet support code rpc-auth.addr..allow" and "NFS subdir auth code" to remove code duplication. Cherry-picked from commit d3f0de90d0c5166e63f5764d2f21703fd29ce976: > Change-Id: I26b0def52c22cda35ca11766afca3df5fd4360bf > BUG: 1102293 > Signed-off-by: Santosh Kumar Pradhan > Reviewed-on: http://review.gluster.org/8048 > Reviewed-by: Rajesh Joseph > Tested-by: Gluster Build System > Reviewed-by: Niels de Vos Change-Id: Ie92a8ac602bec2cd77268acb7b23ad8ba3c52f5f BUG: 1112980 Signed-off-by: Niels de Vos Reviewed-on: http://review.gluster.org/8198 Tested-by: Gluster Build System Reviewed-by: Santosh Pradhan --- libglusterfs/src/common-utils.c | 81 +++++++++++++++++ libglusterfs/src/common-utils.h | 3 + rpc/rpc-lib/src/rpcsvc.c | 66 ++++++++++++++ xlators/nfs/server/src/mount3.c | 193 ++++++++++++++++++++++------------------ xlators/nfs/server/src/mount3.h | 2 +- 5 files changed, 255 insertions(+), 90 deletions(-) diff --git a/libglusterfs/src/common-utils.c b/libglusterfs/src/common-utils.c index 1dfb418e4a8..96319624361 100644 --- a/libglusterfs/src/common-utils.c +++ b/libglusterfs/src/common-utils.c @@ -1921,6 +1921,70 @@ out: return ret; } +/** + * valid_ipv4_subnetwork() takes the pattern and checks if it contains + * a valid ipv4 subnetwork pattern i.e. xx.xx.xx.xx/n. IPv4 address + * part (xx.xx.xx.xx) and mask bits lengh part (n). The mask bits lengh + * must be in 0-32 range (ipv4 addr is 32 bit). The pattern must be + * in this format. + * + * Returns _gf_true if both IP addr and mask bits len are valid + * _gf_false otherwise. + */ +gf_boolean_t +valid_ipv4_subnetwork (const char *address) +{ + char *slash = NULL; + char *paddr = NULL; + char *endptr = NULL; + long prefixlen = -1; + gf_boolean_t retv = _gf_true; + + if (address == NULL) { + gf_log_callingfn (THIS->name, GF_LOG_WARNING, + "argument invalid"); + return _gf_false; + } + + paddr = gf_strdup (address); + if (paddr == NULL) /* ENOMEM */ + return _gf_false; + + /* + * INVALID: If '/' is not present OR + * Nothing specified after '/' + */ + slash = strchr(paddr, '/'); + if ((slash == NULL) || (slash[1] == '\0')) { + gf_log_callingfn (THIS->name, GF_LOG_WARNING, + "Invalid IPv4 subnetwork format"); + retv = _gf_false; + goto out; + } + + *slash = '\0'; + retv = valid_ipv4_address (paddr, strlen(paddr), _gf_false); + if (retv == _gf_false) { + gf_log_callingfn (THIS->name, GF_LOG_WARNING, + "Invalid IPv4 subnetwork address"); + goto out; + } + + prefixlen = strtol (slash + 1, &endptr, 10); + if ((errno != 0) || (*endptr != '\0') || + (prefixlen < 0) || (prefixlen > IPv4_ADDR_SIZE)) { + gf_log_callingfn (THIS->name, GF_LOG_WARNING, + "Invalid IPv4 subnetwork mask"); + retv = _gf_false; + goto out; + } + + retv = _gf_true; +out: + GF_FREE (paddr); + return retv; +} + char valid_ipv6_address (char *address, int length, gf_boolean_t wildcard_acc) { @@ -2045,6 +2109,23 @@ gf_sock_union_equal_addr (union gf_sock_union *a, return _gf_false; } +/* + * Check if both have same network address. + * Extract the network address from the sockaddr(s) addr by applying the + * network mask. If they match, return boolean _gf_true, _gf_false otherwise. + * + * (x == y) <=> (x ^ y == 0) + * (x & y) ^ (x & z) <=> x & (y ^ z) + * + * ((ip1 & mask) == (ip2 & mask)) <=> ((mask & (ip1 ^ ip2)) == 0) + */ +gf_boolean_t +mask_match(const uint32_t a, const uint32_t b, const uint32_t m) +{ + return (((a ^ b) & m) == 0); +} + + /*Thread safe conversion function*/ char * uuid_utoa (uuid_t uuid) diff --git a/libglusterfs/src/common-utils.h b/libglusterfs/src/common-utils.h index 6f8436fcba0..a0c0db170de 100644 --- a/libglusterfs/src/common-utils.h +++ b/libglusterfs/src/common-utils.h @@ -49,6 +49,8 @@ void trap (void); #define roof(a,b) ((((a)+(b)-1)/((b)?(b):1))*(b)) #define floor(a,b) (((a)/((b)?(b):1))*(b)) +#define IPv4_ADDR_SIZE 32 + #define GF_UNIT_KB 1024ULL #define GF_UNIT_MB 1048576ULL @@ -572,6 +574,7 @@ void skip_word (char **str); /* returns a new string with nth word of given string. n>=1 */ char *get_nth_word (const char *str, int n); +gf_boolean_t mask_match (const uint32_t a, const uint32_t b, const uint32_t m); char valid_host_name (char *address, int length); char valid_ipv4_address (char *address, int length, gf_boolean_t wildcard_acc); char valid_ipv6_address (char *address, int length, gf_boolean_t wildcard_acc); diff --git a/rpc/rpc-lib/src/rpcsvc.c b/rpc/rpc-lib/src/rpcsvc.c index 9374ee7328f..a751330a7c9 100644 --- a/rpc/rpc-lib/src/rpcsvc.c +++ b/rpc/rpc-lib/src/rpcsvc.c @@ -60,6 +60,9 @@ int rpcsvc_notify (rpc_transport_t *trans, void *mydata, rpc_transport_event_t event, void *data, ...); +static int +rpcsvc_match_subnet_v4 (const char *addrtok, const char *ipaddr); + rpcsvc_notify_wrapper_t * rpcsvc_notify_wrapper_alloc (void) { @@ -2236,6 +2239,13 @@ rpcsvc_transport_peer_check_search (dict_t *options, char *pattern, goto err; } + /* Compare IPv4 subnetwork, TODO: IPv6 subnet support */ + if (strchr (addrtok, '/')) { + ret = rpcsvc_match_subnet_v4 (addrtok, ip); + if (ret == 0) + goto err; + } + addrtok = strtok_r (NULL, ",", &svptr); } @@ -2516,6 +2526,62 @@ out: return addrstr; } +/* + * rpcsvc_match_subnet_v4() takes subnetwork address pattern and checks + * if the target IPv4 address has the same network address with the help + * of network mask. + * + * Returns 0 for SUCCESS and -1 otherwise. + * + * NB: Validation of subnetwork address pattern is not required + * as it's already being done at the time of CLI SET. + */ +static int +rpcsvc_match_subnet_v4 (const char *addrtok, const char *ipaddr) +{ + char *slash = NULL; + char *netaddr = NULL; + int ret = -1; + uint32_t prefixlen = 0; + uint32_t shift = 0; + struct sockaddr_in sin1 = {0, }; + struct sockaddr_in sin2 = {0, }; + struct sockaddr_in mask = {0, }; + + /* Copy the input */ + netaddr = gf_strdup (addrtok); + if (netaddr == NULL) /* ENOMEM */ + goto out; + + /* Find the network socket addr of target */ + if (inet_pton (AF_INET, ipaddr, &sin1.sin_addr) == 0) + goto out; + + /* Find the network socket addr of subnet pattern */ + slash = strchr (netaddr, '/'); + *slash = '\0'; + if (inet_pton (AF_INET, netaddr, &sin2.sin_addr) == 0) + goto out; + + /* + * Find the IPv4 network mask in network byte order. + * IMP: String slash+1 is already validated, it cant have value + * more than IPv4_ADDR_SIZE (32). + */ + prefixlen = (uint32_t) atoi (slash + 1); + shift = IPv4_ADDR_SIZE - prefixlen; + mask.sin_addr.s_addr = htonl ((uint32_t)~0 << shift); + + if (mask_match (sin1.sin_addr.s_addr, + sin2.sin_addr.s_addr, + mask.sin_addr.s_addr)) { + ret = 0; /* SUCCESS */ + } +out: + GF_FREE (netaddr); + return ret; +} + rpcsvc_actor_t gluster_dump_actors[] = { [GF_DUMP_NULL] = {"NULL", GF_DUMP_NULL, NULL, NULL, 0, DRC_NA}, diff --git a/xlators/nfs/server/src/mount3.c b/xlators/nfs/server/src/mount3.c index b0824bf1029..dd66c44af77 100644 --- a/xlators/nfs/server/src/mount3.c +++ b/xlators/nfs/server/src/mount3.c @@ -37,22 +37,6 @@ #include -#define IPv4_ADDR_SIZE 32 - -/* Macro to typecast the parameter to struct sockaddr_in - */ -#define SA(addr) ((struct sockaddr_in*)(addr)) - -/* Macro will mask the ip address with netmask. - */ -#define MASKED_IP(ipv4addr, netmask) \ - (ntohl(SA(ipv4addr)->sin_addr.s_addr) & (netmask)) - -/* Macro will compare two IP address after applying the mask - */ -#define COMPARE_IPv4_ADDRS(ip1, ip2, netmask) \ - ((MASKED_IP(ip1, netmask)) == (MASKED_IP(ip2, netmask))) - /* This macro will assist in freeing up entire link list * of host_auth_spec structure. */ @@ -1017,6 +1001,23 @@ err: } +static gf_boolean_t +mnt3_match_subnet_v4 (struct addrinfo *ai, uint32_t saddr, uint32_t mask) +{ + for (; ai; ai = ai->ai_next) { + struct sockaddr_in *sin = (struct sockaddr_in *)ai->ai_addr; + + if (sin->sin_family != AF_INET) + continue; + + if (mask_match (saddr, sin->sin_addr.s_addr, mask)) + return _gf_true; + } + + return _gf_false; +} + + /** * This function will verify if the client is allowed to mount * the directory or not. Client's IP address will be compared with @@ -1026,20 +1027,25 @@ err: * @param export - mnt3_export structure. Contains allowed IP list/range. * * @return 0 - on Success and -EACCES on failure. + * + * TODO: Support IPv6 subnetwork */ int mnt3_verify_auth (rpcsvc_request_t *req, struct mnt3_export *export) { int retvalue = -EACCES; int ret = 0; - int shiftbits = 0; - uint32_t ipv4netmask = 0; - uint32_t routingprefix = 0; struct host_auth_spec *host = NULL; struct sockaddr_in *client_addr = NULL; struct sockaddr_in *allowed_addr = NULL; struct addrinfo *allowed_addrinfo = NULL; + struct addrinfo hint = { + .ai_family = AF_INET, + .ai_protocol = (int)IPPROTO_TCP, + .ai_flags = AI_CANONNAME, + }; + /* Sanity check */ if ((NULL == req) || (NULL == req->trans) || @@ -1051,10 +1057,19 @@ mnt3_verify_auth (rpcsvc_request_t *req, struct mnt3_export *export) host = export->hostspec; - /* Client's IP address. */ client_addr = (struct sockaddr_in *)(&(req->trans->peerinfo.sockaddr)); + /* + * Currently IPv4 subnetwork is supported i.e. AF_INET. + * TODO: IPv6 subnetwork i.e. AF_INET6. + */ + if (client_addr->sin_family != AF_INET) { + gf_log (GF_MNT, GF_LOG_ERROR, + "Only IPv4 is supported for subdir-auth"); + return retvalue; + } + /* Try to see if the client IP matches the allowed IP list.*/ while (NULL != host){ GF_ASSERT (host->host_addr); @@ -1065,77 +1080,38 @@ mnt3_verify_auth (rpcsvc_request_t *req, struct mnt3_export *export) } /* Get the addrinfo for the allowed host (host_addr). */ - ret = getaddrinfo (host->host_addr, - NULL, - NULL, - &allowed_addrinfo); + ret = getaddrinfo (host->host_addr, NULL, + &hint, &allowed_addrinfo); if (0 != ret){ - gf_log (GF_MNT, GF_LOG_ERROR, "getaddrinfo: %s\n", - gai_strerror (ret)); - host = host->next; - - /* Failed to get IP addrinfo. Continue to check other - * allowed IPs in the list. + /* + * getaddrinfo() FAILED for the host IP addr. Continue + * to search other allowed hosts in the hostspec list. */ + gf_log (GF_MNT, GF_LOG_DEBUG, + "getaddrinfo: %s\n", gai_strerror (ret)); + host = host->next; continue; } allowed_addr = (struct sockaddr_in *)(allowed_addrinfo->ai_addr); - if (NULL == allowed_addr) { gf_log (GF_MNT, GF_LOG_ERROR, "Invalid structure"); break; } - if (AF_INET == allowed_addr->sin_family){ - if (IPv4_ADDR_SIZE < host->routeprefix) { - gf_log (GF_MNT, GF_LOG_ERROR, "invalid IP " - "configured for export-dir AUTH"); - host = host->next; - continue; - } - - /* -1 means no route prefix is provided. In this case - * the IP should be an exact match. Which is same as - * providing a route prefix of IPv4_ADDR_SIZE. - */ - if (-1 == host->routeprefix) { - routingprefix = IPv4_ADDR_SIZE; - } else { - routingprefix = host->routeprefix; - } - - /* Create a mask from the routing prefix. User provided - * CIDR address is split into IP address (host_addr) and - * routing prefix (routeprefix). This CIDR address may - * denote a single, distinct interface address or the - * beginning address of an entire network. - * - * e.g. the IPv4 block 192.168.100.0/24 represents the - * 256 IPv4 addresses from 192.168.100.0 to - * 192.168.100.255. - * Therefore to check if an IP matches 192.168.100.0/24 - * we should mask the IP with FFFFFF00 and compare it - * with host address part of CIDR. - */ - shiftbits = IPv4_ADDR_SIZE - routingprefix; - ipv4netmask = 0xFFFFFFFFUL << shiftbits; - - /* Mask both the IPs and then check if they match - * or not. */ - if (COMPARE_IPv4_ADDRS (allowed_addr, - client_addr, - ipv4netmask)){ - retvalue = 0; - break; - } + /* Check if the network addr of both IPv4 socket match */ + if (mnt3_match_subnet_v4 (allowed_addrinfo, + client_addr->sin_addr.s_addr, + host->netmask)) { + retvalue = 0; + break; } - /* Client IP didn't match the allowed IP. - * Check with the next allowed IP.*/ + /* No match yet, continue the search */ host = host->next; } + /* FREE the dynamic memory allocated by getaddrinfo() */ if (NULL != allowed_addrinfo) { freeaddrinfo (allowed_addrinfo); } @@ -2020,15 +1996,21 @@ mount3udp_delete_mountlist (char *hostname, dirpath *expname) * @param hostip - IP address, IP range (CIDR format) or hostname * * @return 0 - on success and -1 on failure + * + * NB: This does not support IPv6 currently. */ int mnt3_export_fill_hostspec (struct host_auth_spec* hostspec, const char* hostip) { - char *ipdupstr = NULL; - char *savptr = NULL; - char *ip = NULL; - char *token = NULL; - int ret = -1; + char *ipdupstr = NULL; + char *savptr = NULL; + char *endptr = NULL; + char *ip = NULL; + char *token = NULL; + int ret = -1; + long prefixlen = IPv4_ADDR_SIZE; /* default */ + uint32_t shiftbits = 0; + size_t length = 0; /* Create copy of the string so that the source won't change */ @@ -2039,25 +2021,58 @@ mnt3_export_fill_hostspec (struct host_auth_spec* hostspec, const char* hostip) } ip = strtok_r (ipdupstr, "/", &savptr); + /* Validate the Hostname or IPv4 address + * TODO: IPv6 support for subdir auth. + */ + length = strlen (ip); + if ((!valid_ipv4_address (ip, (int)length, _gf_false)) && + (!valid_host_name (ip, (int)length))) { + gf_log (GF_MNT, GF_LOG_ERROR, + "Invalid hostname or IPv4 address: %s", ip); + goto err; + } + hostspec->host_addr = gf_strdup (ip); if (NULL == hostspec->host_addr) { gf_log (GF_MNT, GF_LOG_ERROR, "Memory allocation failed"); goto err; } - /* Check if the IP is in / format. - * If yes, then strip the range and store it separately. + /** + * User provided CIDR address (xx.xx.xx.xx/n format) is split + * into HOST (IP addr or hostname) and network prefix(n) from + * which netmask would be calculated. This CIDR address may + * denote a single, distinct interface address or the beginning + * address of an entire network. + * + * e.g. the IPv4 block 192.168.100.0/24 represents the 256 + * IPv4 addresses from 192.168.100.0 to 192.168.100.255. + * Therefore to check if an IP matches 192.168.100.0/24 + * we should mask the IP with FFFFFF00 and compare it with + * host address part of CIDR. + * + * Refer: mask_match() in common-utils.c. */ token = strtok_r (NULL, "/", &savptr); - - if (NULL == token) { - hostspec->routeprefix = -1; - } else { - hostspec->routeprefix = atoi (token); + if (token != NULL) { + prefixlen = strtol (token, &endptr, 10); + if ((errno != 0) || (*endptr != '\0') || + (prefixlen < 0) || (prefixlen > IPv4_ADDR_SIZE)) { + gf_log (THIS->name, GF_LOG_WARNING, + "Invalid IPv4 subnetwork mask"); + goto err; + } } - // success - ret = 0; + /* + * 1. Calculate the network mask address. + * 2. Convert it into Big-Endian format. + * 3. Store it in hostspec netmask. + */ + shiftbits = IPv4_ADDR_SIZE - prefixlen; + hostspec->netmask = htonl ((uint32_t)~0 << shiftbits); + + ret = 0; /* SUCCESS */ err: if (NULL != ipdupstr) { GF_FREE (ipdupstr); diff --git a/xlators/nfs/server/src/mount3.h b/xlators/nfs/server/src/mount3.h index 7fc16ed5790..8474244f191 100644 --- a/xlators/nfs/server/src/mount3.h +++ b/xlators/nfs/server/src/mount3.h @@ -68,7 +68,7 @@ struct mountentry { /* Structure to hold export-dir AUTH parameter */ struct host_auth_spec { char *host_addr; /* Allowed IP or host name */ - int routeprefix; /* Routing prefix */ + uint32_t netmask; /* Network mask (Big-Endian) */ struct host_auth_spec *next; /* Pointer to next AUTH struct */ }; -- cgit