static int list_set_uadd(struct ip_set *set, void *value, const struct ip_set_ext *ext, struct ip_set_ext *mext, u32 flags) { struct list_set *map = set->data; struct set_adt_elem *d = value; struct set_elem *e; bool flag_exist = flags & IPSET_FLAG_EXIST; u32 i, ret = 0; if (SET_WITH_TIMEOUT(set)) set_cleanup_entries(set); /* Check already added element */ for (i = 0; i < map->size; i++) { e = list_set_elem(set, map, i); if (e->id == IPSET_INVALID_ID) goto insert; else if (e->id != d->id) continue; if ((d->before > 1 && !id_eq(set, i + 1, d->refid)) || (d->before < 0 && (i == 0 || !id_eq(set, i - 1, d->refid)))) /* Before/after doesn't match */ return -IPSET_ERR_REF_EXIST; if (!flag_exist) /* Can't re-add */ return -IPSET_ERR_EXIST; /* Update extensions */ ip_set_ext_destroy(set, e); if (SET_WITH_TIMEOUT(set)) ip_set_timeout_set(ext_timeout(e, set), ext->timeout); if (SET_WITH_COUNTER(set)) ip_set_init_counter(ext_counter(e, set), ext); if (SET_WITH_COMMENT(set)) ip_set_init_comment(ext_comment(e, set), ext); /* Set is already added to the list */ ip_set_put_byindex(map->net, d->id); return 0; } insert: ret = -IPSET_ERR_LIST_FULL; for (i = 0; i < map->size && ret == -IPSET_ERR_LIST_FULL; i++) { e = list_set_elem(set, map, i); if (e->id == IPSET_INVALID_ID) ret = d->before != 0 ? -IPSET_ERR_REF_EXIST : list_set_add(set, i, d, ext); else if (e->id != d->refid) continue; else if (d->before > 0) ret = list_set_add(set, i, d, ext); else if (i + 1 < map->size) ret = list_set_add(set, i + 1, d, ext); } return ret; }
static int list_set_utest(struct ip_set *set, void *value, const struct ip_set_ext *ext, struct ip_set_ext *mext, u32 flags) { struct list_set *map = set->data; struct set_adt_elem *d = value; struct set_elem *e; u32 i; int ret; for (i = 0; i < map->size; i++) { e = list_set_elem(set, map, i); if (e->id == IPSET_INVALID_ID) return 0; else if (SET_WITH_TIMEOUT(set) && ip_set_timeout_expired(ext_timeout(e, set))) continue; else if (e->id != d->id) continue; if (d->before == 0) return 1; else if (d->before > 0) ret = id_eq(set, i + 1, d->refid); else ret = i > 0 && id_eq(set, i - 1, d->refid); return ret; } return 0; }
static int list_set_udel(struct ip_set *set, void *value, const struct ip_set_ext *ext, struct ip_set_ext *mext, u32 flags) { struct list_set *map = set->data; struct set_adt_elem *d = value; struct set_elem *e; u32 i; for (i = 0; i < map->size; i++) { e = list_set_elem(set, map, i); if (e->id == IPSET_INVALID_ID) return d->before != 0 ? -IPSET_ERR_REF_EXIST : -IPSET_ERR_EXIST; else if (SET_WITH_TIMEOUT(set) && ip_set_timeout_expired(ext_timeout(e, set))) continue; else if (e->id != d->id) continue; if (d->before == 0) return list_set_del(set, i); else if (d->before > 0) { if (!id_eq(set, i + 1, d->refid)) return -IPSET_ERR_REF_EXIST; return list_set_del(set, i); } else if (i == 0 || !id_eq(set, i - 1, d->refid)) return -IPSET_ERR_REF_EXIST; else return list_set_del(set, i); } return -IPSET_ERR_EXIST; }
static int list_set_ktest(struct ip_set *set, const struct sk_buff *skb, const struct xt_action_param *par, struct ip_set_adt_opt *opt, const struct ip_set_ext *ext) { struct list_set *map = set->data; struct set_elem *e; u32 i, cmdflags = opt->cmdflags; int ret; /* Don't lookup sub-counters at all */ opt->cmdflags &= ~IPSET_FLAG_MATCH_COUNTERS; if (opt->cmdflags & IPSET_FLAG_SKIP_SUBCOUNTER_UPDATE) opt->cmdflags &= ~IPSET_FLAG_SKIP_COUNTER_UPDATE; for (i = 0; i < map->size; i++) { e = list_set_elem(set, map, i); if (e->id == IPSET_INVALID_ID) return 0; if (SET_WITH_TIMEOUT(set) && ip_set_timeout_expired(ext_timeout(e, set))) continue; ret = ip_set_test(e->id, skb, par, opt); if (ret > 0) { if (SET_WITH_COUNTER(set)) ip_set_update_counter(ext_counter(e, set), ext, &opt->ext, cmdflags); return ret; } } return 0; }
static int list_set_del(struct ip_set *set, u32 i) { struct list_set *map = set->data; struct set_elem *e = list_set_elem(set, map, i); ip_set_put_byindex(map->net, e->id); ip_set_ext_destroy(set, e); if (i < map->size - 1) memmove(e, list_set_elem(set, map, i + 1), set->dsize * (map->size - (i + 1))); /* Last element */ e = list_set_elem(set, map, map->size - 1); e->id = IPSET_INVALID_ID; return 0; }
static int list_set_list(const struct ip_set *set, struct sk_buff *skb, struct netlink_callback *cb) { const struct list_set *map = set->data; struct nlattr *atd, *nested; u32 i, first = cb->args[2]; const struct set_elem *e; atd = ipset_nest_start(skb, IPSET_ATTR_ADT); if (!atd) return -EMSGSIZE; for (; cb->args[2] < map->size; cb->args[2]++) { i = cb->args[2]; e = list_set_elem(map, i); if (e->id == IPSET_INVALID_ID) goto finish; if (with_timeout(map->timeout) && list_set_expired(map, i)) continue; nested = ipset_nest_start(skb, IPSET_ATTR_DATA); if (!nested) { if (i == first) { nla_nest_cancel(skb, atd); return -EMSGSIZE; } else goto nla_put_failure; } if (nla_put_string(skb, IPSET_ATTR_NAME, ip_set_name_byindex(e->id))) goto nla_put_failure; if (with_timeout(map->timeout)) { const struct set_telem *te = (const struct set_telem *) e; __be32 to = htonl(ip_set_timeout_get(te->timeout)); if (nla_put_net32(skb, IPSET_ATTR_TIMEOUT, to)) goto nla_put_failure; } ipset_nest_end(skb, nested); } finish: ipset_nest_end(skb, atd); /* Set listing finished */ cb->args[2] = 0; return 0; nla_put_failure: nla_nest_cancel(skb, nested); ipset_nest_end(skb, atd); if (unlikely(i == first)) { cb->args[2] = 0; return -EMSGSIZE; } return 0; }
static bool id_eq(const struct list_set *map, u32 i, ip_set_id_t id) { const struct set_elem *elem; if (i < map->size) { elem = list_set_elem(map, i); return elem->id == id; } return 0; }
static int list_set_del(struct list_set *map, u32 i) { struct set_elem *a = list_set_elem(map, i), *b; ip_set_put_byindex(a->id); for (; i < map->size - 1; i++) { b = list_set_elem(map, i + 1); a->id = b->id; if (with_timeout(map->timeout)) ((struct set_telem *)a)->timeout = ((struct set_telem *)b)->timeout; a = b; if (a->id == IPSET_INVALID_ID) break; } /* Last element */ a->id = IPSET_INVALID_ID; return 0; }
static void list_elem_add(struct list_set *map, u32 i, ip_set_id_t id) { struct set_elem *e; for (; i < map->size; i++) { e = list_set_elem(map, i); swap(e->id, id); if (e->id == IPSET_INVALID_ID) break; } }
static int list_set_add(struct ip_set *set, u32 i, struct set_adt_elem *d, const struct ip_set_ext *ext) { struct list_set *map = set->data; struct set_elem *e = list_set_elem(set, map, i); if (e->id != IPSET_INVALID_ID) { if (i == map->size - 1) { /* Last element replaced: e.g. add new,before,last */ ip_set_put_byindex(map->net, e->id); ip_set_ext_destroy(set, e); } else { struct set_elem *x = list_set_elem(set, map, map->size - 1); /* Last element pushed off */ if (x->id != IPSET_INVALID_ID) { ip_set_put_byindex(map->net, x->id); ip_set_ext_destroy(set, x); } memmove(list_set_elem(set, map, i + 1), e, set->dsize * (map->size - (i + 1))); /* Extensions must be initialized to zero */ memset(e, 0, set->dsize); } } e->id = d->id; if (SET_WITH_TIMEOUT(set)) ip_set_timeout_set(ext_timeout(e, set), ext->timeout); if (SET_WITH_COUNTER(set)) ip_set_init_counter(ext_counter(e, set), ext); if (SET_WITH_COMMENT(set)) ip_set_init_comment(ext_comment(e, set), ext); if (SET_WITH_SKBINFO(set)) ip_set_init_skbinfo(ext_skbinfo(e, set), ext); return 0; }
static int list_set_list(const struct ip_set *set, struct sk_buff *skb, struct netlink_callback *cb) { const struct list_set *map = set->data; struct nlattr *atd, *nested; u32 i, first = cb->args[IPSET_CB_ARG0]; const struct set_elem *e; atd = ipset_nest_start(skb, IPSET_ATTR_ADT); if (!atd) return -EMSGSIZE; for (; cb->args[IPSET_CB_ARG0] < map->size; cb->args[IPSET_CB_ARG0]++) { i = cb->args[IPSET_CB_ARG0]; e = list_set_elem(set, map, i); if (e->id == IPSET_INVALID_ID) goto finish; if (SET_WITH_TIMEOUT(set) && ip_set_timeout_expired(ext_timeout(e, set))) continue; nested = ipset_nest_start(skb, IPSET_ATTR_DATA); if (!nested) { if (i == first) { nla_nest_cancel(skb, atd); return -EMSGSIZE; } else goto nla_put_failure; } if (nla_put_string(skb, IPSET_ATTR_NAME, ip_set_name_byindex(map->net, e->id))) goto nla_put_failure; if (ip_set_put_extensions(skb, set, e, true)) goto nla_put_failure; ipset_nest_end(skb, nested); } finish: ipset_nest_end(skb, atd); /* Set listing finished */ cb->args[IPSET_CB_ARG0] = 0; return 0; nla_put_failure: nla_nest_cancel(skb, nested); if (unlikely(i == first)) { cb->args[IPSET_CB_ARG0] = 0; return -EMSGSIZE; } ipset_nest_end(skb, atd); return 0; }
static bool id_eq_timeout(const struct list_set *map, u32 i, ip_set_id_t id) { const struct set_elem *elem; if (i < map->size) { elem = list_set_elem(map, i); return !!(elem->id == id && !(with_timeout(map->timeout) && list_set_expired(map, i))); } return 0; }
static bool id_eq(const struct ip_set *set, u32 i, ip_set_id_t id) { const struct list_set *map = set->data; const struct set_elem *e; if (i >= map->size) return 0; e = list_set_elem(set, map, i); return !!(e->id == id && !(SET_WITH_TIMEOUT(set) && ip_set_timeout_expired(ext_timeout(e, set)))); }
static void list_set_flush(struct ip_set *set) { struct list_set *map = set->data; struct set_elem *elem; u32 i; for (i = 0; i < map->size; i++) { elem = list_set_elem(map, i); if (elem->id != IPSET_INVALID_ID) { ip_set_put_byindex(elem->id); elem->id = IPSET_INVALID_ID; } } }
static void list_set_flush(struct ip_set *set) { struct list_set *map = set->data; struct set_elem *e; u32 i; for (i = 0; i < map->size; i++) { e = list_set_elem(set, map, i); if (e->id != IPSET_INVALID_ID) { ip_set_put_byindex(map->net, e->id); ip_set_ext_destroy(set, e); e->id = IPSET_INVALID_ID; } } }
static int list_set_add(struct list_set *map, u32 i, ip_set_id_t id, unsigned long timeout) { const struct set_elem *e = list_set_elem(map, i); if (i == map->size - 1 && e->id != IPSET_INVALID_ID) /* Last element replaced: e.g. add new,before,last */ ip_set_put_byindex(e->id); if (with_timeout(map->timeout)) list_elem_tadd(map, i, id, ip_set_timeout_set(timeout)); else list_elem_add(map, i, id); return 0; }
static void set_cleanup_entries(struct ip_set *set) { struct list_set *map = set->data; struct set_elem *e; u32 i = 0; while (i < map->size) { e = list_set_elem(set, map, i); if (e->id != IPSET_INVALID_ID && ip_set_timeout_expired(ext_timeout(e, set))) list_set_del(set, i); /* Check element moved to position i in next loop */ else i++; } }
static bool init_list_set(struct net *net, struct ip_set *set, u32 size) { struct list_set *map; struct set_elem *e; u32 i; map = kzalloc(sizeof(*map) + size * set->dsize, GFP_KERNEL); if (!map) return false; map->size = size; map->net = net; set->data = map; for (i = 0; i < size; i++) { e = list_set_elem(set, map, i); e->id = IPSET_INVALID_ID; } return true; }
static int list_set_kadt(struct ip_set *set, const struct sk_buff *skb, const struct xt_action_param *par, enum ipset_adt adt, const struct ip_set_adt_opt *opt) { struct list_set *map = set->data; struct set_elem *elem; u32 i; int ret; for (i = 0; i < map->size; i++) { elem = list_set_elem(map, i); if (elem->id == IPSET_INVALID_ID) return 0; if (with_timeout(map->timeout) && list_set_expired(map, i)) continue; switch (adt) { case IPSET_TEST: ret = ip_set_test(elem->id, skb, par, opt); if (ret > 0) return ret; break; case IPSET_ADD: ret = ip_set_add(elem->id, skb, par, opt); if (ret == 0) return ret; break; case IPSET_DEL: ret = ip_set_del(elem->id, skb, par, opt); if (ret == 0) return ret; break; default: break; } } return -EINVAL; }
static int list_set_kadd(struct ip_set *set, const struct sk_buff *skb, const struct xt_action_param *par, struct ip_set_adt_opt *opt, const struct ip_set_ext *ext) { struct list_set *map = set->data; struct set_elem *e; u32 i; int ret; for (i = 0; i < map->size; i++) { e = list_set_elem(set, map, i); if (e->id == IPSET_INVALID_ID) return 0; if (SET_WITH_TIMEOUT(set) && ip_set_timeout_expired(ext_timeout(e, set))) continue; ret = ip_set_add(e->id, skb, par, opt); if (ret == 0) return ret; } return 0; }
static bool init_list_set(struct ip_set *set, u32 size, size_t dsize, unsigned long timeout) { struct list_set *map; struct set_elem *e; u32 i; map = kzalloc(sizeof(*map) + size * dsize, GFP_KERNEL); if (!map) return false; map->size = size; map->dsize = dsize; map->timeout = timeout; set->data = map; for (i = 0; i < size; i++) { e = list_set_elem(map, i); e->id = IPSET_INVALID_ID; } return true; }
static int list_set_uadt(struct ip_set *set, struct nlattr *tb[], enum ipset_adt adt, u32 *lineno, u32 flags, bool retried) { struct list_set *map = set->data; bool with_timeout = with_timeout(map->timeout); bool flag_exist = flags & IPSET_FLAG_EXIST; int before = 0; u32 timeout = map->timeout; ip_set_id_t id, refid = IPSET_INVALID_ID; const struct set_elem *elem; struct ip_set *s; u32 i; int ret = 0; if (unlikely(!tb[IPSET_ATTR_NAME] || !ip_set_optattr_netorder(tb, IPSET_ATTR_TIMEOUT) || !ip_set_optattr_netorder(tb, IPSET_ATTR_CADT_FLAGS))) return -IPSET_ERR_PROTOCOL; if (tb[IPSET_ATTR_LINENO]) *lineno = nla_get_u32(tb[IPSET_ATTR_LINENO]); id = ip_set_get_byname(nla_data(tb[IPSET_ATTR_NAME]), &s); if (id == IPSET_INVALID_ID) return -IPSET_ERR_NAME; /* "Loop detection" */ if (s->type->features & IPSET_TYPE_NAME) { ret = -IPSET_ERR_LOOP; goto finish; } if (tb[IPSET_ATTR_CADT_FLAGS]) { u32 f = ip_set_get_h32(tb[IPSET_ATTR_CADT_FLAGS]); before = f & IPSET_FLAG_BEFORE; } if (before && !tb[IPSET_ATTR_NAMEREF]) { ret = -IPSET_ERR_BEFORE; goto finish; } if (tb[IPSET_ATTR_NAMEREF]) { refid = ip_set_get_byname(nla_data(tb[IPSET_ATTR_NAMEREF]), &s); if (refid == IPSET_INVALID_ID) { ret = -IPSET_ERR_NAMEREF; goto finish; } if (!before) before = -1; } if (tb[IPSET_ATTR_TIMEOUT]) { if (!with_timeout) { ret = -IPSET_ERR_TIMEOUT; goto finish; } timeout = ip_set_timeout_uget(tb[IPSET_ATTR_TIMEOUT]); } if (with_timeout && adt != IPSET_TEST) cleanup_entries(map); switch (adt) { case IPSET_TEST: for (i = 0; i < map->size && !ret; i++) { elem = list_set_elem(map, i); if (elem->id == IPSET_INVALID_ID || (before != 0 && i + 1 >= map->size)) break; else if (with_timeout && list_set_expired(map, i)) continue; else if (before > 0 && elem->id == id) ret = id_eq_timeout(map, i + 1, refid); else if (before < 0 && elem->id == refid) ret = id_eq_timeout(map, i + 1, id); else if (before == 0 && elem->id == id) ret = 1; } break; case IPSET_ADD: for (i = 0; i < map->size; i++) { elem = list_set_elem(map, i); if (elem->id != id) continue; if (!(with_timeout && flag_exist)) { ret = -IPSET_ERR_EXIST; goto finish; } else { struct set_telem *e = list_set_telem(map, i); if ((before > 1 && !id_eq(map, i + 1, refid)) || (before < 0 && (i == 0 || !id_eq(map, i - 1, refid)))) { ret = -IPSET_ERR_EXIST; goto finish; } e->timeout = ip_set_timeout_set(timeout); ip_set_put_byindex(id); ret = 0; goto finish; } } ret = -IPSET_ERR_LIST_FULL; for (i = 0; i < map->size && ret == -IPSET_ERR_LIST_FULL; i++) { elem = list_set_elem(map, i); if (elem->id == IPSET_INVALID_ID) ret = before != 0 ? -IPSET_ERR_REF_EXIST : list_set_add(map, i, id, timeout); else if (elem->id != refid) continue; else if (before > 0) ret = list_set_add(map, i, id, timeout); else if (i + 1 < map->size) ret = list_set_add(map, i + 1, id, timeout); } break; case IPSET_DEL: ret = -IPSET_ERR_EXIST; for (i = 0; i < map->size && ret == -IPSET_ERR_EXIST; i++) { elem = list_set_elem(map, i); if (elem->id == IPSET_INVALID_ID) { ret = before != 0 ? -IPSET_ERR_REF_EXIST : -IPSET_ERR_EXIST; break; } else if (elem->id == id && (before == 0 || (before > 0 && id_eq(map, i + 1, refid)))) ret = list_set_del(map, i); else if (elem->id == refid && before < 0 && id_eq(map, i + 1, id)) ret = list_set_del(map, i + 1); } break; default: break; } finish: if (refid != IPSET_INVALID_ID) ip_set_put_byindex(refid); if (adt != IPSET_ADD || ret) ip_set_put_byindex(id); return ip_set_eexist(ret, flags) ? 0 : ret; }