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.<volname>.allow"
   and "NFS subdir auth code" to remove code duplication.

Change-Id: I26b0def52c22cda35ca11766afca3df5fd4360bf
BUG: 1102293
Signed-off-by: Santosh Kumar Pradhan <spradhan@redhat.com>
Reviewed-on: http://review.gluster.org/8048
Reviewed-by: Rajesh Joseph <rjoseph@redhat.com>
Tested-by: Gluster Build System <jenkins@build.gluster.com>
Reviewed-by: Niels de Vos <ndevos@redhat.com>
This commit is contained in:
Santosh Kumar Pradhan 2014-06-06 12:22:04 +05:30 committed by Niels de Vos
parent 5740fd4048
commit d3f0de90d0
5 changed files with 144 additions and 118 deletions

View File

@ -1936,7 +1936,7 @@ valid_ipv4_subnetwork (const char *address)
prefixlen = strtol (slash + 1, &endptr, 10);
if ((errno != 0) || (*endptr != '\0') ||
(prefixlen < 0) || (prefixlen > 32)) {
(prefixlen < 0) || (prefixlen > IPv4_ADDR_SIZE)) {
gf_log_callingfn (THIS->name, GF_LOG_WARNING,
"Invalid IPv4 subnetwork mask");
retv = _gf_false;
@ -2132,6 +2132,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)

View File

@ -50,6 +50,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
@ -582,6 +584,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);

View File

@ -61,7 +61,7 @@ rpcsvc_notify (rpc_transport_t *trans, void *mydata,
rpc_transport_event_t event, void *data, ...);
static int
match_subnet_v4 (const char *addrtok, const char *ipaddr);
rpcsvc_match_subnet_v4 (const char *addrtok, const char *ipaddr);
rpcsvc_notify_wrapper_t *
rpcsvc_notify_wrapper_alloc (void)
@ -2265,9 +2265,9 @@ rpcsvc_transport_peer_check_search (dict_t *options, char *pattern,
goto err;
}
/* Compare IPv4 subnetwork */
/* Compare IPv4 subnetwork, TODO: IPv6 subnet support */
if (strchr (addrtok, '/')) {
ret = match_subnet_v4 (addrtok, ip);
ret = rpcsvc_match_subnet_v4 (addrtok, ip);
if (ret == 0)
goto err;
}
@ -2565,9 +2565,9 @@ out:
}
/*
* 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.
* 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.
*
@ -2575,12 +2575,12 @@ out:
* as it's already being done at the time of CLI SET.
*/
static int
match_subnet_v4 (const char *addrtok, const char *ipaddr)
rpcsvc_match_subnet_v4 (const char *addrtok, const char *ipaddr)
{
char *slash = NULL;
char *netaddr = NULL;
long prefixlen = -1;
int ret = -1;
uint32_t prefixlen = 0;
uint32_t shift = 0;
struct sockaddr_in sin1 = {0, };
struct sockaddr_in sin2 = {0, };
@ -2602,28 +2602,19 @@ match_subnet_v4 (const char *addrtok, const char *ipaddr)
goto out;
/*
* Find the network mask in network byte order.
* NB: 32 : Max len of IPv4 address.
* 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 = atoi (slash + 1);
shift = 32 - (uint32_t)prefixlen;
prefixlen = (uint32_t) atoi (slash + 1);
shift = IPv4_ADDR_SIZE - prefixlen;
mask.sin_addr.s_addr = htonl ((uint32_t)~0 << shift);
/*
* Check if both have same network address.
* Extract the network address from the IP addr by applying the
* network mask. If they match, return SUCCESS. i.e.
*
* (x == y) <=> (x ^ y == 0)
* (x & y) ^ (x & z) <=> x & (y ^ z)
*
* ((ip1 & mask) == (ip2 & mask)) <=> ((mask & (ip1 ^ ip2)) == 0)
*/
if (((mask.sin_addr.s_addr) &
(sin1.sin_addr.s_addr ^ sin2.sin_addr.s_addr)) != 0)
goto out;
ret = 0; /* SUCCESS */
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;

View File

@ -37,22 +37,6 @@
#include <sys/uio.h>
#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.
*/
@ -1015,6 +999,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
@ -1024,20 +1025,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) ||
@ -1049,10 +1055,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);
@ -1063,77 +1078,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);
}
@ -2040,15 +2016,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
*/
@ -2059,25 +2041,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 <IP address> / <Range> 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);

View File

@ -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 */
};