static int tfw_bmb_connect(int tn, int cn) { int ret; struct sock *sk; TfwBmbConn *conn; conn = &bmb_task[tn].conn[cn]; ret = ss_sock_create(bmb_server_address.sa.sa_family, SOCK_STREAM, IPPROTO_TCP, &sk); if (ret) { TFW_ERR("Unable to create kernel socket (%d)\n", ret); return ret; } ss_proto_init(&conn->proto, &bmb_hooks, tn * nconns + cn); rcu_assign_sk_user_data(sk, &conn->proto); ss_set_callbacks(sk); ret = ss_connect(sk, &bmb_server_address.sa, tfw_addr_sa_len(&bmb_server_address), 0); if (ret) { TFW_ERR("Connect error on server socket sk %p (%d)\n", sk, ret); tfw_connection_unlink_from_sk(sk); ss_close(sk); return ret; } conn->sk = sk; bmb_task[tn].conn_attempt++; return 0; }
/** * Check name of an attribute or name * * Much like C identifiers, names must start with a letter and consist * only of alphanumeric and underscore characters. Currently this is * only a sanity check and the parser code would work without it, but in * future it may help to preserve compatibility if we decide to change * the parser. */ static bool check_identifier(const char *buf, size_t len) { size_t i; if (!len) { TFW_ERR("the string is empty\n"); return false; } if (!isalpha(buf[0])) { TFW_ERR("the first character is not a letter: '%c'\n", buf[0]); return false; } for (i = 0; i < len; ++i) { if (!isalnum(buf[i]) && buf[i] != '_') { TFW_ERR("invalid character: '%c' in '%.*s'\n", buf[i], (int)len, buf); return false; } } return true; }
static void tfw_bmb_msg_send(int tn, int cn) { TfwBmbTask *task = &bmb_task[tn]; int fz_tries = 0, r; TfwStr msg; TfwHttpMsg req; TfwMsgIter it; BUG_ON(!task->conn[cn].sk); do { if (++fz_tries > 10) { TFW_ERR("Too many fuzzer tries to generate request\n"); return; } r = fuzz_gen(&task->ctx, task->buf, task->buf + BUF_SIZE, 0, 1, FUZZ_REQ); if (r < 0) { TFW_ERR("Cannot generate HTTP request, r=%d\n", r); return; } if (r == FUZZ_END) fuzz_init(&task->ctx, true); } while (r != FUZZ_VALID); msg.ptr = task->buf; msg.skb = NULL; msg.len = strlen(msg.ptr); msg.flags = 0; if (!tfw_http_msg_create(&req, &it, Conn_Clnt, msg.len)) { TFW_WARN("Cannot create HTTP request.\n"); return; } if (verbose) TFW_LOG("Send request:\n" "------------------------------\n" "%s\n" "------------------------------\n", task->buf); tfw_http_msg_write(&it, &req, &msg); ss_send(task->conn[cn].sk, &req.msg.skb_list, true); atomic_inc(&bmb_request_send); }
static int fop_open(struct inode *inode, struct file *file) { fmode_t mode = file->f_mode; tfw_debugfs_handler_t fn = file->f_inode->i_private; TfwDebugfsIoState *state; if ((mode & FMODE_READ) && (mode & FMODE_WRITE)) { TFW_ERR("This debugfs file can't be opened in read-write mode"); return -EPERM; } state = kzalloc(sizeof(*state), GFP_KERNEL); BUG_ON(!state); /* Simply allocate a fixed-size buffer. The buffer doesn't expand, the * data is cropped if it doesn't fit to it. Later on we may change it */ state->buf_size = PAGE_SIZE; state->buf = kmalloc(state->buf_size, GFP_KERNEL); BUG_ON(!state->buf); if (mode & FMODE_WRITE) state->is_input = true; else state->len = fn(state->is_input, state->buf, state->buf_size); file->private_data = state; return 0; }
static int tfw_bmb_worker(void *data) { int tn = (int)(long)data; TfwBmbTask *task = &bmb_task[tn]; int attempt, send, k, i; unsigned long time_max; fuzz_init(&task->ctx, true); for (k = 0; k < niters; k++) { task->conn_attempt = 0; atomic_set(&task->conn_compl, 0); atomic_set(&task->conn_error, 0); atomic_set(&task->conn_rd_tail, 0); init_waitqueue_head(&task->conn_wq); for (i = 0; i < nconns; i++) tfw_bmb_connect(tn, i); set_freezable(); time_max = jiffies + 60 * HZ; attempt = task->conn_attempt; do { #define COND() (atomic_read(&task->conn_compl) > 0 || \ atomic_read(&task->conn_error) == attempt) wait_event_freezable_timeout(task->conn_wq, COND(), HZ); #undef COND if (atomic_read(&task->conn_compl) > 0) break; if (atomic_read(&task->conn_error) == attempt) goto release_sockets; if (jiffies > time_max) { TFW_ERR("worker exceeded maximum wait time\n"); goto release_sockets; } } while (!kthread_should_stop()); for (send = 0; send < nconns * nmessages; ) { int tail = atomic_read(&task->conn_rd_tail); for (i = 0; i < tail; i++){ tfw_bmb_msg_send(tn, task->conn_rd[i]); send++; } } release_sockets: atomic_add(attempt, &bmb_conn_attempt); atomic_add(atomic_read(&task->conn_compl), &bmb_conn_compl); atomic_add(atomic_read(&task->conn_error), &bmb_conn_error); tfw_bmb_release_sockets(tn); } task->task_struct = NULL; atomic_dec(&bmb_threads); wake_up(&bmb_task_wq); return 0; }
static ssize_t fop_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { int len; TfwDebugfsIoState *state = file->private_data; if (!state->is_input) { TFW_ERR("Can't write to this debugfs file: " "it was open()'ed only for reading\n"); return -EPERM; } /* copy data from user-space */ len = simple_write_to_buffer(state->buf, (state->buf_size - 1), ppos, user_buf, count); if (len > 0) { state->len += len; state->buf[state->len] = '\0'; } return len; }
static int __init tfw_bmb_init(void) { long i; volatile unsigned long ts_start; struct task_struct *task; int r = 0; if (tfw_addr_pton(&TFW_STR_FROM(server), &bmb_server_address)) { TFW_ERR("Unable to parse server's address: %s", server); return -EINVAL; } TFW_LOG("Started bomber module, server's address is %s\n", server); if (tfw_bmb_alloc()) return -ENOMEM; ts_start = jiffies; for (i = 0; i < nthreads; i++) { task = kthread_create(tfw_bmb_worker, (void *)i, "worker"); if (IS_ERR_OR_NULL(task)) { TFW_ERR("Unable to create worker\n"); r = -EINVAL; goto stop_threads; } bmb_task[i].task_struct = task; } atomic_set(&bmb_threads, nthreads); for (i = 0; i < nthreads; i++) wake_up_process(bmb_task[i].task_struct); wait_event_interruptible(bmb_task_wq, !atomic_read(&bmb_threads)); tfw_bmb_report(ts_start); stop_threads: tfw_bmb_stop_threads(); tfw_bmb_free(); return r; }
/** * Register a hook which will be called with priority @prio when FSM @fsm_id * reaches state @state. The hooks switches calling FSM to FSM represented by * @hndl_fsm_id at state @st0. * @return resulting priority at which the hook was registered or * negative value of failure. */ int tfw_gfsm_register_hook(int fsm_id, int prio, int state, unsigned short hndl_fsm_id, int st0) { int shift, st = state & TFW_GFSM_STATE_MASK; unsigned int st_bit = 1 << st; /* Initial FSM state isn't hookable. */ BUG_ON(!st); if (prio == TFW_GFSM_HOOK_PRIORITY_ANY) { /* * Try to register the hook with higest priority. * If the state slot for the priority is already acquired, * then try lower priority. */ for (prio = TFW_GFSM_HOOK_PRIORITY_HIGH; prio < TFW_GFSM_HOOK_PRIORITY_NUM; ++prio) if (!(fsm_hooks_bm[fsm_id][prio] & st_bit)) break; if (prio == TFW_GFSM_HOOK_PRIORITY_NUM) { TFW_ERR("All hook slots for FSM %d are acquired\n", fsm_id); return -EBUSY; } } shift = prio * TFW_GFSM_PRIO_N + st; if (fsm_hooks[fsm_id][shift].fsm_id) return -EBUSY; if (!fsm_htbl[fsm_id]) { TFW_ERR("gfsm: fsm %d is not registered\n", fsm_id); return -ENOENT; } fsm_hooks[fsm_id][shift].st0 = st0; fsm_hooks[fsm_id][shift].fsm_id = hndl_fsm_id; fsm_hooks_bm[fsm_id][prio] |= st_bit; return prio; }
/** * Parse IP address, create a socket and bind it with the address, * but not yet start listening. */ static int add_listen_sock(TfwAddr *addr, int type) { int r; SsProto *proto; struct socket *s; if (listen_socks_n == ARRAY_SIZE(listen_socks)) { TFW_ERR("maximum number of listen sockets (%d) is reached\n", listen_socks_n); return -ENOBUFS; } r = sock_create_kern(addr->sa.sa_family, SOCK_STREAM, IPPROTO_TCP, &s); if (r) { TFW_ERR("can't create socket (err: %d)\n", r); return r; } inet_sk(s->sk)->freebind = 1; s->sk->sk_reuse = 1; r = s->ops->bind(s, &addr->sa, tfw_addr_sa_len(addr)); if (r) { TFW_ERR_ADDR("can't bind to", addr); sock_release(s); return r; } proto = &protos[listen_socks_n]; proto->type = type; BUG_ON(proto->listener); ss_tcp_set_listen(s, proto); TFW_DBG("created front-end socket: sk=%p\n", s->sk); BUG_ON(listen_socks[listen_socks_n]); listen_socks[listen_socks_n] = s; ++listen_socks_n; return 0; }
/** * Create a listening front-end socket. */ static int __open_listen_socket(SsProto *proto, void *addr) { struct socket *s; unsigned short family = *(unsigned short *)addr; unsigned short sza = family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6); int r; r = sock_create_kern(family, SOCK_STREAM, IPPROTO_TCP, &s); if (r) { TFW_ERR("Can't create front-end listening socket (%d)\n", r); return r; } inet_sk(s->sk)->freebind = 1; s->sk->sk_reuse = 1; r = s->ops->bind(s, (struct sockaddr *)addr, sza); if (r) { TFW_ERR("Can't bind front-end listening socket (%d)\n", r); goto err; } ss_tcp_set_listen(s, proto); TFW_DBG("Created listening socket %p\n", s->sk); /* TODO adjust /proc/sys/net/core/somaxconn */ r = s->ops->listen(s, 1024); if (r) { TFW_ERR("Can't listen on front-end socket (%d)\n", r); goto err; } return r; err: sock_release(s); return r; }
int tfw_sched_http_init(void) { int ret; TFW_DBG("sched_http: init\n"); ret = tfw_cfg_mod_register(&tfw_sched_http_cfg_mod); if (ret) { TFW_ERR("sched_http: can't register configuration module\n"); return ret; } ret = tfw_sched_register(&tfw_sched_http); if (ret) { TFW_ERR("sched_http: can't register scheduler module\n"); tfw_cfg_mod_unregister(&tfw_sched_http_cfg_mod); return ret; } return 0; }
int tfw_open_listen_sockets(void) { struct sockaddr_in6 *addr; int r = -ENOMEM; __be16 ports[DEF_MAX_PORTS]; down_read(&tfw_cfg.mtx); TFW_LOG("Open %u listening sockets\n", tfw_cfg.listen->count); protos = kzalloc(sizeof(void *) * tfw_cfg.listen->count, GFP_KERNEL); if (!protos) goto out; for (listen_socks_n = 0; listen_socks_n < tfw_cfg.listen->count; ++listen_socks_n) { SsProto *proto; if (listen_socks_n > DEF_MAX_PORTS) { TFW_ERR("Too many listening sockets\n"); tfw_close_listen_sockets(); goto out; } /* * TODO If multiprotocol support is required, then here we must * have information for which protocol we're establishing * the new listener. So TfwAddrCfg must be extended with * protocol information (e.g. HTTP enum value). */ proto = protos + listen_socks_n; proto->type = TFW_FSM_HTTP; addr = (struct sockaddr_in6 *)(tfw_cfg.listen->addr + listen_socks_n); r = __open_listen_socket(proto, addr); if (r) { tfw_close_listen_sockets(); goto out; } ports[listen_socks_n] = addr->sin6_port; } tfw_filter_set_inports(ports, listen_socks_n); r = 0; out: up_read(&tfw_cfg.mtx); return r; }
int tfw_classifier_register(TfwClassifier *mod) { write_lock(&tfw_class_lock); if (classifier) { write_unlock(&tfw_class_lock); TFW_ERR("can't register a classifier - there is already one" " registered\n"); return -1; } classifier = mod; write_unlock(&tfw_class_lock); return 0; }
static int start_listen_socks(void) { struct socket *sock; int i, r; FOR_EACH_SOCK(sock, i) { /* TODO adjust /proc/sys/net/core/somaxconn */ TFW_DBG("start listening on socket: sk=%p\n", sock->sk); r = sock->ops->listen(sock, LISTEN_SOCK_BACKLOG_LEN); if (r) { TFW_ERR("can't listen on front-end socket sk=%p (%d)\n", sock->sk, r); return r; } }
TfwServer * tfw_create_server(struct sock *s) { TfwServer *srv = kmem_cache_alloc(srv_cache, GFP_ATOMIC); if (!srv) return NULL; srv->sock = s; if (tfw_sched_add_srv(srv)) { TFW_ERR("Can't add a server to requests scheduler\n"); kmem_cache_free(srv_cache, srv); return NULL; } return srv; }
static ssize_t fop_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { TfwDebugfsIoState *state = file->private_data; if (state->is_input) { TFW_ERR("Can't read this debugfs file: " "it was open()'ed only for writing\n"); return -EPERM; } if (state->len < 0) return state->len; return simple_read_from_buffer(user_buf, count, ppos, state->buf, state->len); }
static const char * alloc_and_copy_literal(const char *src, size_t len) { const char *src_pos, *src_end; char *dst, *dst_pos; bool is_escaped; BUG_ON(!src); dst = kmalloc(len + 1, GFP_KERNEL); if (!dst) { TFW_ERR("can't allocate memory\n"); return NULL; } /* Copy the string eating escaping backslashes. */ /* FIXME: the logic looks like a tiny FSM, * so perhaps it should be included to the TFSM. */ src_end = src + len; src_pos = src; dst_pos = dst; is_escaped = false; while (src_pos < src_end) { if (*src_pos != '\\' || is_escaped) { is_escaped = false; *dst_pos = *src_pos; ++dst_pos; } else if (*src_pos == '\\') { is_escaped = true; } ++src_pos; } *dst_pos = '\0'; return dst; }
int __init tfw_cache_init(void) { int r = 0; if (!tfw_cfg.cache) return 0; db = tdb_open(tfw_cfg.c_path, tfw_cfg.c_size, 0); if (!db) return 1; cache_mgr_thr = kthread_run(tfw_cache_mgr, NULL, "tfw_cache_mgr"); if (IS_ERR(cache_mgr_thr)) { r = PTR_ERR(cache_mgr_thr); TFW_ERR("Can't start cache manager, %d\n", r); goto err_thr; } c_cache = KMEM_CACHE(tfw_cache_work_t, 0); if (!c_cache) goto err_cache; cache_wq = alloc_workqueue("tfw_cache_wq", WQ_MEM_RECLAIM, 0); if (!cache_wq) goto err_wq; return 0; err_wq: kmem_cache_destroy(c_cache); err_cache: kthread_stop(cache_mgr_thr); err_thr: tdb_close(db); return r; }
/** * Handle a "match" entry within "sched_http_rules" section, e.g.: * sched_http_rules { * match group1 uri prefix "/foo"; * match group2 host eq "example.com"; * } * * This callback is invoked for every such "match" entry. * It resolves name of the group, parses the rule and adds the entry to the * tfw_sched_http_rules list. * * Syntax: * +------------------------ a reference to "srv_group"; * | +------------------ HTTP request field * | | +------------ operator (eq, prefix, substr, etc) * | | | +---- argument for the operator (any string) * | | | | * V V V V * match group3 uri prefix "/foo/bar/baz.html" backup=group4 * ^ * | * a backup "srv_group" (optional)----+ * */ static int tfw_sched_http_cfg_handle_match(TfwCfgSpec *cs, TfwCfgEntry *e) { int r; size_t arg_size; TfwSchedHttpRule *rule; tfw_http_match_op_t op; tfw_http_match_fld_t field; tfw_http_match_arg_t type; TfwSrvGroup *main_sg, *backup_sg; const char *in_main_sg, *in_field, *in_op, *in_arg, *in_backup_sg; r = tfw_cfg_check_val_n(e, 4); if (r) return r; in_main_sg = e->vals[0]; in_field = e->vals[1]; in_op = e->vals[2]; in_arg = e->vals[3]; in_backup_sg = tfw_cfg_get_attr(e, "backup", NULL); main_sg = tfw_sg_lookup(in_main_sg); if (!main_sg) { TFW_ERR("sched_http: srv_group is not found: '%s'\n", in_main_sg); return -EINVAL; } if (!in_backup_sg) { backup_sg = NULL; } else { backup_sg = tfw_sg_lookup(in_backup_sg); if (!backup_sg) { TFW_ERR("sched_http: backup srv_group is not found:" " '%s'\n", in_backup_sg); return -EINVAL; } } r = tfw_cfg_map_enum(tfw_sched_http_cfg_field_enum, in_field, &field); if (r) { TFW_ERR("sched_http: invalid HTTP request field: '%s'\n", in_field); return -EINVAL; } r = tfw_cfg_map_enum(tfw_sched_http_cfg_op_enum, in_op, &op); if (r) { TFW_ERR("sched_http: invalid matching operator: '%s'\n", in_op); return -EINVAL; } arg_size = strlen(in_arg) + 1; type = tfw_sched_http_cfg_arg_tbl[field]; rule = tfw_http_match_entry_new(tfw_sched_http_rules, TfwSchedHttpRule, rule, arg_size); if (!rule) { TFW_ERR("sched_http: can't allocate memory for parsed rule\n"); return -ENOMEM; } TFW_DBG("sched_http: parsed rule: match" " '%s'=%p '%s'=%d '%s'=%d '%s'\n", in_main_sg, main_sg, in_field, field, in_op, op, in_arg); if (type == TFW_HTTP_MATCH_A_STR || type == TFW_HTTP_MATCH_A_WILDCARD) { tfw_http_match_rule_init(&rule->rule, field, op, type, in_arg); } else { BUG(); // TODO: parsing not string matching rules } rule->main_sg = main_sg; rule->backup_sg = backup_sg; return 0; }
static unsigned int add_body(TfwFuzzContext *ctx, char **p, char *end, int type) { size_t len = 0, i, j; char *len_str; int err, ret = FUZZ_VALID; i = ctx->i[CONTENT_LENGTH]; len_str = (i < gen_vector[CONTENT_LENGTH].size) ? (content_len[i].flags & FUZZ_MSG_F_INVAL) ? "500" /* Generate content of arbitrary size for invalid * Content-Length value. */ : content_len[i].sval : ctx->content_length; err = kstrtoul(len_str, 10, &len); if (err) { TFW_ERR("error %d on getting content length from \"%s\"" "(%lu)\n", err, len_str, i); return FUZZ_INVALID; } if (!(ctx->fld_flags[TRANSFER_ENCODING] & FUZZ_FLD_F_CHUNKED)) { if (!ctx->is_only_valid && len && !(i % INVALID_BODY_PERIOD)) { len /= 2; ret = FUZZ_INVALID; TFW_DBG("1/2 invalid body %lu\n", len); } add_rand_string(p, end, len, A_BODY); } else { int chunks = ctx->i[BODY_CHUNKS_NUM] + 1; size_t chlen, rem, step; BUG_ON(chunks <= 0); if (len > 0) { chlen = len / chunks; rem = len % chunks; for (j = 0; j < chunks; j++) { char buf[256]; step = chlen; if (rem) { step += rem; rem = 0; } snprintf(buf, sizeof(buf), "%zx", step); add_string(p, end, buf); add_string(p, end, "\r\n"); if (!ctx->is_only_valid && step && !(i % INVALID_BODY_PERIOD)) { step /= 2; ret = FUZZ_INVALID; TFW_DBG("1/2 invalid chunked body %lu," " chunks %d\n", len, chunks); } add_rand_string(p, end, step, A_BODY); add_string(p, end, "\r\n"); } } add_string(p, end, "0\r\n\r\n"); } return ret; }
/** * Work to copy response skbs to database mapped area. * * It's nasty to copy data on CPU, but we can't use DMA for mmaped file * as well as for unaligned memory areas. */ static void tfw_cache_copy_resp(struct work_struct *work) { int i; size_t hlens, tot_len; long n; char *p; TfwCWork *cw = (TfwCWork *)work; TfwCacheEntry *ce = cw->cw_ce; TdbVRec *trec; TfwHttpHdrTbl *htbl; TfwHttpHdr *hdr; BUG_ON(!ce->resp); /* Write HTTP headers. */ htbl = ce->resp->h_tbl; ce->hdr_num = htbl->size; hlens = sizeof(ce->hdr_lens[0]) * ce->hdr_num; tot_len = hlens + ce->resp->msg.len; /* * Try to place the cached response in single memory chunk. * * Number of HTTP headers is limited by TFW_HTTP_HDR_NUM_MAX while TDB * should be able to allocate an empty page if we issued a large * request. So HTTP header lengths must fit the first allocated data * chunk, also there must be some space for headers and message bodies. */ trec = tdb_entry_add(db, (TdbVRec *)ce, tot_len); if (!trec || trec->len <= hlens) { TFW_WARN("Cannot allocate memory to cache HTTP headers." " Probably TDB cache is exhausted.\n"); goto err; } p = (char *)(trec + 1) + hlens; tot_len -= hlens; /* * Set start of headers pointer just after array of * header length. */ ce->hdrs = p; hdr = htbl->tbl; for (i = 0; i < hlens / sizeof(ce->hdr_lens[0]); ++i, ++hdr) { n = tfw_cache_copy_str_compound(&p, &trec, &hdr->field, tot_len); if (n < 0) { TFW_ERR("Cache: cannot copy HTTP header\n"); goto err; } BUG_ON(n > tot_len); tot_len -= n; } /* Write HTTP response body. */ ce->body = p; n = tfw_cache_copy_str_compound(&p, &trec, &ce->resp->body, tot_len); if (n < 0) { TFW_ERR("Cache: cannot copy HTTP body\n"); goto err; } ce->body_len = n; err: /* FIXME all allocated TDB blocks are leaked here. */ kmem_cache_free(c_cache, cw); }