{"action":"created","comment":{"url":"https://api.github.com/repos/AltraMayor/gatekeeper/pulls/comments/92418546","pull_request_review_id":12916817,"id":92418546,"diff_hunk":"@@ -0,0 +1,468 @@\n+/*\n+ * Gatekeeper - DoS protection system.\n+ * Copyright (C) 2016 Digirati LTDA.\n+ *\n+ * This program is free software: you can redistribute it and/or modify\n+ * it under the terms of the GNU General Public License as published by\n+ * the Free Software Foundation, either version 3 of the License, or\n+ * (at your option) any later version.\n+ *\n+ * This program is distributed in the hope that it will be useful,\n+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\n+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n+ * GNU General Public License for more details.\n+ *\n+ * You should have received a copy of the GNU General Public License\n+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n+ */\n+\n+#include <stdbool.h>\n+\n+#include <rte_hash.h>\n+\n+#include <gatekeeper_lls.h>\n+#include \"cache.h\"\n+\n+#ifdef RTE_MACHINE_CPUFLAG_SSE4_2\n+#include <rte_hash_crc.h>\n+#define DEFAULT_HASH_FUNC rte_hash_crc\n+#else\n+#include <rte_jhash.h>\n+#define DEFAULT_HASH_FUNC rte_jhash\n+#endif\n+\n+/* XXX Sample parameters, need to be tested for better performance. */\n+#define LLS_CACHE_BURST_SIZE (32)\n+\n+static void\n+lls_send_request(struct lls_config *lls_conf, struct lls_cache *cache,\n+\tconst uint8_t *ip_be, const struct ether_addr *ha)\n+{\n+\t/*\n+\t * TODO Use network mask to determine which\n+\t * iface to use for invalid entries.\n+\t */\n+\tif (cache->iface_enabled(lls_conf->net, &lls_conf->net->front))\n+\t\tcache->xmit_req(&lls_conf->net->front, ip_be, ha,\n+\t\t\tlls_conf->tx_queue_front);\n+\tif (cache->iface_enabled(lls_conf->net, &lls_conf->net->back))\n+\t\tcache->xmit_req(&lls_conf->net->back, ip_be, ha,\n+\t\t\tlls_conf->tx_queue_back);\n+}\n+\n+static void\n+lls_cache_dump(struct lls_cache *cache)\n+{\n+\tuint32_t iter = 0;\n+\tint32_t index;\n+\tconst void *key;\n+\tvoid *data;\n+\n+\tRTE_LOG(INFO, GATEKEEPER, \"LLS cache (%s)\\n=====================\\n\",\n+\t\tcache->name);\n+\tindex = rte_hash_iterate(cache->hash, &key, &data, &iter);\n+\twhile (index >= 0) {\n+\t\tcache->print_record(cache, &cache->records[index]);\n+\t\tindex = rte_hash_iterate(cache->hash, &key, &data, &iter);\n+\t}\n+}\n+\n+static void\n+lls_update_subscribers(struct lls_record *record)\n+{\n+\tunsigned int i;\n+\tfor (i = 0; i < record->num_holds; i++) {\n+\t\tint call_again = false;\n+\n+\t\trecord->holds[i].cb(&record->map, record->holds[i].arg,\n+\t\t\tLLS_REPLY_RESOLUTION, &call_again);\n+\n+\t\tif (call_again)\n+\t\t\tcontinue;\n+\n+\t\t/* Delete hold; keep all holds in beginning of array. */\n+\t\trecord->num_holds--;\n+\t\tif (i < record->num_holds) {\n+\t\t\tmemcpy(&record->holds[i],\n+\t\t\t\t&record->holds[record->num_holds],\n+\t\t\t\tsizeof(record->holds[i]));\n+\t\t\t/*\n+\t\t\t * This cancels out the update of the for loop so we\n+\t\t\t * can redo update of hold at position @i, if needed.\n+\t\t\t */\n+\t\t\ti--;\n+\t\t}\n+\t}\n+}\n+\n+static int\n+lls_add_record(struct lls_cache *cache, const uint8_t *ip_be)\n+{\n+\tint ret = rte_hash_add_key(cache->hash, ip_be);\n+\tif (unlikely(ret == -EINVAL || ret == -ENOSPC)) {\n+\t\tchar ip_buf[cache->key_str_len];\n+\t\tchar *ip_str = cache->ip_str(cache, ip_be,\n+\t\t\tip_buf, cache->key_str_len);\n+\t\tRTE_LOG(ERR, HASH, \"%s, could not add record for %s\\n\",\n+\t\t\tret == -EINVAL ? \"Invalid params\" : \"No space\",\n+\t\t\tip_str == NULL ? cache->name : ip_str);\n+\t} else\n+\t\tRTE_ASSERT(ret >= 0);\n+\treturn ret;\n+}\n+\n+static void\n+lls_del_record(struct lls_cache *cache, const uint8_t *ip_be)\n+{\n+\tint32_t ret = rte_hash_del_key(cache->hash, ip_be);\n+\tif (unlikely(ret == -ENOENT || ret == -EINVAL)) {\n+\t\tchar ip_buf[cache->key_str_len];\n+\t\tchar *ip_str = cache->ip_str(cache, ip_be, ip_buf,\n+\t\t\tcache->key_str_len);\n+\t\tRTE_LOG(ERR, HASH, \"%s, record for %s not deleted\\n\",\n+\t\t\tret == -ENOENT ? \"No map found\" : \"Invalid params\",\n+\t\t\tip_str == NULL ? cache->name : ip_str);\n+\t}\n+}\n+\n+static void\n+lls_process_hold(struct lls_config *lls_conf, struct lls_hold *hold)\n+{\n+\tstruct lls_cache *cache = hold->cache;\n+\tstruct lls_record *record;\n+\tint ret = rte_hash_lookup(cache->hash, hold->ip_be);\n+\n+\tif (ret == -ENOENT) {\n+\t\tret = lls_add_record(cache, hold->ip_be);\n+\t\tif (ret < 0)\n+\t\t\treturn;\n+\n+\t\trecord = &hold->cache->records[ret];\n+\t\trecord->map.stale = true;\n+\t\tmemcpy(record->map.ip_be, hold->ip_be, cache->key_len);\n+\t\trecord->ts = time(NULL);\n+\t\tRTE_ASSERT(record->ts >= 0);\n+\t\trecord->holds[0] = *hold;\n+\t\trecord->num_holds = 1;\n+\n+\t\t/* Try to resolve record using broadcast. */\n+\t\tlls_send_request(lls_conf, cache, hold->ip_be, NULL);\n+\n+\t\tif (lls_conf->debug)\n+\t\t\tlls_cache_dump(cache);\n+\t\treturn;\n+\t} else if (unlikely(ret == -EINVAL)) {\n+\t\tchar ip_buf[cache->key_str_len];\n+\t\tchar *ip_str;\n+\t\tip_str = cache->ip_str(cache, hold->ip_be, ip_buf,\n+\t\t\tcache->key_str_len);\n+\t\tRTE_LOG(ERR, HASH,\n+\t\t\t\"Invalid params, could not get %s map; hold failed\\n\",\n+\t\t\tip_str == NULL ? cache->name : ip_str);\n+\t\treturn;\n+\t}\n+\n+\tRTE_ASSERT(ret >= 0);\n+\trecord = &hold->cache->records[ret];\n+\n+\tif (!record->map.stale) {\n+\t\tint call_again = false;\n+\t\t/* Alert requester this map is ready. */\n+\t\thold->cb((const struct lls_map *)&record->map, hold->arg,\n+\t\t\tLLS_REPLY_RESOLUTION, &call_again);\n+\t\tif (!call_again)\n+\t\t\treturn;\n+\t}\n+\trecord->holds[record->num_holds++] = *hold;\n+\n+\tif (lls_conf->debug)\n+\t\tlls_cache_dump(cache);\n+}\n+\n+static void\n+lls_process_put(struct lls_config *lls_conf, struct lls_put *put)\n+{\n+\tstruct lls_cache *cache = put->cache;\n+\tstruct lls_record *record;\n+\tunsigned int i;\n+\tint ret = rte_hash_lookup(cache->hash, put->ip_be);\n+\n+\tif (ret == -ENOENT) {\n+\t\t/*\n+\t\t * Not necessarily an error: the block may have indicated\n+\t\t * it did not want its callback to be called again, and\n+\t\t * all holds have been released on that entry.\n+\t\t */\n+\t\treturn;\n+\t} else if (unlikely(ret == -EINVAL)) {\n+\t\tchar ip_buf[cache->key_str_len];\n+\t\tchar *ip_str = cache->ip_str(cache, put->ip_be, ip_buf,\n+\t\t\tcache->key_str_len);\n+\t\tRTE_LOG(ERR, HASH,\n+\t\t\t\"Invalid params, could not get %s map; put failed\\n\",\n+\t\t\tip_str == NULL ? cache->name : ip_str);\n+\t\treturn;\n+\t}\n+\n+\tRTE_ASSERT(ret >= 0);\n+\trecord = &put->cache->records[ret];\n+\n+\tfor (i = 0; i < record->num_holds; i++) {\n+\t\tif (put->lcore_id == record->holds[i].lcore_id)\n+\t\t\tbreak;\n+\t}\n+\n+\t/* Requesting lcore not found in holds. */\n+\tif (i == record->num_holds)\n+\t\treturn;\n+\n+\t/*\n+\t * Alert the requester that its hold will be removed, so it\n+\t * may free any state that is keeping track of that hold.\n+\t *\n+\t * Technically the hold will be removed in the step\n+\t * below, but alerting the requester first removes the need\n+\t * to copy the hold into a temporary variable, remove\n+\t * the hold from record->holds, and then alert the\n+\t * requester using the temporary variable. This is OK since\n+\t * there's only one writer.\n+\t */\n+\trecord->holds[i].cb((const struct lls_map *)&record->map,\n+\t\trecord->holds[i].arg, LLS_REPLY_FORCE, NULL);\n+\n+\t/* Keep all holds in beginning of array. */\n+\trecord->num_holds--;\n+\tif (i < record->num_holds)\n+\t\tmemcpy(&record->holds[i], &record->holds[record->num_holds],\n+\t\t\tsizeof(record->holds[i]));\n+\n+\tif (lls_conf->debug)\n+\t\tlls_cache_dump(cache);\n+}\n+\n+void\n+lls_process_mod(struct lls_config *lls_conf, struct lls_mod *mod)\n+{\n+\tstruct lls_cache *cache = mod->cache;\n+\tstruct lls_record *record;\n+\tint changed_ha = false;\n+\tint changed_port = false;\n+\tint changed_stale = false;\n+\tint ret = rte_hash_lookup(cache->hash, mod->ip_be);\n+\n+\tif (ret == -ENOENT) {\n+\t\tret = lls_add_record(cache, mod->ip_be);\n+\t\tif (ret < 0)\n+\t\t\treturn;\n+\n+\t\t/* Fill-in new record. */\n+\t\trecord = &mod->cache->records[ret];\n+\t\tether_addr_copy(&mod->ha, &record->map.ha);\n+\t\trecord->map.port_id = mod->port_id;\n+\t\trecord->map.stale = false;\n+\t\tmemcpy(record->map.ip_be, mod->ip_be, cache->key_len);\n+\t\trecord->ts = mod->ts;\n+\t\trecord->num_holds = 0;\n+\n+\t\tif (lls_conf->debug)\n+\t\t\tlls_cache_dump(cache);\n+\t\treturn;\n+\t} else if (unlikely(ret == -EINVAL)) {\n+\t\tchar ip_buf[cache->key_str_len];\n+\t\tchar *ip_str;\n+\t\tip_str = cache->ip_str(cache, mod->ip_be, ip_buf,\n+\t\t\tcache->key_str_len);\n+\t\tRTE_LOG(ERR, HASH,\n+\t\t\t\"Invalid params, could not get %s map; mod failed\\n\",\n+\t\t\tip_str == NULL ? cache->name : ip_str);\n+\t\treturn;\n+\t}\n+\n+\tRTE_ASSERT(ret >= 0);\n+\trecord = &mod->cache->records[ret];\n+\n+\tif (!is_same_ether_addr(&mod->ha, &record->map.ha)) {\n+\t\tether_addr_copy(&mod->ha, &record->map.ha);\n+\t\tchanged_ha = true;\n+\t}\n+\tif (record->map.port_id != mod->port_id) {\n+\t\trecord->map.port_id = mod->port_id;\n+\t\tchanged_port = true;\n+\t}\n+\tif (record->map.stale) {\n+\t\trecord->map.stale = false;\n+\t\tchanged_stale = true;\n+\t}\n+\trecord->ts = mod->ts;\n+\n+\tif (changed_ha || changed_port || changed_stale) {\n+\t\tlls_update_subscribers(record);\n+\t\tif (lls_conf->debug)\n+\t\t\tlls_cache_dump(cache);\n+\t}\n+}\n+\n+unsigned int\n+lls_process_reqs(struct lls_config *lls_conf)\n+{\n+\tstruct lls_request *reqs[LLS_CACHE_BURST_SIZE];\n+\tunsigned int count = mb_dequeue_burst(&lls_conf->requests,\n+\t\t(void **)reqs, LLS_CACHE_BURST_SIZE);\n+\tunsigned int i;\n+\n+\tfor (i = 0; i < count; i++) {\n+\t\tswitch (reqs[i]->ty) {\n+\t\tcase LLS_REQ_HOLD:\n+\t\t\tlls_process_hold(lls_conf, &reqs[i]->u.hold);\n+\t\t\tbreak;\n+\t\tcase LLS_REQ_PUT:\n+\t\t\tlls_process_put(lls_conf, &reqs[i]->u.put);\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tRTE_LOG(ERR, GATEKEEPER,\n+\t\t\t\t\"lls: unrecognized request type (%d)\\n\",\n+\t\t\t\treqs[i]->ty);\n+\t\t\tbreak;\n+\t\t}\n+\t\tmb_free_entry(&lls_conf->requests, reqs[i]);\n+\t}\n+\n+\treturn count;\n+}\n+\n+int\n+lls_req(enum lls_req_ty ty, void *req_arg)\n+{\n+\tstruct lls_config *lls_conf = get_lls_conf();\n+\tstruct lls_request *req = mb_alloc_entry(&lls_conf->requests);\n+\tint ret;\n+\n+\tif (req == NULL) {\n+\t\tRTE_LOG(ERR, GATEKEEPER,\n+\t\t\t\"lls: allocation for request of type %d failed\", ty);\n+\t\treturn -1;\n+\t}\n+\n+\treq->ty = ty;\n+\n+\tswitch (ty) {\n+\tcase LLS_REQ_HOLD:\n+\t\treq->u.hold = *(struct lls_hold *)req_arg;\n+\t\tbreak;\n+\tcase LLS_REQ_PUT:\n+\t\treq->u.put = *(struct lls_put *)req_arg;\n+\t\tbreak;\n+\tdefault:\n+\t\tmb_free_entry(&lls_conf->requests, req);\n+\t\tRTE_LOG(ERR, GATEKEEPER,\n+\t\t\t\"lls: unknown request type %d failed\", ty);\n+\t\treturn -1;\n+\t}\n+\n+\tret = mb_send_entry(&lls_conf->requests, req);\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\treturn 0;\n+}\n+\n+void\n+lls_cache_scan(struct lls_config *lls_conf, struct lls_cache *cache)\n+{\n+\tuint32_t iter = 0;\n+\tint32_t index;\n+\tconst uint8_t *ip_be;\n+\tvoid *data;\n+\tstruct gatekeeper_if *front = &lls_conf->net->front;\n+\tstruct gatekeeper_if *back = &lls_conf->net->back;\n+\ttime_t now = time(NULL);\n+\n+\tRTE_ASSERT(now >= 0);\n+\tindex = rte_hash_iterate(cache->hash, (void *)&ip_be, &data, &iter);\n+\twhile (index >= 0) {\n+\t\tstruct lls_record *record = &cache->records[index];\n+\t\tuint32_t timeout;\n+\n+\t\t/*\n+\t\t * If a map is already stale, continue to\n+\t\t * try to resolve it while there's interest.\n+\t\t */\n+\t\tif (record->map.stale) {\n+\t\t\tif (record->num_holds > 0)\n+\t\t\t\tlls_send_request(lls_conf, cache, ip_be, NULL);\n+\t\t\telse\n+\t\t\t\tlls_del_record(cache, ip_be);","path":"lls/cache.c","position":394,"original_position":394,"commit_id":"09383adccaa9de423b0037984c1ad881cfdb96de","original_commit_id":"be171089a246800c710542ff83794e2a71214c39","user":{"login":"AltraMayor","id":727014,"avatar_url":"https://avatars.githubusercontent.com/u/727014?v=3","gravatar_id":"","url":"https://api.github.com/users/AltraMayor","html_url":"https://github.com/AltraMayor","followers_url":"https://api.github.com/users/AltraMayor/followers","following_url":"https://api.github.com/users/AltraMayor/following{/other_user}","gists_url":"https://api.github.com/users/AltraMayor/gists{/gist_id}","starred_url":"https://api.github.com/users/AltraMayor/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AltraMayor/subscriptions","organizations_url":"https://api.github.com/users/AltraMayor/orgs","repos_url":"https://api.github.com/users/AltraMayor/repos","events_url":"https://api.github.com/users/AltraMayor/events{/privacy}","received_events_url":"https://api.github.com/users/AltraMayor/received_events","type":"User","site_admin":false},"body":"Does `rte_hash_iterate()` support deletions while scanning? Not all iterators do so.","created_at":"2016-12-14T15:36:44Z","updated_at":"2016-12-14T15:51:21Z","html_url":"https://github.com/AltraMayor/gatekeeper/pull/20#discussion_r92418546","pull_request_url":"https://api.github.com/repos/AltraMayor/gatekeeper/pulls/20","_links":{"self":{"href":"https://api.github.com/repos/AltraMayor/gatekeeper/pulls/comments/92418546"},"html":{"href":"https://github.com/AltraMayor/gatekeeper/pull/20#discussion_r92418546"},"pull_request":{"href":"https://api.github.com/repos/AltraMayor/gatekeeper/pulls/20"}}},"pull_request":{"url":"https://api.github.com/repos/AltraMayor/gatekeeper/pulls/20","id":97205828,"html_url":"https://github.com/AltraMayor/gatekeeper/pull/20","diff_url":"https://github.com/AltraMayor/gatekeeper/pull/20.diff","patch_url":"https://github.com/AltraMayor/gatekeeper/pull/20.patch","issue_url":"https://api.github.com/repos/AltraMayor/gatekeeper/issues/20","number":20,"state":"open","locked":false,"title":"add LLS block and ARP","user":{"login":"cjdoucette","id":1501593,"avatar_url":"https://avatars.githubusercontent.com/u/1501593?v=3","gravatar_id":"","url":"https://api.github.com/users/cjdoucette","html_url":"https://github.com/cjdoucette","followers_url":"https://api.github.com/users/cjdoucette/followers","following_url":"https://api.github.com/users/cjdoucette/following{/other_user}","gists_url":"https://api.github.com/users/cjdoucette/gists{/gist_id}","starred_url":"https://api.github.com/users/cjdoucette/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cjdoucette/subscriptions","organizations_url":"https://api.github.com/users/cjdoucette/orgs","repos_url":"https://api.github.com/users/cjdoucette/repos","events_url":"https://api.github.com/users/cjdoucette/events{/privacy}","received_events_url":"https://api.github.com/users/cjdoucette/received_events","type":"User","site_admin":false},"body":"The LLS block and the ARP cache, implemented to allow other functional blocks to request one-time resolution or to always be notified of updates to an entry.\r\n\r\n@mengxiang0811 This patch, not included in this pull request, should give you an idea of how the GK block can subscribe to LLS entries.","created_at":"2016-12-08T21:38:02Z","updated_at":"2016-12-14T15:51:21Z","closed_at":null,"merged_at":null,"merge_commit_sha":"6f1f30d620e884b52a9547ccb1eb6f324816c089","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/AltraMayor/gatekeeper/pulls/20/commits","review_comments_url":"https://api.github.com/repos/AltraMayor/gatekeeper/pulls/20/comments","review_comment_url":"https://api.github.com/repos/AltraMayor/gatekeeper/pulls/comments{/number}","comments_url":"https://api.github.com/repos/AltraMayor/gatekeeper/issues/20/comments","statuses_url":"https://api.github.com/repos/AltraMayor/gatekeeper/statuses/09383adccaa9de423b0037984c1ad881cfdb96de","head":{"label":"cjdoucette:arp","ref":"arp","sha":"09383adccaa9de423b0037984c1ad881cfdb96de","user":{"login":"cjdoucette","id":1501593,"avatar_url":"https://avatars.githubusercontent.com/u/1501593?v=3","gravatar_id":"","url":"https://api.github.com/users/cjdoucette","html_url":"https://github.com/cjdoucette","followers_url":"https://api.github.com/users/cjdoucette/followers","following_url":"https://api.github.com/users/cjdoucette/following{/other_user}","gists_url":"https://api.github.com/users/cjdoucette/gists{/gist_id}","starred_url":"https://api.github.com/users/cjdoucette/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cjdoucette/subscriptions","organizations_url":"https://api.github.com/users/cjdoucette/orgs","repos_url":"https://api.github.com/users/cjdoucette/repos","events_url":"https://api.github.com/users/cjdoucette/events{/privacy}","received_events_url":"https://api.github.com/users/cjdoucette/received_events","type":"User","site_admin":false},"repo":{"id":66696649,"name":"gatekeeper","full_name":"cjdoucette/gatekeeper","owner":{"login":"cjdoucette","id":1501593,"avatar_url":"https://avatars.githubusercontent.com/u/1501593?v=3","gravatar_id":"","url":"https://api.github.com/users/cjdoucette","html_url":"https://github.com/cjdoucette","followers_url":"https://api.github.com/users/cjdoucette/followers","following_url":"https://api.github.com/users/cjdoucette/following{/other_user}","gists_url":"https://api.github.com/users/cjdoucette/gists{/gist_id}","starred_url":"https://api.github.com/users/cjdoucette/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cjdoucette/subscriptions","organizations_url":"https://api.github.com/users/cjdoucette/orgs","repos_url":"https://api.github.com/users/cjdoucette/repos","events_url":"https://api.github.com/users/cjdoucette/events{/privacy}","received_events_url":"https://api.github.com/users/cjdoucette/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/cjdoucette/gatekeeper","description":"First open source DoS protection system","fork":true,"url":"https://api.github.com/repos/cjdoucette/gatekeeper","forks_url":"https://api.github.com/repos/cjdoucette/gatekeeper/forks","keys_url":"https://api.github.com/repos/cjdoucette/gatekeeper/keys{/key_id}","collaborators_url":"https://api.github.com/repos/cjdoucette/gatekeeper/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/cjdoucette/gatekeeper/teams","hooks_url":"https://api.github.com/repos/cjdoucette/gatekeeper/hooks","issue_events_url":"https://api.github.com/repos/cjdoucette/gatekeeper/issues/events{/number}","events_url":"https://api.github.com/repos/cjdoucette/gatekeeper/events","assignees_url":"https://api.github.com/repos/cjdoucette/gatekeeper/assignees{/user}","branches_url":"https://api.github.com/repos/cjdoucette/gatekeeper/branches{/branch}","tags_url":"https://api.github.com/repos/cjdoucette/gatekeeper/tags","blobs_url":"https://api.github.com/repos/cjdoucette/gatekeeper/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/cjdoucette/gatekeeper/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/cjdoucette/gatekeeper/git/refs{/sha}","trees_url":"https://api.github.com/repos/cjdoucette/gatekeeper/git/trees{/sha}","statuses_url":"https://api.github.com/repos/cjdoucette/gatekeeper/statuses/{sha}","languages_url":"https://api.github.com/repos/cjdoucette/gatekeeper/languages","stargazers_url":"https://api.github.com/repos/cjdoucette/gatekeeper/stargazers","contributors_url":"https://api.github.com/repos/cjdoucette/gatekeeper/contributors","subscribers_url":"https://api.github.com/repos/cjdoucette/gatekeeper/subscribers","subscription_url":"https://api.github.com/repos/cjdoucette/gatekeeper/subscription","commits_url":"https://api.github.com/repos/cjdoucette/gatekeeper/commits{/sha}","git_commits_url":"https://api.github.com/repos/cjdoucette/gatekeeper/git/commits{/sha}","comments_url":"https://api.github.com/repos/cjdoucette/gatekeeper/comments{/number}","issue_comment_url":"https://api.github.com/repos/cjdoucette/gatekeeper/issues/comments{/number}","contents_url":"https://api.github.com/repos/cjdoucette/gatekeeper/contents/{+path}","compare_url":"https://api.github.com/repos/cjdoucette/gatekeeper/compare/{base}...{head}","merges_url":"https://api.github.com/repos/cjdoucette/gatekeeper/merges","archive_url":"https://api.github.com/repos/cjdoucette/gatekeeper/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/cjdoucette/gatekeeper/downloads","issues_url":"https://api.github.com/repos/cjdoucette/gatekeeper/issues{/number}","pulls_url":"https://api.github.com/repos/cjdoucette/gatekeeper/pulls{/number}","milestones_url":"https://api.github.com/repos/cjdoucette/gatekeeper/milestones{/number}","notifications_url":"https://api.github.com/repos/cjdoucette/gatekeeper/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/cjdoucette/gatekeeper/labels{/name}","releases_url":"https://api.github.com/repos/cjdoucette/gatekeeper/releases{/id}","deployments_url":"https://api.github.com/repos/cjdoucette/gatekeeper/deployments","created_at":"2016-08-27T04:52:32Z","updated_at":"2016-08-30T05:50:38Z","pushed_at":"2016-12-14T02:44:12Z","git_url":"git://github.com/cjdoucette/gatekeeper.git","ssh_url":"git@github.com:cjdoucette/gatekeeper.git","clone_url":"https://github.com/cjdoucette/gatekeeper.git","svn_url":"https://github.com/cjdoucette/gatekeeper","homepage":"","size":226,"stargazers_count":0,"watchers_count":0,"language":"C","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"AltraMayor:master","ref":"master","sha":"72538de9b37c23b7105a4f9c6251216cca49a79c","user":{"login":"AltraMayor","id":727014,"avatar_url":"https://avatars.githubusercontent.com/u/727014?v=3","gravatar_id":"","url":"https://api.github.com/users/AltraMayor","html_url":"https://github.com/AltraMayor","followers_url":"https://api.github.com/users/AltraMayor/followers","following_url":"https://api.github.com/users/AltraMayor/following{/other_user}","gists_url":"https://api.github.com/users/AltraMayor/gists{/gist_id}","starred_url":"https://api.github.com/users/AltraMayor/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AltraMayor/subscriptions","organizations_url":"https://api.github.com/users/AltraMayor/orgs","repos_url":"https://api.github.com/users/AltraMayor/repos","events_url":"https://api.github.com/users/AltraMayor/events{/privacy}","received_events_url":"https://api.github.com/users/AltraMayor/received_events","type":"User","site_admin":false},"repo":{"id":62745741,"name":"gatekeeper","full_name":"AltraMayor/gatekeeper","owner":{"login":"AltraMayor","id":727014,"avatar_url":"https://avatars.githubusercontent.com/u/727014?v=3","gravatar_id":"","url":"https://api.github.com/users/AltraMayor","html_url":"https://github.com/AltraMayor","followers_url":"https://api.github.com/users/AltraMayor/followers","following_url":"https://api.github.com/users/AltraMayor/following{/other_user}","gists_url":"https://api.github.com/users/AltraMayor/gists{/gist_id}","starred_url":"https://api.github.com/users/AltraMayor/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AltraMayor/subscriptions","organizations_url":"https://api.github.com/users/AltraMayor/orgs","repos_url":"https://api.github.com/users/AltraMayor/repos","events_url":"https://api.github.com/users/AltraMayor/events{/privacy}","received_events_url":"https://api.github.com/users/AltraMayor/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/AltraMayor/gatekeeper","description":"First open source DDoS protection system","fork":false,"url":"https://api.github.com/repos/AltraMayor/gatekeeper","forks_url":"https://api.github.com/repos/AltraMayor/gatekeeper/forks","keys_url":"https://api.github.com/repos/AltraMayor/gatekeeper/keys{/key_id}","collaborators_url":"https://api.github.com/repos/AltraMayor/gatekeeper/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/AltraMayor/gatekeeper/teams","hooks_url":"https://api.github.com/repos/AltraMayor/gatekeeper/hooks","issue_events_url":"https://api.github.com/repos/AltraMayor/gatekeeper/issues/events{/number}","events_url":"https://api.github.com/repos/AltraMayor/gatekeeper/events","assignees_url":"https://api.github.com/repos/AltraMayor/gatekeeper/assignees{/user}","branches_url":"https://api.github.com/repos/AltraMayor/gatekeeper/branches{/branch}","tags_url":"https://api.github.com/repos/AltraMayor/gatekeeper/tags","blobs_url":"https://api.github.com/repos/AltraMayor/gatekeeper/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/AltraMayor/gatekeeper/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/AltraMayor/gatekeeper/git/refs{/sha}","trees_url":"https://api.github.com/repos/AltraMayor/gatekeeper/git/trees{/sha}","statuses_url":"https://api.github.com/repos/AltraMayor/gatekeeper/statuses/{sha}","languages_url":"https://api.github.com/repos/AltraMayor/gatekeeper/languages","stargazers_url":"https://api.github.com/repos/AltraMayor/gatekeeper/stargazers","contributors_url":"https://api.github.com/repos/AltraMayor/gatekeeper/contributors","subscribers_url":"https://api.github.com/repos/AltraMayor/gatekeeper/subscribers","subscription_url":"https://api.github.com/repos/AltraMayor/gatekeeper/subscription","commits_url":"https://api.github.com/repos/AltraMayor/gatekeeper/commits{/sha}","git_commits_url":"https://api.github.com/repos/AltraMayor/gatekeeper/git/commits{/sha}","comments_url":"https://api.github.com/repos/AltraMayor/gatekeeper/comments{/number}","issue_comment_url":"https://api.github.com/repos/AltraMayor/gatekeeper/issues/comments{/number}","contents_url":"https://api.github.com/repos/AltraMayor/gatekeeper/contents/{+path}","compare_url":"https://api.github.com/repos/AltraMayor/gatekeeper/compare/{base}...{head}","merges_url":"https://api.github.com/repos/AltraMayor/gatekeeper/merges","archive_url":"https://api.github.com/repos/AltraMayor/gatekeeper/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/AltraMayor/gatekeeper/downloads","issues_url":"https://api.github.com/repos/AltraMayor/gatekeeper/issues{/number}","pulls_url":"https://api.github.com/repos/AltraMayor/gatekeeper/pulls{/number}","milestones_url":"https://api.github.com/repos/AltraMayor/gatekeeper/milestones{/number}","notifications_url":"https://api.github.com/repos/AltraMayor/gatekeeper/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/AltraMayor/gatekeeper/labels{/name}","releases_url":"https://api.github.com/repos/AltraMayor/gatekeeper/releases{/id}","deployments_url":"https://api.github.com/repos/AltraMayor/gatekeeper/deployments","created_at":"2016-07-06T18:53:45Z","updated_at":"2016-11-04T13:44:41Z","pushed_at":"2016-12-14T02:42:06Z","git_url":"git://github.com/AltraMayor/gatekeeper.git","ssh_url":"git@github.com:AltraMayor/gatekeeper.git","clone_url":"https://github.com/AltraMayor/gatekeeper.git","svn_url":"https://github.com/AltraMayor/gatekeeper","homepage":"","size":174,"stargazers_count":3,"watchers_count":3,"language":"C","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":3,"mirror_url":null,"open_issues_count":10,"forks":3,"open_issues":10,"watchers":3,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/AltraMayor/gatekeeper/pulls/20"},"html":{"href":"https://github.com/AltraMayor/gatekeeper/pull/20"},"issue":{"href":"https://api.github.com/repos/AltraMayor/gatekeeper/issues/20"},"comments":{"href":"https://api.github.com/repos/AltraMayor/gatekeeper/issues/20/comments"},"review_comments":{"href":"https://api.github.com/repos/AltraMayor/gatekeeper/pulls/20/comments"},"review_comment":{"href":"https://api.github.com/repos/AltraMayor/gatekeeper/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/AltraMayor/gatekeeper/pulls/20/commits"},"statuses":{"href":"https://api.github.com/repos/AltraMayor/gatekeeper/statuses/09383adccaa9de423b0037984c1ad881cfdb96de"}}}}
{"actor":{"display_login":"AltraMayor"}}