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:
parent
5740fd4048
commit
d3f0de90d0
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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 */
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user