diff options
| -rw-r--r-- | libglusterfs/src/common-utils.c | 81 | ||||
| -rw-r--r-- | libglusterfs/src/common-utils.h | 3 | ||||
| -rw-r--r-- | rpc/rpc-lib/src/rpcsvc.c | 66 | ||||
| -rw-r--r-- | xlators/nfs/server/src/mount3.c | 193 | ||||
| -rw-r--r-- | 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 <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.   */ @@ -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 <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); 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 */  };  | 
