Hi,

attached is a patch which adds a new option to HAProxy called 'inject'
for the mode 'tcp'. In the current version of this patch you can only
add data at the beginning of the session. I think this is very useful -
at least for me it is. :))

The configuration syntax is the following (I will update the documentation
with a more recent patch):

  option    inject req 1 forwardfor
  option    inject req 2 data "EHLO localhost.localdomain"
  option    inject req 3 data "GET / HTTP/1.1"
  option    inject rsp 1 data "500 Service Unavailable"

There are still some things open which I want to add later. If someone
has ideas about things which should be implemented, feel free to reply. :)

  - remove code duplication.
    * X-Forward-For, X-Original-Dst in src/cfgparse.c and src/proto_tcp.c,
      maybe src/proto_http.c

  - add support for inject data on response.
    * currently it works only on request.

  - add session injection based on request or response.
    * currently injection only works at the beginning of request and first
      response.

  - modify session information based on regular expression for request
    or response.
    * currently this isn't possible.

This will make it possible to inject data at every time of the session
without knowing the underlying protocol. The attached patch is very basic,
but I send it in that early stage, to get some feedback if it is a feature
for HAProxy or not. Comments are welcome. :) Also it should be possible to
transfer some very minimalistic html pages like:

  option    inject rsp 1 data "<html>"
  option    inject rsp 2 data "<head>"
  option    inject rsp 3 data "<title>Error</title>"
  option    inject rsp 4 data "</head>"
  option    inject rsp 5 data "<body>Access denied</body>"
  option    inject rsp 6 data "</html>

For example: With a later version of this patch it is possible to create a
service named 'smtp-mail-me' and at connect via telnet it can do the
following:

  >> EHLO localhost.localdomain
  << 250-mydomain.de Hello localhost.localdomain [127.0.0.1]
  << 250-SIZE 52428800
  << 250-PIPELINING
  >> AUTH PLAIN XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX==
  << 235 Authentication succeeded
  >> MAIL FROM:<mbroe...@plusserver.de>
  << 250 Ok
  >> RCPT TO:<mbroe...@plusserver.de>
  << 250 Accepted
  >> DATA
  >> ...
  >> .
  << 250 Ok id=1LudBO-0000SQ-Qa
  >> QUIT

Every other text-based protocol can be handled in such case too. I want to
mention that I don't know exactly how the timeout handling is done in
HAProxy. The source is very anoying to me there, some hints would be nice. :)

--Maik
diff -Nur haproxy-1.3.17/include/proto/proto_tcp.h 
haproxy-1.3.17-tcp-inject/include/proto/proto_tcp.h
--- haproxy-1.3.17/include/proto/proto_tcp.h    2009-03-29 15:26:57.000000000 
+0200
+++ haproxy-1.3.17-tcp-inject/include/proto/proto_tcp.h 2009-04-17 
02:37:44.000000000 +0200
@@ -33,6 +33,7 @@
 void tcpv6_add_listener(struct listener *listener);
 int tcp_bind_listener(struct listener *listener, char *errmsg, int errlen);
 int tcp_inspect_request(struct session *s, struct buffer *req);
+int tcp_inject_request(struct session *s, struct buffer *req);
 
 #endif /* _PROTO_PROTO_TCP_H */
 
diff -Nur haproxy-1.3.17/include/types/buffers.h 
haproxy-1.3.17-tcp-inject/include/types/buffers.h
--- haproxy-1.3.17/include/types/buffers.h      2009-03-29 15:26:57.000000000 
+0200
+++ haproxy-1.3.17-tcp-inject/include/types/buffers.h   2009-04-17 
02:37:44.000000000 +0200
@@ -111,6 +111,7 @@
 #define AN_REQ_HTTP_TARPIT      0x00000008  /* wait for end of HTTP tarpit */
 #define AN_RTR_HTTP_HDR         0x00000010  /* inspect HTTP response headers */
 #define AN_REQ_UNIX_STATS       0x00000020  /* process unix stats socket 
