diff --git a/extra/tarantool.rb b/extra/tarantool.rb index fefb202a7458bb985e8865848b5a2fac38fd5609..cb5e2a858be8239e399eb90ec21ecdfe50c59a80 100644 --- a/extra/tarantool.rb +++ b/extra/tarantool.rb @@ -72,11 +72,11 @@ class Tarantool < Formula else doc.install "test/box/box.lua" inreplace doc/"box.lua" do |s| - s.gsub!(/^os = require.*\n/ , '') - s.gsub!(/(primary_port\s*=).*/, '\1 3301,') - s.gsub!(/(admin_port\s*=).*/ , '\1 3313,') - s.gsub!(/(rows_per_wal.*)/ , '\1,') - s.gsub!(/^}.*/ , "\twork_dir\t\t\t= \"#{prefix}/var/lib/tarantool\",\n}") + s.gsub!(/^os = require.*\n/ , '') + s.gsub!(/os.getenv\("LISTEN"\)/, '3301') + s.gsub!(/os.getenv\('ADMIN'\)/ , '3313') + s.gsub!(/(rows_per_wal\s*=).*/ , '\1 500,') + s.gsub!(/^}.*/ , "\twork_dir\t\t\t= \"#{prefix}/var/lib/tarantool\",\n}") end end diff --git a/src/box/alter.cc b/src/box/alter.cc index cfafda8de106173f9362a2e051837516f4130df2..280831565647e83092047b48893c6fff2e4fb41c 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -1146,14 +1146,22 @@ space_has_data(uint32_t id, uint32_t iid, uint32_t uid) } bool -user_has_data(uint32_t uid) +user_has_data(struct user *user) { + uint32_t uid = user->uid; uint32_t spaces[] = { SC_SPACE_ID, SC_FUNC_ID, SC_PRIV_ID }; uint32_t *end = spaces + sizeof(spaces)/sizeof(*spaces); for (uint32_t *i = spaces; i < end; i++) { if (space_has_data(*i, 1, uid)) return true; } + if (! user_map_is_empty(&user->users)) + return true; + /* + * If there was a role, the previous check would have + * returned true. + */ + assert(user_map_is_empty(&user->roles)); return false; } @@ -1279,7 +1287,7 @@ on_replace_dd_user(struct trigger * /* trigger */, void *event) uint32_t uid = tuple_field_u32(old_tuple ? old_tuple : new_tuple, ID); - struct user_def *old_user = user_by_id(uid); + struct user *old_user = user_by_id(uid); if (new_tuple != NULL && old_user == NULL) { /* INSERT */ struct user_def user; user_def_create_from_tuple(&user, new_tuple); @@ -1299,7 +1307,7 @@ on_replace_dd_user(struct trigger * /* trigger */, void *event) * Can only delete user if it has no spaces, * no functions and no grants. */ - if (user_has_data(uid)) { + if (user_has_data(old_user)) { tnt_raise(ClientError, ER_DROP_USER, old_user->name, "the user has objects"); } @@ -1425,7 +1433,7 @@ on_replace_dd_func(struct trigger * /* trigger */, void *event) /** * Create a privilege definition from tuple. */ -static void +void priv_def_create_from_tuple(struct priv_def *priv, struct tuple *tuple) { priv->grantor_id = tuple_field_u32(tuple, ID); @@ -1508,6 +1516,10 @@ priv_def_check(struct priv_def *priv) default: break; } + if (priv->access == 0) { + tnt_raise(ClientError, ER_GRANT, + "the grant tuple has no privileges"); + } } /** @@ -1520,47 +1532,17 @@ grant_or_revoke(struct priv_def *priv) struct user *grantee = user_by_id(priv->grantee_id); if (grantee == NULL) return; - struct access *access = NULL; - switch (priv->object_type) { - case SC_UNIVERSE: - { - access = universe.access; - /** Update cache at least in the current session. */ - struct credentials *cr = current_user(); - if (grantee->uid == cr->uid) - cr->universal_access = priv->access; - break; - } - case SC_SPACE: - { - struct space *space = space_by_id(priv->object_id); - if (space) - access = space->access; - break; - } - case SC_FUNCTION: - { - struct func_def *func = func_by_id(priv->object_id); - if (func) - access = func->access; - break; - } - case SC_ROLE: - { + if (priv->object_type == SC_ROLE) { struct user *role = user_by_id(priv->object_id); if (role == NULL || role->type != SC_ROLE) - break; + return; if (priv->access) role_grant(grantee, role); else role_revoke(grantee, role); - break; - } - default: - break; + } else { + priv_grant(grantee, priv); } - if (access) - privilege_grant(grantee, access, priv->access); } /** A trigger called on rollback of grant, or on commit of revoke. */ diff --git a/src/box/key_def.h b/src/box/key_def.h index e730735d3bf66026c1f89b290e32e31bea2cf26a..b347740c5415829e443838c2b18da7e258c74412 100644 --- a/src/box/key_def.h +++ b/src/box/key_def.h @@ -32,6 +32,8 @@ #include "salad/rlist.h" #include <exception.h> #include "msgpuck/msgpuck.h" +#define RB_COMPACT 1 +#include "third_party/rb.h" #include <limits.h> #include <wchar.h> #include <wctype.h> @@ -355,6 +357,8 @@ struct priv_def { * revoked. */ uint8_t access; + /** To maintain a set of effective privileges. */ + rb_node(struct priv_def) link; }; /** diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua index cf918b9f17058852e4a0f8ca511b6d3574160a2a..cd2303342ddb93cc573a1b60030690e45c26be45 100644 --- a/src/box/lua/schema.lua +++ b/src/box/lua/schema.lua @@ -1018,7 +1018,7 @@ box.schema.user.drop = function(name) for k, tuple in pairs(funcs) do box.schema.func.drop(tuple[1]) end - -- if this is a role, revoke grants of this role + -- if this is a role, revoke this role from whoever it was granted to grants = _priv.index.object:select{'role', uid} for k, tuple in pairs(grants) do box.schema.user.revoke(tuple[2], uid) @@ -1035,6 +1035,9 @@ box.schema.user.grant = function(user_name, privilege, object_type, -- named 'execute' object_type = 'role' object_name = privilege + end + -- sanitize privilege type for role object type + if object_type == 'role' then privilege = 'execute' end local uid = user_resolve(user_name) @@ -1071,7 +1074,9 @@ box.schema.user.revoke = function(user_name, privilege, object_type, object_name if object_name == nil and object_type == nil then object_type = 'role' object_name = privilege - privilege = 'execute' + -- revoke everything possible from role, + -- to prevent stupid mistakes with privilege name + privilege = 'read,write,execute' end local uid = user_resolve(user_name) if uid == nil then @@ -1086,10 +1091,12 @@ box.schema.user.revoke = function(user_name, privilege, object_type, object_name end local old_privilege = tuple[5] local grantor = tuple[1] - -- XXX gh-449: the privilege may be removed by someone who did - -- not grant it - if privilege ~= old_privilege then - privilege = bit.band(old_privilege, bit.bnot(privilege)) + -- sic: + -- a user may revoke more than he/she granted + -- (erroneous user input) + -- + privilege = bit.band(old_privilege, bit.bnot(privilege)) + if privilege ~= 0 then _priv:replace{grantor, uid, object_type, oid, privilege} else _priv:delete{uid, object_type, oid} diff --git a/src/box/user.cc b/src/box/user.cc index 2b7ae6e5dc5c845ccad39980a12e4d9241ef0946..b7ee0157f03919317b32ac3a11c8f2054c1e3162 100644 --- a/src/box/user.cc +++ b/src/box/user.cc @@ -30,24 +30,24 @@ #include "user_def.h" #include "assoc.h" #include "schema.h" +#include "space.h" +#include "index.h" #include "bit/bit.h" +#include "fiber.h" +#include "scoped_guard.h" +#include "session.h" struct universe universe; static struct user users[BOX_USER_MAX]; struct user *guest_user = users; struct user *admin_user = users + 1; +static struct user_map user_map_nil; + struct mh_i32ptr_t *user_registry; /* {{{ user_map */ -/* Initialize an empty user map. */ -void -user_map_init(struct user_map *map) -{ - memset(map, 0, sizeof(*map)); -} - static inline int user_map_calc_idx(uint8_t auth_token, uint8_t *bit_no) { @@ -83,40 +83,41 @@ user_map_is_set(struct user_map *map, uint8_t auth_token) return map->m[idx] & (((umap_int_t) 1) << bit_no); } -static inline bool -user_map_is_empty(struct user_map *map) -{ - for (int i = 0; i < USER_MAP_SIZE; i++) - if (map->m[i]) - return false; - return true; -} - /** * Merge two sets of users: add all users from right argument * to the left one. */ -void +static void user_map_union(struct user_map *lhs, struct user_map *rhs) { for (int i = 0; i < USER_MAP_SIZE; i++) lhs->m[i] |= rhs->m[i]; } +/** + * Remove all users present in rhs from lhs + */ +static void +user_map_minus(struct user_map *lhs, struct user_map *rhs) +{ + for (int i = 0; i < USER_MAP_SIZE; i++) + lhs->m[i] &= ~rhs->m[i]; +} + /** Iterate over users in the set of users. */ struct user_map_iterator { struct bit_iterator it; }; -void +static void user_map_iterator_init(struct user_map_iterator *it, struct user_map *map) { bit_iterator_init(&it->it, map->m, USER_MAP_SIZE * sizeof(umap_int_t), true); } -struct user * +static struct user * user_map_iterator_next(struct user_map_iterator *it) { size_t auth_token = bit_iterator_next(&it->it); @@ -127,6 +128,193 @@ user_map_iterator_next(struct user_map_iterator *it) /* }}} */ +/* {{{ privset_t - set of effective privileges of a user */ + +extern "C" { + +static int +priv_def_compare(const struct priv_def *lhs, const struct priv_def *rhs) +{ + if (lhs->object_type != rhs->object_type) + return lhs->object_type > rhs->object_type ? 1 : -1; + if (lhs->object_id != rhs->object_id) + return lhs->object_id > rhs->object_id ? 1 : -1; + return 0; +} + +} /* extern "C" */ + +rb_gen(, privset_, privset_t, struct priv_def, link, priv_def_compare); + +/* }}} */ + +/** {{{ user */ + +static void +user_create(struct user *user, uint8_t auth_token) +{ + assert(user->auth_token == 0); + user->auth_token = auth_token; + privset_new(&user->privs); + region_create(&user->pool, &cord()->slabc); +} + +static void +user_destroy(struct user *user) +{ + /* + * Sic: we don't have to remove a deleted + * user from users set of roles, since + * to drop a user, one has to revoke + * all privileges from them first. + */ + region_destroy(&user->pool); + memset(user, 0, sizeof(*user)); +} + +/** + * Add a privilege definition to the list + * of effective privileges of a user. + */ +void +user_grant_priv(struct user *user, struct priv_def *def) +{ + struct priv_def *old = privset_search(&user->privs, def); + if (old == NULL) { + old = (struct priv_def *) + region_alloc(&user->pool, sizeof(struct priv_def)); + *old = *def; + privset_insert(&user->privs, old); + } else { + old->access |= def->access; + } +} + +/** + * Find the corresponding access structure + * given object type and object id. + */ +struct access * +access_find(struct priv_def *priv) +{ + struct access *access = NULL; + switch (priv->object_type) { + case SC_UNIVERSE: + { + access = universe.access; + break; + } + case SC_SPACE: + { + struct space *space = space_by_id(priv->object_id); + if (space) + access = space->access; + break; + } + case SC_FUNCTION: + { + struct func_def *func = func_by_id(priv->object_id); + if (func) + access = func->access; + break; + } + default: + break; + } + return access; +} + + +/** + * Reset effective access of the user in the + * corresponding objects. + */ +static void +user_set_effective_access(struct user *user) +{ + struct credentials *cr = current_user(); + struct priv_def *priv; + for (priv = privset_first(&user->privs); + priv; + priv = privset_next(&user->privs, priv)) { + struct access *object = access_find(priv); + /* Protect against a concurrent drop. */ + if (object == NULL) + continue; + struct access *access = &object[user->auth_token]; + access->effective = access->granted | priv->access; + /** Update global access in the current session. */ + if (priv->object_type == SC_UNIVERSE && user->uid == cr->uid) + cr->universal_access = access->effective; + } +} + +/** + * Reload user privileges and re-grant them. + */ +static void +user_reload_privs(struct user *user) +{ + if (user->is_dirty == false) + return; + struct priv_def *priv; + /** + * Reset effective access of the user in the + * corresponding objects to have + * only the stuff that it's granted directly. + */ + for (priv = privset_first(&user->privs); + priv; + priv = privset_next(&user->privs, priv)) { + priv->access = 0; + } + user_set_effective_access(user); + region_free(&user->pool); + privset_new(&user->privs); + /* Load granted privs from _priv space. */ + { + struct space *space = space_cache_find(SC_PRIV_ID); + char key[6]; + /** Primary key - by user id */ + Index *index = index_find(space, 0); + mp_encode_uint(key, user->uid); + + struct iterator *it = index->position(); + index->initIterator(it, ITER_EQ, key, 1); + auto iterator_guard = + make_scoped_guard([=] { iterator_close(it); }); + + struct tuple *tuple; + while ((tuple = it->next(it))) { + struct priv_def priv; + priv_def_create_from_tuple(&priv, tuple); + /** + * Skip role grants, we're only + * interested in real objects. + */ + if (priv.object_type != SC_ROLE) + user_grant_priv(user, &priv); + } + } + { + /* Take into account privs granted through roles. */ + struct user_map_iterator it; + user_map_iterator_init(&it, &user->roles); + struct user *role; + while ((role = user_map_iterator_next(&it))) { + struct priv_def *def = privset_first(&role->privs); + while (def) { + user_grant_priv(user, def); + def = privset_next(&role->privs, def); + } + } + } + user_set_effective_access(user); + user->is_dirty = false; +} + +/** }}} */ + /* {{{ authentication tokens */ /** A map to quickly look up free slots in users[] array. */ @@ -195,8 +383,7 @@ user_cache_replace(struct user_def *def) if (user == NULL) { uint8_t auth_token = auth_token_get(); user = users + auth_token; - assert(user->auth_token == 0); - user->auth_token = auth_token; + user_create(user, auth_token); struct mh_i32ptr_node_t node = { def->uid, user }; mh_i32ptr_put(user_registry, &node, NULL, NULL); } @@ -211,13 +398,15 @@ user_cache_delete(uint32_t uid) if (user) { assert(user->auth_token > ADMIN); auth_token_put(user->auth_token); + assert(user_map_is_empty(&user->roles)); + assert(user_map_is_empty(&user->users)); /* * Sic: we don't have to remove a deleted * user from users hash of roles, since * to drop a user, one has to revoke * all privileges from them first. */ - memset(user, 0, sizeof(*user)); + user_destroy(user); mh_i32ptr_del(user_registry, uid, NULL); } } @@ -308,8 +497,7 @@ role_check(struct user *grantee, struct user *role) * immediate and indirect users of grantee, and ensure * the granted role is not in this set. */ - struct user_map transitive_closure; - user_map_init(&transitive_closure); + struct user_map transitive_closure = user_map_nil; user_map_set(&transitive_closure, grantee->auth_token); struct user_map current_layer = transitive_closure; while (! user_map_is_empty(¤t_layer)) { @@ -318,8 +506,7 @@ role_check(struct user *grantee, struct user *role) * acyclic graph, we're bound to end at some * point in a layer with no incoming edges. */ - struct user_map next_layer; - user_map_init(&next_layer); + struct user_map next_layer = user_map_nil; struct user_map_iterator it; user_map_iterator_init(&it, ¤t_layer); struct user *user; @@ -328,7 +515,10 @@ role_check(struct user *grantee, struct user *role) user_map_union(&transitive_closure, &next_layer); current_layer = next_layer; } - + /* + * Check if the role is in the list of roles to which the + * grantee is granted. + */ if (user_map_is_set(&transitive_closure, role->auth_token)) { tnt_raise(ClientError, ER_ROLE_LOOP, @@ -336,63 +526,121 @@ role_check(struct user *grantee, struct user *role) } } +/** + * Re-calculate effective grants of the linked subgraph + * this user/role is a part of. + */ +void +rebuild_effective_grants(struct user *grantee) +{ + /* + * Recurse over all roles to which grantee is granted + * and mark them as dirty - in need for rebuild. + */ + struct user_map_iterator it; + struct user *user; + struct user_map current_layer = user_map_nil; + user_map_set(¤t_layer, grantee->auth_token); + while (!user_map_is_empty(¤t_layer)) { + struct user_map next_layer = user_map_nil; + user_map_iterator_init(&it, ¤t_layer); + while ((user = user_map_iterator_next(&it))) { + user->is_dirty = true; + user_map_union(&next_layer, &user->users); + } + /* + * Switch to the nodes which are not in the set + * yet. + */ + current_layer = next_layer; + } + /* + * First, construct a subset of the transitive + * closure consisting from the nodes with no + * incoming edges (roles which have no granted + * roles). Build their list of effective grants + * from their actual grants. + * + * Propagate the effective grants through the + * outgoing edges of the nodes, avoiding the nodes + * with incoming edges from not-yet-evaluated nodes. + * Eventually this process will end with a set of + * nodes with no outgoing edges. + */ + struct user_map transitive_closure = user_map_nil; + current_layer = user_map_nil; + user_map_set(¤t_layer, grantee->auth_token); + /* + * Propagate effective privileges from the nodes + * with no incoming edges to the remaining nodes. + */ + while (! user_map_is_empty(¤t_layer)) { + struct user_map postponed = user_map_nil; + struct user_map next_layer = user_map_nil; + user_map_iterator_init(&it, ¤t_layer); + while ((user = user_map_iterator_next(&it))) { + struct user_map indirect_edges = user->roles; + user_map_minus(&indirect_edges, &transitive_closure); + if (user_map_is_empty(&indirect_edges)) { + user_reload_privs(user); + user_map_union(&next_layer, &user->users); + } else { + /* + * The user has roles whose + * effective grants have not been + * calculated yet. Postpone + * evaluation of effective grants + * of this user till these roles' + * effective grants have been + * built. + */ + user_map_union(&next_layer, &indirect_edges); + user_map_set(&postponed, user->auth_token); + user_map_set(&next_layer, user->auth_token); + } + } + user_map_minus(¤t_layer, &postponed); + user_map_union(&transitive_closure, ¤t_layer); + current_layer = next_layer; + } +} + + +/** + * Update verges in the graph of dependencies. + * Grant all effective privileges of the role to whoever + * this role was granted to. + */ void role_grant(struct user *grantee, struct user *role) { user_map_set(&role->users, grantee->auth_token); - /** - * Todo: grant all effective privileges of - * the role to whoever this role was granted - * to. - */ + user_map_set(&grantee->roles, role->auth_token); + rebuild_effective_grants(grantee); } +/** + * Update the role dependencies graph. + * Rebuild effective privileges of the grantee. + */ void role_revoke(struct user *grantee, struct user *role) { user_map_clear(&role->users, grantee->auth_token); - /** - * Todo: rebuild effective privileges of grantee, - * for all effective privileges which he/she - * might have inherited through the revoked role. - */ + user_map_clear(&grantee->roles, role->auth_token); + rebuild_effective_grants(grantee); } void -privilege_grant(struct user *user, - struct access *object, uint8_t access) +priv_grant(struct user *grantee, struct priv_def *priv) { - bool grant = access > object[user->auth_token].granted; - object[user->auth_token].granted = access; - if (grant) { - /* - * Grant the privilege to this user or - * role and all users to which this - * role has been granted, if this is - * a role. - */ - struct user_map current_layer; - user_map_init(¤t_layer); - user_map_set(¤t_layer, user->auth_token); - while (!user_map_is_empty(¤t_layer)) { - struct user_map next_layer; - user_map_init(&next_layer); - struct user_map_iterator it; - user_map_iterator_init(&it, ¤t_layer); - while ((user = user_map_iterator_next(&it))) { - object[user->auth_token].effective |= access; - user_map_union(&next_layer, &user->users); - } - current_layer = next_layer; - } - } else { - /** - * @fixme: this only works for users and - * non-recursive roles - */ - object[user->auth_token].effective = - object[user->auth_token].granted; - } + struct access *object = access_find(priv); + if (object == NULL) + return; + struct access *access = &object[grantee->auth_token]; + assert(privset_search(&grantee->privs, priv) || access->granted == 0); + access->granted = priv->access; + rebuild_effective_grants(grantee); } /** }}} */ diff --git a/src/box/user.h b/src/box/user.h index 5bfb4b63794b62cb167c8f341c82d630eb139fe1..e767ca6c15091556f4c58bc660b8228285e7e9d6 100644 --- a/src/box/user.h +++ b/src/box/user.h @@ -30,6 +30,7 @@ */ #include <stdint.h> #include "user_def.h" +#include "small/region.h" /** Global grants. */ struct universe { @@ -51,6 +52,18 @@ struct user_map { umap_int_t m[USER_MAP_SIZE]; }; +static inline bool +user_map_is_empty(struct user_map *map) +{ + for (int i = 0; i < USER_MAP_SIZE; i++) + if (map->m[i]) + return false; + return true; +} + +typedef rb_tree(struct priv_def) privset_t; +rb_proto(, privset_, privset_t, struct priv_def); + struct user: public user_def { /** @@ -60,6 +73,14 @@ struct user: public user_def uint8_t auth_token; /** List of users or roles this role has been granted to */ struct user_map users; + /** List of roles granted to this role or user. */ + struct user_map roles; + /** A cache of effective privileges of this user. */ + privset_t privs; + /** True if this user privileges need to be reloaded. */ + bool is_dirty; + /** Memory pool for privs */ + struct region pool; }; /** @@ -144,8 +165,10 @@ role_revoke(struct user *grantee, struct user *role); * role if this role. */ void -privilege_grant(struct user *grantee, struct access *object, - uint8_t access); +priv_grant(struct user *grantee, struct priv_def *priv); + +void +priv_def_create_from_tuple(struct priv_def *priv, struct tuple *tuple); /* }}} */ diff --git a/src/box/vclock.h b/src/box/vclock.h index 80713f186d2048c66b8d69b29470cf0e3e9184f4..a17185168d0b6121adc6165e7ae9415fa4966be6 100644 --- a/src/box/vclock.h +++ b/src/box/vclock.h @@ -47,6 +47,7 @@ enum { VCLOCK_MAX = 16 }; /** Cluster vector clock */ struct vclock { int64_t lsn[VCLOCK_MAX]; + /** To order binary logs by vector clock. */ rb_node(struct vclock) link; }; diff --git a/src/coio.cc b/src/coio.cc index e3d92a0d7f7856c64c28a1f692eed6e99e7e712e..b8b5e5edd2f9e6d68109efc266e372eca31a02b7 100644 --- a/src/coio.cc +++ b/src/coio.cc @@ -220,7 +220,8 @@ coio_accept(struct ev_io *coio, struct sockaddr *addr, * available */ int fd = sio_accept(coio->fd, addr, &addrlen); if (fd >= 0) { - evio_setsockopt_tcp(fd, addr->sa_family); + evio_setsockopt_client(fd, addr->sa_family, + SOCK_STREAM); return fd; } /* The socket is not ready, yield */ diff --git a/src/errcode.h b/src/errcode.h index a89a9c34efbd14e772b5ea49446ac85474bc94ad..5160e869884d29f6601813d12d1ba039f7b3fc3f 100644 --- a/src/errcode.h +++ b/src/errcode.h @@ -137,6 +137,7 @@ enum { TNT_ERRMSG_MAX = 512 }; /* 85 */_(ER_INDEX_EXISTS, 2, "Index '%s' already exists") \ /* 86 */_(ER_TUPLE_REF_OVERFLOW, 1, "Tuple reference counter overflow") \ /* 87 */_(ER_ROLE_LOOP, 2, "Granting role '%s' to role '%s' would create a loop") \ + /* 88 */_(ER_GRANT, 2, "Incorrect grant arguments: %s") \ /* * !IMPORTANT! Please follow instructions at start of the file diff --git a/src/evio.cc b/src/evio.cc index 75b721161c3f8d30fa18ec00cd17ec46856bde07..7ce4e681b19e447824aa7d212c62f9910b8f3e69 100644 --- a/src/evio.cc +++ b/src/evio.cc @@ -38,6 +38,9 @@ #define BIND_RETRY_DELAY 0.1 +static void +evio_setsockopt_server(int fd, int family, int type); + /** Note: this function does not throw. */ void evio_close(ev_loop *loop, struct ev_io *evio) @@ -60,38 +63,66 @@ evio_socket(struct ev_io *coio, int domain, int type, int protocol) assert(coio->fd == -1); /* Don't leak fd if setsockopt fails. */ coio->fd = sio_socket(domain, type, protocol); - if (type == SOCK_STREAM) { - evio_setsockopt_tcp(coio->fd, domain); - } else { - sio_setfl(coio->fd, O_NONBLOCK, 1); - } + evio_setsockopt_client(coio->fd, domain, type); } - -/** Set common tcp socket client options. */ -void -evio_setsockopt_tcp(int fd, int family) +static void +evio_setsockopt_keepalive(int fd) { int on = 1; - /* In case this throws, the socket is not leaked. */ - sio_setfl(fd, O_NONBLOCK, on); - /* SO_KEEPALIVE to ensure connections don't hang + /* + * SO_KEEPALIVE to ensure connections don't hang * around for too long when a link goes away. */ sio_setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)); +#ifdef __linux__ /* - * Lower latency is more important than higher - * bandwidth, and we usually write entire - * request/response in a single syscall. + * On Linux, we are able to fine-tune keepalive + * intervals. Set smaller defaults, since the system-wide + * defaults are in days. */ - if (family != AF_UNIX) - sio_setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)); + int keepcnt = 5; + sio_setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &keepcnt, + sizeof(int)); + int keepidle = 30; + + sio_setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &keepidle, + sizeof(int)); + + int keepintvl = 60; + sio_setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &keepintvl, + sizeof(int)); +#endif } -/** Set tcp options for server sockets. */ +/** Set common client socket options. */ void -evio_setsockopt_tcpserver(int fd) +evio_setsockopt_client(int fd, int family, int type) +{ + int on = 1; + /* In case this throws, the socket is not leaked. */ + sio_setfl(fd, O_NONBLOCK, on); + if (type == SOCK_STREAM && family != AF_UNIX) { + if (family != AF_UNIX) { + /* + * SO_KEEPALIVE to ensure connections don't hang + * around for too long when a link goes away. + */ + evio_setsockopt_keepalive(fd); + /* + * Lower latency is more important than higher + * bandwidth, and we usually write entire + * request/response in a single syscall. + */ + sio_setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)); + } + } +} + +/** Set options for server sockets. */ +static void +evio_setsockopt_server(int fd, int family, int type) { int on = 1; /* In case this throws, the socket is not leaked. */ @@ -99,8 +130,6 @@ evio_setsockopt_tcpserver(int fd) /* Allow reuse local adresses. */ sio_setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); - sio_setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, - &on, sizeof(on)); /* Send all buffered messages on socket before take * control out from close(2) or shutdown(2). */ @@ -108,6 +137,8 @@ evio_setsockopt_tcpserver(int fd) sio_setsockopt(fd, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger)); + if (type == SOCK_STREAM && family != AF_UNIX) + evio_setsockopt_keepalive(fd); } static inline const char * @@ -136,8 +167,8 @@ evio_service_accept_cb(ev_loop * /* loop */, ev_io *watcher, if (fd < 0) /* EAGAIN, EWOULDLOCK, EINTR */ return; - /* set common tcp options */ - evio_setsockopt_tcp(fd, service->addr.sa_family); + /* set common client socket options */ + evio_setsockopt_client(fd, service->addr.sa_family, SOCK_STREAM); /* * Invoke the callback and pass it the accepted * socket. @@ -167,7 +198,7 @@ evio_service_bind_addr(struct evio_service *service) SOCK_STREAM, IPPROTO_TCP); try { - evio_setsockopt_tcpserver(fd); + evio_setsockopt_server(fd, service->addr.sa_family, SOCK_STREAM); if (sio_bind(fd, &service->addr, service->addr_len) || sio_listen(fd)) { diff --git a/src/evio.h b/src/evio.h index e05c017556f487fc0a6e979311fda288f495c2a0..906b968ca8c4ca56f1ec7c21d893649d5504e63c 100644 --- a/src/evio.h +++ b/src/evio.h @@ -152,9 +152,6 @@ evio_timeout_update(ev_loop *loop, ev_tstamp start, ev_tstamp *delay) } void -evio_setsockopt_tcp(int fd, int family); - -void -evio_setsockopt_tcpserver(int fd); +evio_setsockopt_client(int fd, int family, int type); #endif /* TARANTOOL_EVIO_H_INCLUDED */ diff --git a/src/sio.cc b/src/sio.cc index 3bafff5629503ea7d62c0d686af27bb1b08e5b7f..dc04c4eaea1b3ca8417caedc96b8363742f34ef1 100644 --- a/src/sio.cc +++ b/src/sio.cc @@ -33,6 +33,7 @@ #include <errno.h> #include <stdio.h> #include <limits.h> +#include <netinet/in.h> /* TCP_NODELAY */ #include <netinet/tcp.h> /* TCP_NODELAY */ #include <arpa/inet.h> /* inet_ntoa */ #include <poll.h> @@ -102,6 +103,10 @@ sio_option_name(int option) CASE_OPTION(SO_ERROR); CASE_OPTION(SO_REUSEADDR); CASE_OPTION(TCP_NODELAY); +#ifdef __linux__ + CASE_OPTION(TCP_KEEPCNT); + CASE_OPTION(TCP_KEEPINTVL); +#endif default: return "undefined"; } diff --git a/src/tarantool.cc b/src/tarantool.cc index 35ea9350497a6cc4c752e664cbeb2ac5cc0d6d20..92174466a75f27124e4032c083d5759df402e310 100644 --- a/src/tarantool.cc +++ b/src/tarantool.cc @@ -92,7 +92,7 @@ title(const char *role, const char *fmt, ...) (void) role; va_list ap; - char buf[128], *bufptr = buf, *bufend = buf + sizeof(buf); + char buf[256], *bufptr = buf, *bufend = buf + sizeof(buf); char *statusptr = status, *statusend = status + sizeof(status); statusptr += snprintf(statusptr, statusend - statusptr, "%s", role); bufptr += snprintf(bufptr, bufend - bufptr, "%s%s", role, diff --git a/test/box/misc.result b/test/box/misc.result index 249ece55c017ef29225019125d319d8d392c60c8..e08f06bee68af9cc5835a80a771bf2857e7ce0c6 100644 --- a/test/box/misc.result +++ b/test/box/misc.result @@ -208,6 +208,7 @@ t; - 'box.error.WAL_IO : 40' - 'box.error.CREATE_USER : 43' - 'box.error.CREATE_SPACE : 9' + - 'box.error.GRANT : 88' - 'box.error.TUPLE_REF_OVERFLOW : 86' - 'box.error.UNKNOWN_SCHEMA_OBJECT : 49' - 'box.error.PROC_LUA : 32' diff --git a/test/box/role.result b/test/box/role.result index ea2e0877d5819397d6471b4456a91e2b5fa31681..cc5187caebaa3128f74a23bff3dc26aa992c7c14 100644 --- a/test/box/role.result +++ b/test/box/role.result @@ -89,6 +89,9 @@ box.schema.user.grant('tester', 'iddqd') box.schema.user.revoke('tester', 'iddqd') --- ... +box.schema.role.drop('iddqd') +--- +... box.schema.user.revoke('tester', 'no-such-role') --- - error: Role 'no-such-role' is not found @@ -97,6 +100,9 @@ box.schema.user.grant('tester', 'no-such-role') --- - error: Role 'no-such-role' is not found ... +box.schema.user.drop('tester') +--- +... -- check for loops in role grants box.schema.role.create('a') --- @@ -217,11 +223,11 @@ box.session.su('grantee') ... box.space.test:insert{1} --- -- error: Duplicate key exists in unique index 0 +- error: Write access denied for user 'grantee' to space 'test' ... box.space.test:select{1} --- -- - [1] +- error: Read access denied for user 'grantee' to space 'test' ... box.session.su('admin') --- @@ -235,3 +241,4 @@ box.schema.user.drop('grantee') box.schema.user.drop('liaison') --- ... +-- cleanup diff --git a/test/box/role.test.lua b/test/box/role.test.lua index 5ec9efe38e7385c0141b72729e21d140e3753e7a..8d2a177a381073f2fb4a83b655f057ae9229144d 100644 --- a/test/box/role.test.lua +++ b/test/box/role.test.lua @@ -26,8 +26,10 @@ box.schema.user.grant('iddqd', 'iddqd') box.schema.user.revoke('iddqd', 'iddqd') box.schema.user.grant('tester', 'iddqd') box.schema.user.revoke('tester', 'iddqd') +box.schema.role.drop('iddqd') box.schema.user.revoke('tester', 'no-such-role') box.schema.user.grant('tester', 'no-such-role') +box.schema.user.drop('tester') -- check for loops in role grants box.schema.role.create('a') box.schema.role.create('b') @@ -73,3 +75,5 @@ box.schema.user.drop('test') box.schema.user.drop('grantee') box.schema.user.drop('liaison') + +-- cleanup