/* * KTCPVS An implementation of the TCP Virtual Server daemon inside * kernel for the LINUX operating system. KTCPVS can be used * to build a moderately scalable and highly available server * based on a cluster of servers, with more flexibility. * * Version: $Id$ * * Authors: Wensong Zhang * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. * */ #include #include #ifdef CONFIG_KMOD #include #endif #include #include #include #include #include #include #include #include #include #include "tcp_vs.h" #define KTCPVS_HTTPRULE_BUF_MAXLEN 4096 static int proc_do_tcpvs_httprule (ctl_table *table, int write, struct file *filp, void *buffer, size_t *lenp); static struct tcp_vs_sysctl_table dummy_vs_http_sysctl = { NULL, {{NET_KTCPVS_SCHED_HTTP, "httprule", NULL, KTCPVS_HTTPRULE_BUF_MAXLEN, 0644, NULL, &proc_do_tcpvs_httprule}, {0}}, {{NET_KTCPVS, NULL, NULL, 0, 0555, dummy_vs_http_sysctl.vs_vars}, {0}}, {{NET_KTCPVS, "ktcpvs", NULL, 0, 0555, dummy_vs_http_sysctl.vs_dir}, {0}}, {{CTL_NET, "net", NULL, 0, 0555, dummy_vs_http_sysctl.ktcpvs_dir}, {0}} }; int tcp_vs_http_sysctl_register(struct tcp_vs *vs) { struct tcp_vs_sysctl_table *t; EnterFunction("tcp_vs_http_sysctl_register"); if (vs == NULL) return -1; t = kmalloc(sizeof(*t), GFP_KERNEL); if (t == NULL) return -1; memcpy(t, &dummy_vs_http_sysctl, sizeof(*t)); t->vs_vars[0].data = vs; t->vs_dir[0].procname = vs->name; t->vs_dir[0].ctl_name = vs->index; t->vs_dir[0].child = t->vs_vars; t->vs_dir[0].de = NULL; t->ktcpvs_dir[0].child = t->vs_dir; t->ktcpvs_dir[0].de = NULL; t->root_dir[0].child = t->ktcpvs_dir; t->root_dir[0].de = NULL; t->sysctl_header = register_sysctl_table(t->root_dir, 0); if (t->sysctl_header == NULL) { kfree(t); return -1; } vs->sched_st = t; LeaveFunction("tcp_vs_http_sysctl_register"); return 0; } int tcp_vs_http_sysctl_unregister(struct tcp_vs *vs) { EnterFunction("tcp_vs_http_sysctl_unregister"); if (vs->sched_st) { struct tcp_vs_sysctl_table *t = vs->sched_st; vs->sched_st = NULL; unregister_sysctl_table(t->sysctl_header); kfree(t); } LeaveFunction("tcp_vs_http_sysctl_unregister"); return 0; } /* struct tcp_vs_http_dest { struct list_head list; struct tcp_vs_dest *dest; }; */ struct tcp_vs_http_rule { struct list_head list; char *pattern; /* struct list_head dest_list; */ struct tcp_vs_dest *dest; }; struct tcp_vs_http_rule *tcp_vs_http_rule_create(struct tcp_vs *vs, char *line) { struct tcp_vs_http_rule *r; __u32 daddr; __u16 dport; char *p; char word[30]; char *q=word; r = kmalloc(sizeof(*r), GFP_KERNEL); if (!r) return NULL; p = tcp_vs_getword(line, q, 30); TCP_VS_DBG("pattern %s\n", q); r->pattern = strdup(q); /* parse addr */ if (!p) goto error; p = tcp_vs_getword(p, q, 30); TCP_VS_DBG("addr %s\n", q); daddr = htonl(simple_strtoul(q, &q, 16)); /* parse port */ if (!p) goto error; p = tcp_vs_getword(p, q, 30); TCP_VS_DBG("port %s\n", q); dport = htons(simple_strtoul(q, &q, 16)); r->dest = tcp_vs_lookup_dest(vs, daddr, dport); if (!r->dest) goto error; atomic_inc(&r->dest->refcnt); list_add(&r->list, &vs->sched_rule); return r; error: kfree(r->pattern); kfree(r); return NULL; } int tcp_vs_http_rule_release(struct tcp_vs_http_rule *r) { #if 0 struct list_head *e; struct tcp_vs_http_dest *d; for (e=&r->dest_list; e->next!=e;) { d = list_entry(l->next, struct tcp_vs_http_dest, list); if (atomic_dec_and_test(&d->dest->refcnt)) kfree(d->dest); list_del(&d->list); kfree(d); } #endif if (atomic_dec_and_test(&r->dest->refcnt)) kfree(r->dest); if (r->pattern) kfree(r->pattern); list_del(&r->list); kfree(r); return 0; } #if 0 static int tcp_vs_http_addrule(struct tcp_vs *vs) { #define MAXLEN 256 char *p; char line[MAXLEN]; EnterFunction("tcp_vs_http_addrule"); p = (char *)vs->sched_data; while ((p=tcp_vs_getline(p, line, MAXLEN))) { tcp_vs_http_rule_create(vs, line); } TCP_VS_DBG("The rule buffer is %s\n", (char *)vs->sched_data); LeaveFunction("tcp_vs_http_addrule"); return 0; } #endif static int tcp_vs_http_delrule(struct tcp_vs *vs) { struct list_head *l; struct tcp_vs_http_rule *r; for (l=&vs->sched_rule; l->next!=l;) { r = list_entry(l->next, struct tcp_vs_http_rule, list); tcp_vs_http_rule_release(r); } return 0; } static int tcp_vs_http_getrule(struct tcp_vs *vs, char *buf, size_t length) { struct list_head *l; struct tcp_vs_http_rule *r; int len=0; list_for_each (l, &vs->sched_rule) { r = list_entry(l, struct tcp_vs_http_rule, list); len += sprintf(buf+len, "pattern %-40s --> dest %08X %X\n", r->pattern, ntohl(r->dest->addr), ntohs(r->dest->port)); if (len > length-80) break; } return 0; } static struct tcp_vs_dest * tcp_vs_http_matchrule(struct tcp_vs *vs, const char *request) { struct list_head *l; struct tcp_vs_http_rule *r; list_for_each (l, &vs->sched_rule) { r = list_entry(l, struct tcp_vs_http_rule, list); if (!strncmp(request, r->pattern, strlen(r->pattern))) /* HIT */ return r->dest; } list_for_each (l, &vs->sched_rule) { r = list_entry(l, struct tcp_vs_http_rule, list); if (!strncmp("default", r->pattern, 7)) return r->dest; } return NULL; } static int tcp_vs_http_init_vs(struct tcp_vs *vs) { EnterFunction("tcp_vs_http_init_vs"); MOD_INC_USE_COUNT; if (!(vs->sched_data=(void *)get_free_page(GFP_KERNEL))) return -1; tcp_vs_http_sysctl_register(vs); LeaveFunction("tcp_vs_http_init_vs"); return 0; } static int tcp_vs_http_done_vs(struct tcp_vs *vs) { EnterFunction("tcp_vs_http_done_vs"); tcp_vs_http_sysctl_unregister(vs); tcp_vs_http_delrule(vs); if (vs->sched_data) free_page((unsigned long)vs->sched_data); MOD_DEC_USE_COUNT; LeaveFunction("tcp_vs_http_done_vs"); return 0; } static int tcp_vs_http_update_vs(struct tcp_vs *vs) { EnterFunction("tcp_vs_http_update_vs"); /* tcp_vs_http_delrule(vs); */ /* tcp_vs_http_addrule(vs); */ LeaveFunction("tcp_vs_http_update_vs"); return 0; } static int proc_do_tcpvs_httprule (ctl_table *table, int write, struct file *filp, void *buffer, size_t *lenp) { struct tcp_vs *vs; int len; char *p, c=0; char *str; EnterFunction("proc_do_tcpvs_httprule"); if (!table->data || !table->maxlen || !*lenp || (filp->f_pos && !write)) { *lenp = 0; return 0; } vs = (struct tcp_vs *) table->data; str = (char *) vs->sched_data; if (write) { len = 0; p = buffer; while (len < *lenp) { if(get_user(c, p++)) return -EFAULT; if (c == 0) /* || c == '\n' */ break; len++; } if (len >= table->maxlen) len = table->maxlen-1; if(copy_from_user(str, buffer,(unsigned long)len)) return -EFAULT; str[len] = 0; filp->f_pos += *lenp; /* process the rule string here */ /* if ((vs->sched_rule).next != &vs->sched_rule) */ /* tcp_vs_http_delrule(vs); */ /* tcp_vs_http_addrule(vs); */ tcp_vs_http_rule_create(vs, str); } else { tcp_vs_http_getrule(vs, str, KTCPVS_HTTPRULE_BUF_MAXLEN); len = strlen(str); if (len > table->maxlen) len = table->maxlen; if (len > *lenp) len = *lenp; if (len) if(copy_to_user(buffer, str, len)) return -EFAULT; if (len < *lenp) { if(put_user('\n', ((char *) buffer) + len)) return -EFAULT; len++; } *lenp = len; filp->f_pos += len; } LeaveFunction("proc_do_tcpvs_httprule"); return 0; } /* * Parse HTTP header */ int parse_http_header(char *buffer, size_t buflen, char* request, size_t reqlen) { char *eob,*eol,*tmp; EnterFunction("parse_http_header"); eob = buffer + buflen; /* parse only the first header if multiple headers are present */ tmp = strstr(buffer, "\r\n\r\n"); if (tmp!=NULL) eob = tmp; while (buffer < eob) { if (isspace(buffer[0])) { buffer++; continue; } eol=strchr(buffer, '\n'); if (eol==NULL) eol=eob; if (eol-buffer<4) { buffer++; continue; } if (!strncmp("GET ", buffer, 4)) { int len; buffer+=4; tmp=strchr(buffer, ' '); if (tmp == NULL) tmp=eol-1; if (tmp > eob) continue; len = min(reqlen-1,tmp-buffer); strncpy(request, buffer, len); request[len] = 0; buffer=eol+1; LeaveFunction("parse_http_header"); return 0; } /* next line */ buffer = eol+1; } LeaveFunction("parse_http_header"); return -1; } /* * HTTP content-based scheduling * Parse the http request, select a server according to the * request, and create a socket the server finally. */ static struct socket *tcp_vs_http_schedule(struct tcp_vs_conn *conn, struct tcp_vs *vs) { #define REQLEN 1024 struct tcp_vs_dest *dest; struct socket *csock, *dsock; int len; char *buffer; size_t buflen; static char request[REQLEN]; EnterFunction("tcp_vs_http_schedule"); buffer = conn->buffer; buflen = conn->buflen; csock = conn->csock; /* Do we have data ? */ while (skb_queue_empty(&(csock->sk->receive_queue))) { interruptible_sleep_on_timeout(&csock->wait,HZ); } len = tcp_vs_recvbuffer(csock, buffer, buflen); if (len < 0){ TCP_VS_ERR("error reading request from client\n"); return NULL; } if (parse_http_header(buffer, len, request, REQLEN)) { TCP_VS_ERR("cannot parse http request\n"); return NULL; } TCP_VS_DBG("request: GET %s\n", request); /* Head.RemoteHost.s_addr = sock->sk->daddr; */ dest = tcp_vs_http_matchrule(vs, request); if (!dest) { TCP_VS_ERR("no suitable destination available\n"); return NULL; } TCP_VS_DBG ("HTTP: server %d.%d.%d.%d:%d " "conns %d refcnt %d weight %d\n", NIPQUAD(dest->addr), ntohs(dest->port), atomic_read(&dest->conns), atomic_read (&dest->refcnt), dest->weight); atomic_inc(&dest->conns); conn->dest = dest; dsock = tcp_vs_connect2dest(dest); if (!dsock) { TCP_VS_ERR("The destination is not available\n"); return NULL; } if (tcp_vs_sendbuffer(dsock, buffer, len)!=len) { TCP_VS_ERR("Error sending buffer\n"); } LeaveFunction("tcp_vs_http_schedule"); return dsock; } static struct tcp_vs_scheduler tcp_vs_http_scheduler = { {0}, /* n_list */ "http", /* name */ ATOMIC_INIT (0), /* refcnt */ tcp_vs_http_init_vs, /* initializer */ tcp_vs_http_done_vs, /* done */ tcp_vs_http_update_vs, /* update */ NULL, /* control */ tcp_vs_http_schedule, /* select a server by http CBS */ }; #ifdef MODULE int tcp_vs_http_init (void) #else int __init tcp_vs_http_init (void) #endif { TCP_VS_INFO ("Initializing HTTP scheduling\n"); INIT_LIST_HEAD (&tcp_vs_http_scheduler.n_list); return register_tcp_vs_scheduler (&tcp_vs_http_scheduler); } #ifdef MODULE EXPORT_NO_SYMBOLS; int init_module (void) { if (tcp_vs_http_init() != 0) return -EIO; TCP_VS_INFO ("HTTP scheduling module loaded.\n"); return 0; } void cleanup_module (void) { /* module cleanup by 'release_module' */ if (unregister_tcp_vs_scheduler (&tcp_vs_http_scheduler) != 0) TCP_VS_INFO ("cannot remove HTTP scheduling module\n"); else TCP_VS_INFO ("HTTP scheduling module unloaded.\n"); } #endif /* MODULE */