request */
+#define AN_REQ_INJECT           0x00000040  /* inject header lines into tcp 
session */
 
 /* describes a chunk of string */
 struct chunk {
diff -Nur haproxy-1.3.17/src/cfgparse.c haproxy-1.3.17-tcp-inject/src/cfgparse.c
--- haproxy-1.3.17/src/cfgparse.c       2009-03-29 15:26:57.000000000 +0200
+++ haproxy-1.3.17-tcp-inject/src/cfgparse.c    2009-04-17 04:12:18.000000000 
+0200
@@ -1604,6 +1604,78 @@
                                }
                        }
                }
+               else if (!strcmp(args[1], "inject")) {
+                       int cur_arg;
+
+                       /* inject x-forwarded-for field at the beginning of tcp 
session. */
+                       curproxy->options |= PR_O_FWDFOR;
+
+                       free(curproxy->fwdfor_hdr_name);
+                       curproxy->fwdfor_hdr_name = strdup(DEF_XFORWARDFOR_HDR);
+                       curproxy->fwdfor_hdr_len  = strlen(DEF_XFORWARDFOR_HDR);
+
+                       /* next argument must be "req" or "rep" */
+                       if (!strcmp(args[2], "req")) {
+                               curproxy->listen->analysers |= AN_REQ_INJECT;
+                       } else if (!strcmp(args[2], "rsp")) {
+// TODO: implement it
+                               Alert("parsing [%s:%d] : '%s %s %s' not yet 
implemented.\n",
+                                     file, linenum, args[0], args[1], args[2]);
+                               return -1;
+                       } else {
+                               Alert("parsing [%s:%d] : '%s %s' expects 'req' 
or 'rsp' as argument.\n",
+                                     file, linenum, args[0], args[1]);
+                               return -1;
+                       }
+
+                       /* next argument is the inject order. */
+// TODO: implement it  atol(args[3]);
+
+                       /* check what data should be injected. */
+                       if (!strcmp(args[4], "forwardfor")) {
+
+                               /* loop to go through arguments - start at 2, 
since 0+1 = "option" "inject" */
+                               cur_arg = 5;
+                               while (*(args[cur_arg])) {
+                                       if (!strcmp(args[cur_arg], "except")) {
+                                               /* suboption except - needs 
additional argument for it */
+                                               if (!*(args[cur_arg+1]) || 
!str2net(args[cur_arg+1], &curproxy->except_net, &curproxy->except_mask)) {
+                                                       Alert("parsing [%s:%d] 
: '%s %s %s %s %s %s' expects <address>[/mask] as argument.\n",
+                                                             file, linenum, 
args[0], args[1], args[2], args[3], args[4], args[cur_arg]);
+                                                       return -1;
+                                               }
+                                               /* flush useless bits */
+                                               curproxy->except_net.s_addr &= 
curproxy->except_mask.s_addr;
+                                               cur_arg += 2;
+                                       } else if (!strcmp(args[cur_arg], 
"header")) {
+                                               /* suboption header - needs 
additional argument for it */
+                                               if (*(args[cur_arg+1]) == 0) {
+                                                       Alert("parsing [%s:%d] 
: '%s %s %s %s %s %s' expects <header_name> as argument.\n",
+                                                             file, linenum, 
args[0], args[1], args[2], args[3], args[4], args[cur_arg]);
+                                                       return -1;
+                                               }
+                                               free(curproxy->fwdfor_hdr_name);
+                                               curproxy->fwdfor_hdr_name = 
strdup(args[cur_arg+1]);
+                                               curproxy->fwdfor_hdr_len  = 
strlen(curproxy->fwdfor_hdr_name);
+                                               cur_arg += 2;
+                                       } else {
+                                               /* unknown suboption - catchall 
*/
+                                               Alert("parsing [%s:%d] : '%s 
%s' only supports optional values: 'except' and 'header'.\n",
+                                                     file, linenum, args[0], 
args[1]);
+                                               return -1;
+                                       }
+                               } /* end while loop */
+                       } else if (!strcmp(args[4], "data")) {
+// TODO: implement it
+                               Alert("parsing [%s:%d] : '%s %s %s %s %s' not 
yet implemented.\n",
+                                     file, linenum, args[0], args[1], args[2], 
args[3], args[4]);
+                               return -1;
+                       } else {
+                               Alert("parsing [%s:%d] : '%s %s %s %s' expects 
'forwardfor' or 'data' as argument.\n",
+                                     file, linenum, args[0], args[1], args[2], 
args[3]);
+                               return -1;
+                       }
+               }
                else if (!strcmp(args[1], "forwardfor")) {
                        int cur_arg;
 
diff -Nur haproxy-1.3.17/src/proto_tcp.c 
haproxy-1.3.17-tcp-inject/src/proto_tcp.c
--- haproxy-1.3.17/src/proto_tcp.c      2009-03-29 15:26:57.000000000 +0200
+++ haproxy-1.3.17-tcp-inject/src/proto_tcp.c   2009-04-17 02:37:44.000000000 
+0200
@@ -40,6 +40,7 @@
 #include <proto/acl.h>
 #include <proto/backend.h>
 #include <proto/buffers.h>
+#include <proto/client.h>
 #include <proto/fd.h>
 #include <proto/protocols.h>
 #include <proto/proto_tcp.h>
@@ -428,6 +429,136 @@
        return 1;
 }
 
+/* Add some data in the current server TCP session. This makes a backend
+ * behind us able to parse them and make decisions based on it.
+ */
+int tcp_inject_request(struct session *s, struct buffer *req)
+{
+
+       /* position of buffer. */
+       int buf_pos = 0;
+
+       DPRINTF(stderr,"[%u] %s: session=%p b=%p, exp(r,w)=%u,%u bf=%08x bl=%d 
analysers=%02x\n",
+               now_ms, __FUNCTION__,
+               s,
+               req,
+               req->rex, req->wex,
+               req->flags,
+               req->l,
+               req->analysers);
+
+       /* FIXME: I have no real idea how the timeouts are handled here,
+        * so is it required to add some handler here?
+        */
+
+       /* 1: add X-Forward-For if either the frontend or the backend asks for 
it. (IPv4) */
+       if (((s->fe->options | s->be->options) & PR_O_FWDFOR) != 0 && 
s->cli_addr.ss_family == AF_INET) {
+
+               /* add an X-Forward-For header unless the source ip is in the 
'except' network range. */
+               if (((s->fe->except_mask.s_addr == 0) || (((struct sockaddr_in 
*)&s->cli_addr)->sin_addr.s_addr & s->fe->except_mask.s_addr) != 
s->fe->except_net.s_addr) &&
+                   ((s->be->except_mask.s_addr == 0) || (((struct sockaddr_in 
*)&s->cli_addr)->sin_addr.s_addr & s->be->except_mask.s_addr) != 
s->be->except_net.s_addr)) {
+
+                       /* variables to build the header entry. */
+                       int len;
+                       unsigned char *pn;
+                       pn = (unsigned char *)&((struct sockaddr_in 
*)&s->cli_addr)->sin_addr;
+
+                       /* Note: we rely on the backend to get the header name 
to be used for
+                        * X-Forward-For, because the header is really meant 
for the backends.
+                        * However, if the backend did not specify any option, 
we have to rely
+                        * on the frontend's header name.
+                        */
+                       if (s->be->fwdfor_hdr_len) {
+                               len = s->be->fwdfor_hdr_len;
+                               memcpy(trash, s->be->fwdfor_hdr_name, len);
+                       } else {
+                               len = s->fe->fwdfor_hdr_len;
+                               memcpy(trash, s->fe->fwdfor_hdr_name, len);
+                       }
+
+                       /* fetch length of complete header. */
+                       len += sprintf(trash + len, ": %d.%d.%d.%d", pn[0], 
pn[1], pn[2], pn[3]);
+
+                       /* copy the header into connection. */
+                       if ((buf_pos = buffer_insert_line2(req, req->data, 
trash, len)) < 0) {
+
+                               /* marks the buffer as "shutdown" ASAP in both 
directions. */
+                               buffer_abort(req);
+                               buffer_abort(s->rep);
+                               req->analysers = 0;
+                               s->fe->failed_req++;
+
+                               if ((s->flags & SN_ERR_MASK) == 0) {
+                                       s->flags |= SN_ERR_PRXCOND;
+                               }
+
+                               if ((s->flags & SN_FINST_MASK) == 0) {
+                                       s->flags |= SN_FINST_R;
+                               }
+
+                               /* error occured. */
+                               return 0;
+                       }
+               }
+       }
+
+       /* IPv6. */
+       else if (((s->fe->options | s->be->options) & PR_O_FWDFOR) != 0 && 
s->cli_addr.ss_family == AF_INET6) {
+
+               /* FIXME: for the sake of completeness, we should also support
+                * 'except' here, although it is mostly useless in this case.
+                */
+
+               /* variables to build the header entry. */
+               int len;
+               char pn[INET6_ADDRSTRLEN];
+               inet_ntop(AF_INET6, (const void *)&((struct sockaddr_in6 
*)(&s->cli_addr))->sin6_addr, pn, sizeof(pn));
+
+               /* Note: we rely on the backend to get the header name to be 
used for
+                * X-Forward-For, because the header is really meant for the 
backends.
+                * However, if the backend did not specify any option, we have 
to rely
+                * on the frontend's header name.
+                */
+               if (s->be->fwdfor_hdr_len) {
+                       len = s->be->fwdfor_hdr_len;
+                       memcpy(trash, s->be->fwdfor_hdr_name, len);
+               } else {
+                       len = s->fe->fwdfor_hdr_len;
+                       memcpy(trash, s->fe->fwdfor_hdr_name, len);
+               }
+
+               /* fetch length of complete header. */
+               len += sprintf(trash + len, ": %s", pn);
+
+               /* copy the header into connection. */
+               if ((buf_pos = buffer_insert_line2(req, req->data, trash, len)) 
< 0) {
+
+                       /* marks the buffer as "shutdown" ASAP in both 
directions. */
+                       buffer_abort(req);
+                       buffer_abort(s->rep);
+                       req->analysers = 0;
+                       s->fe->failed_req++;
+
+                       if ((s->flags & SN_ERR_MASK) == 0) {
+                               s->flags |= SN_ERR_PRXCOND;
+                       }
+
+                       if ((s->flags & SN_FINST_MASK) == 0) {
+                               s->flags |= SN_FINST_R;
+                       }
+
+                       /* error occured. */
+                       return 0;
+               }
+       }
+
+       /* if we get there, it means we have no rule which matches, or
+        * we have an explicit accept, so we apply the default accept.
+        */
+       req->analysers &= ~AN_REQ_INJECT;
+       req->analyse_exp = TICK_ETERNITY;
+       return 1;
+}
 
 /* This function should be called to parse a line starting with the 
"tcp-request"
  * keyword.
diff -Nur haproxy-1.3.17/src/session.c haproxy-1.3.17-tcp-inject/src/session.c
--- haproxy-1.3.17/src/session.c        2009-03-29 15:26:57.000000000 +0200
+++ haproxy-1.3.17-tcp-inject/src/session.c     2009-04-17 02:37:44.000000000 
+0200
@@ -755,8 +755,12 @@
                                        if (!http_process_request_body(s, 
s->req))
                                                break;
 
+                               if (s->req->analysers & AN_REQ_INJECT)
+                                       if (!tcp_inject_request(s, s->req))
+                                               break;
+
                                /* Just make sure that nobody set a wrong flag 
causing an endless loop */
-                               s->req->analysers &= AN_REQ_INSPECT | 
AN_REQ_HTTP_HDR | AN_REQ_HTTP_TARPIT | AN_REQ_HTTP_BODY;
+                               s->req->analysers &= AN_REQ_INSPECT | 
AN_REQ_HTTP_HDR | AN_REQ_HTTP_TARPIT | AN_REQ_HTTP_BODY | AN_REQ_INJECT;
 
                                /* we don't want to loop anyway */
                                break;

Reply via email to