diff --git a/Makefile b/Makefile index feec783d2..384bc38cf 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ # You should use it this way : # make TARGET=os CPU=cpu -VERSION := 1.2.14 +VERSION := 1.3.0 # Select target OS. TARGET must match a system for which COPTS and LIBS are # correctly defined below. @@ -91,17 +91,15 @@ ADDLIB = # set some defines when needed. # Known ones are -DENABLE_POLL, -DENABLE_EPOLL, and -DUSE_MY_EPOLL -# - use -DSTATTIME=0 to disable statistics, else specify an interval in -# milliseconds. # - use -DTPROXY to compile with transparent proxy support. -DEFINE = -DSTATTIME=0 -DTPROXY +DEFINE = -DTPROXY # global options TARGET_OPTS=$(COPTS.$(TARGET)) REGEX_OPTS=$(COPTS.$(REGEX)) CPU_OPTS=$(COPTS.$(CPU)) -COPTS=-I. $(ADDINC) $(CPU_OPTS) $(TARGET_OPTS) $(REGEX_OPTS) $(SMALL_OPTS) $(DEFINE) +COPTS=-Iinclude $(ADDINC) $(CPU_OPTS) $(TARGET_OPTS) $(REGEX_OPTS) $(SMALL_OPTS) $(DEFINE) LIBS=$(LIBS.$(TARGET)) $(LIBS.$(REGEX)) $(ADDLIB) CFLAGS = -Wall $(COPTS) $(DEBUG) @@ -109,18 +107,31 @@ LDFLAGS = -g all: haproxy -haproxy: src/list.o src/chtbl.o src/hashpjw.o haproxy.o src/base64.o src/uri_auth.o +OBJS = src/haproxy.o src/list.o src/chtbl.o src/hashpjw.o src/base64.o \ + src/uri_auth.o src/standard.o src/buffers.o src/log.o src/task.o \ + src/time.o src/fd.o src/regex.o src/cfgparse.o src/server.o \ + src/checks.o src/queue.o src/capture.o src/client.o src/proxy.o \ + src/proto_http.o src/stream_sock.o src/appsession.o src/backend.o \ + src/session.o + +haproxy: $(OBJS) $(LD) $(LDFLAGS) -o $@ $^ $(LIBS) +objsize: haproxy + @objdump -t $^|grep ' g '|grep -F '.text'|awk '{print $$5 FS $$6}'|sort + %.o: %.c $(CC) $(CFLAGS) -c -o $@ $< clean: - rm -f {.,src}/*.[oas] {.,src,include,doc}/*{~,.rej} core haproxy test nohup.out gmon.out - rm -f haproxy-$(VERSION).tar.gz haproxy-$(VERSION) + rm -f {.,src}/*.[oas] {.,src,include/*,doc}/*{~,.rej} core haproxy test + rm -f haproxy-$(VERSION).tar.gz haproxy-$(VERSION) nohup.out gmon.out tar: clean ln -s . haproxy-$(VERSION) - tar --exclude=haproxy-$(VERSION)/.git --exclude=haproxy-$(VERSION)/haproxy-$(VERSION) -cf - haproxy-$(VERSION)/* | gzip -c9 >haproxy-$(VERSION).tar.gz + tar --exclude=haproxy-$(VERSION)/.git \ + --exclude=haproxy-$(VERSION)/haproxy-$(VERSION) \ + --exclude=haproxy-$(VERSION)/haproxy-$(VERSION).tar.gz \ + -cf - haproxy-$(VERSION)/* | gzip -c9 >haproxy-$(VERSION).tar.gz rm -f haproxy-$(VERSION) diff --git a/Makefile.bsd b/Makefile.bsd index 3dbb1189d..cf190ef1f 100644 --- a/Makefile.bsd +++ b/Makefile.bsd @@ -62,10 +62,8 @@ ADDLIB = # set some defines when needed. # Known ones are -DENABLE_POLL, -DENABLE_EPOLL, and -DUSE_MY_EPOLL -# - use -DSTATTIME=0 to disable statistics, else specify an interval in -# milliseconds. # - use -DTPROXY to compile with transparent proxy support. -DEFINE = -DSTATTIME=0 -DTPROXY +DEFINE = -DTPROXY # global options TARGET_OPTS=$(COPTS.$(TARGET)) diff --git a/ROADMAP b/ROADMAP index c960c9b45..e529eb1fc 100644 --- a/ROADMAP +++ b/ROADMAP @@ -36,6 +36,46 @@ max(srv->maxconn * px->nbsess / px->maxconn, srv->minconn) 1.3 : + - remove unused STATTIME + + - reference all the include files that must be created, possibly under subdirs : + + - acl.h => more general ACL work + - appcook.h => appsession-related cookies + - backend.h => back-end part of the PR_O_* + backend definitions + - buffers.h => buffer management relying on memory.h + - capture.h => header and cookie capture + - cfgparse.h => configuration parser + - checks.h => health checks + - clireq.h => the client side "request" part of the current sessions. + - compat.h => compatibility with other OSes (TCP_NODELAY, ...) + - config.h => config parameters, renamed CONFIG_HAP_*, includes defaults.h + - controls.h => SN_CACHEABLE, ... + - cookies.h => definitions related to cookie management + SN_SCK_* + - defaults.h => many default values, might disappear soon after cleanup + - frontend.h => front-end part of the PR_O_* + client definitions + listeners + - global.h => shared global variables + - http.h => HTTP state definitions and transitions + - httperr.{hc} => HTTP return codes + - libtask.h => task scheduler + - libtime.h => time-related definitions + - loadbal.h => load balancing algorithms + - log.h => log definitions + - memory.h => pools + - polling.h => definitions of select(), poll(), INTBITS, ... + - queue.h => queue management + - regex.h => filtering + - servers.h => servers definitions (SRV_*, states, ...) + - fd.h => FD_ST* (add FD_DGRAM), RES_*, socket states, etc... + - srvreq.h => the server side "request" part of the current sessions. + - standard.h => general purpose macros and defines (eg: MIN/MAX, ...) + - startup.h => MODE_* + - tuning.h => platform-specific tuning parameters + + + - clarify licence by adding a 'MODULE_LICENCE("GPL")' or something equivalent. + + - handle half-closed connections better (cli/srv would not distinguish DATA/SHUTR/SHUTW, it would be a session flag which would tell shutr/shutw). Check how it got changed in httpterm. diff --git a/TODO b/TODO index 5b656290d..bd59aacdc 100644 --- a/TODO +++ b/TODO @@ -160,3 +160,11 @@ Todo for 1.2 - embedded error pages loaded in memory at startup time (eg: for expired time in connection queue) + +TODO for 1.3 +============ + - check all copyrights + - put haproxy/config.h in every include file. This one will automatically + include version.h, defaults.h and compat.h. + - fix Makefile.bsd + - separate inline functions to put them in files covered by GPL diff --git a/doc/buffers.fig b/doc/buffers.fig new file mode 100644 index 000000000..be6fac3cb --- /dev/null +++ b/doc/buffers.fig @@ -0,0 +1,1052 @@ +#FIG 3.2 Produced by xfig version 3.2.5-alpha5 +Portrait +Center +Metric +A4 +100.00 +Single +-2 +1200 2 +6 630 900 1620 1395 +5 1 0 1 0 4 51 -1 20 0.000 0 1 0 0 900.000 1125.000 900 900 675 1125 900 1350 +6 900 900 1620 1350 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 900 900 900 1350 +4 0 0 50 -1 16 8 0.0000 4 120 570 1035 1245 descriptor\001 +4 0 0 50 -1 16 8 0.0000 4 105 165 1035 1080 file\001 +-6 +-6 +6 630 1530 1620 2070 +6 630 1530 1170 2070 +5 1 0 1 0 11 51 -1 20 0.000 0 0 0 0 900.000 1800.000 900 1575 1125 1800 900 2025 +5 1 0 1 0 4 51 -1 20 0.000 0 1 0 0 900.000 1800.000 900 1575 675 1800 900 2025 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 900 1575 900 2025 +-6 +4 0 0 50 -1 16 8 0.0000 4 105 360 1260 1710 stream\001 +4 0 0 50 -1 16 8 0.0000 4 105 330 1260 1890 driver\001 +-6 +6 675 2340 2070 2610 +6 675 2340 1575 2610 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 1530 2340 1530 2610 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 1485 2340 1485 2610 +2 2 0 1 0 6 51 -1 20 0.000 1 0 -1 0 0 5 + 675 2340 1575 2340 1575 2610 675 2610 675 2340 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 720 2340 720 2610 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 765 2340 765 2610 +-6 +4 0 0 50 -1 16 8 0.0000 4 105 330 1710 2475 buffer\001 +-6 +6 2340 2205 3420 2745 +6 2340 2205 2610 2745 +5 1 0 1 0 14 51 -1 20 0.000 0 0 0 0 2475.000 2475.000 2610 2655 2475 2700 2340 2655 +5 1 0 1 0 14 51 -1 20 0.000 0 1 0 0 2475.000 2475.000 2610 2295 2475 2250 2340 2295 +2 2 0 1 14 14 52 -1 20 0.000 2 0 -1 0 0 5 + 2340 2295 2610 2295 2610 2655 2340 2655 2340 2295 +-6 +4 0 0 50 -1 16 8 0.0000 4 105 645 2745 2430 schedulable\001 +4 0 0 50 -1 16 8 0.0000 4 105 225 2745 2595 task\001 +-6 +6 4455 900 7200 2070 +6 4455 900 4725 1395 +5 1 0 1 0 11 51 -1 20 0.000 0 0 0 0 4455.000 1125.000 4455 900 4680 1125 4455 1350 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 4455 900 4455 1350 +-6 +6 5130 1170 5715 2070 +4 0 0 50 -1 16 8 0.0000 4 105 585 5130 1305 functions :\001 +4 0 0 50 -1 16 6 0.0000 4 75 315 5130 1440 -queue\001 +4 0 0 50 -1 16 6 0.0000 4 90 525 5130 1590 -shutdown\001 +4 0 0 50 -1 16 6 0.0000 4 90 300 5130 1740 -flush\001 +4 0 0 50 -1 16 6 0.0000 4 90 285 5130 1890 -send\001 +4 0 0 50 -1 16 6 0.0000 4 60 270 5130 2040 -recv\001 +-6 +6 6030 900 7200 2025 +4 0 0 50 -1 16 8 0.0000 4 105 555 6030 1035 callbacks :\001 +4 0 0 50 -1 16 6 0.0000 4 105 735 6030 1170 -read_complete\001 +4 0 0 50 -1 16 6 0.0000 4 105 780 6030 1320 -write_complete\001 +4 0 0 50 -1 16 6 0.0000 4 105 450 6030 1470 -wake_up\001 +4 0 0 50 -1 16 6 0.0000 4 105 1110 6030 1755 -context (for sessions)\001 +4 0 0 50 -1 16 6 0.0000 4 105 810 6030 1890 -source (=buffer)\001 +4 0 0 50 -1 16 6 0.0000 4 90 525 6030 2025 -condition\001 +4 0 0 50 -1 16 8 0.0000 4 120 1140 6030 1620 args for *_complete :\001 +-6 +2 1 0 1 0 -1 50 -1 -1 0.000 1 0 -1 1 0 3 + 0 0 1.00 30.00 45.00 + 4635 990 4905 990 5985 990 +2 1 0 1 0 -1 50 -1 -1 0.000 1 0 -1 0 1 3 + 0 0 1.00 30.00 45.00 + 4635 1260 4905 1260 5085 1260 +-6 +6 2205 1530 3420 2070 +6 2205 1530 2745 2070 +5 1 0 1 0 11 51 -1 20 0.000 0 1 0 0 2475.000 1800.000 2475 1575 2250 1800 2475 2025 +5 1 0 1 0 11 51 -1 20 0.000 0 0 0 0 2475.000 1800.000 2475 1575 2700 1800 2475 2025 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 2475 1575 2475 2025 +-6 +4 0 0 50 -1 16 8 0.0000 4 105 360 2835 1755 stream\001 +4 0 0 50 -1 16 8 0.0000 4 90 555 2835 1935 processor\001 +-6 +6 3825 2205 4905 2745 +6 3825 2205 4365 2745 +5 1 0 1 0 11 51 -1 20 0.000 0 0 0 0 4095.000 2475.000 4230 2295 4320 2475 4230 2655 +5 1 0 1 0 11 51 -1 20 0.000 0 1 0 0 4095.000 2475.000 3960 2295 3870 2475 3960 2655 +5 1 0 1 0 14 51 -1 20 0.000 0 0 0 0 4095.000 2475.000 4230 2655 4095 2700 3960 2655 +5 1 0 1 0 14 51 -1 20 0.000 0 1 0 0 4095.000 2475.000 4230 2295 4095 2250 3960 2295 +2 2 0 1 14 14 52 -1 20 0.000 2 0 -1 0 0 5 + 3960 2295 4230 2295 4230 2655 3960 2655 3960 2295 +-6 +4 0 0 50 -1 16 8 0.0000 4 105 240 4455 2430 flow\001 +4 0 0 50 -1 16 8 0.0000 4 120 450 4455 2610 analyzer\001 +-6 +6 2250 900 3960 1395 +6 2250 900 2520 1395 +5 1 0 1 0 11 51 -1 20 0.000 0 0 0 0 2250.000 1125.000 2250 900 2475 1125 2250 1350 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 2250 900 2250 1350 +-6 +4 0 0 50 -1 16 8 0.0000 4 105 1320 2610 1245 callbacks and functions.\001 +4 0 0 50 -1 16 8 0.0000 4 105 1335 2610 1080 stream interface made of\001 +-6 +6 7740 900 8775 3465 +6 7740 900 8640 1170 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 8595 900 8595 1170 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 8550 900 8550 1170 +2 2 0 1 0 6 51 -1 20 0.000 1 0 -1 0 0 5 + 7740 900 8640 900 8640 1170 7740 1170 7740 900 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 7785 900 7785 1170 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 7830 900 7830 1170 +4 1 0 50 -1 16 6 0.0000 4 105 420 8190 1080 http_req\001 +-6 +4 0 0 50 -1 16 8 0.0000 4 105 495 7740 1395 variables\001 +4 0 0 50 -1 16 6 0.0000 4 105 300 7740 1485 -flags\001 +4 0 0 50 -1 16 6 0.0000 4 105 720 7740 1755 -producer_task\001 +4 0 0 50 -1 16 6 0.0000 4 105 735 7740 1845 -consumer_task\001 +4 0 0 50 -1 16 6 0.0000 4 105 780 7740 1665 -write_complete\001 +4 0 0 50 -1 16 6 0.0000 4 105 735 7740 1575 -read_complete\001 +4 0 0 50 -1 16 8 0.0000 4 120 780 7740 2160 internal flags :\001 +4 0 0 50 -1 16 6 0.0000 4 90 960 7740 2295 -SHUTR_PENDING\001 +4 0 0 50 -1 16 6 0.0000 4 90 780 7740 2385 -SHUTR_DONE\001 +4 0 0 50 -1 16 6 0.0000 4 90 975 7740 2475 -SHUTW_PENDING\001 +4 0 0 50 -1 16 6 0.0000 4 90 795 7740 2565 -SHUTW_DONE\001 +4 0 0 50 -1 16 6 0.0000 4 75 450 7740 2655 -HOLDW\001 +4 0 0 50 -1 16 6 0.0000 4 90 585 7740 2745 -READ_EN\001 +4 0 0 50 -1 16 6 0.0000 4 90 600 7740 2835 -WRITE_EN\001 +4 0 0 50 -1 16 6 0.0000 4 105 945 7740 1935 -flow_analyzer_task\001 +4 0 0 50 -1 16 8 0.0000 4 105 540 7740 3015 interface :\001 +4 0 0 50 -1 16 6 0.0000 4 90 780 7740 3150 -1 stream reader\001 +4 0 0 50 -1 16 6 0.0000 4 90 780 7740 3285 -1 stream writer\001 +4 0 0 50 -1 16 6 0.0000 4 105 1020 7740 3420 -0..n stream analyzers\001 +-6 +6 3555 3645 5895 6075 +6 3555 3915 4095 4455 +5 1 0 1 0 11 51 -1 20 0.000 0 0 0 0 3825.000 4185.000 3825 3960 4050 4185 3825 4410 +5 1 0 1 0 4 51 -1 20 0.000 0 1 0 0 3825.000 4185.000 3825 3960 3600 4185 3825 4410 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 3825 3960 3825 4410 +-6 +6 5355 3915 5895 4455 +5 1 0 1 0 11 51 -1 20 0.000 0 1 0 0 5625.000 4185.000 5625 3960 5400 4185 5625 4410 +5 1 0 1 0 4 51 -1 20 0.000 0 0 0 0 5625.000 4185.000 5625 3960 5850 4185 5625 4410 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 5625 3960 5625 4410 +-6 +6 3555 5265 4095 5805 +5 1 0 1 0 11 51 -1 20 0.000 0 0 0 0 3825.000 5535.000 3825 5310 4050 5535 3825 5760 +5 1 0 1 0 4 51 -1 20 0.000 0 1 0 0 3825.000 5535.000 3825 5310 3600 5535 3825 5760 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 3825 5310 3825 5760 +-6 +6 5355 5265 5895 5805 +5 1 0 1 0 11 51 -1 20 0.000 0 1 0 0 5625.000 5535.000 5625 5310 5400 5535 5625 5760 +5 1 0 1 0 4 51 -1 20 0.000 0 0 0 0 5625.000 5535.000 5625 5310 5850 5535 5625 5760 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 5625 5310 5625 5760 +-6 +6 4455 4590 4995 5130 +5 1 0 1 0 11 51 -1 20 0.000 0 0 0 0 4725.000 4860.000 4860 4680 4950 4860 4860 5040 +5 1 0 1 0 11 51 -1 20 0.000 0 1 0 0 4725.000 4860.000 4590 4680 4500 4860 4590 5040 +5 1 0 1 0 14 51 -1 20 0.000 0 0 0 0 4725.000 4860.000 4860 5040 4725 5085 4590 5040 +5 1 0 1 0 14 51 -1 20 0.000 0 1 0 0 4725.000 4860.000 4860 4680 4725 4635 4590 4680 +2 2 0 1 14 14 52 -1 20 0.000 2 0 -1 0 0 5 + 4590 4680 4860 4680 4860 5040 4590 5040 4590 4680 +-6 +6 4275 5400 5175 5670 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 5130 5400 5130 5670 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 5085 5400 5085 5670 +2 2 0 1 0 6 51 -1 20 0.000 1 0 -1 0 0 5 + 4275 5400 5175 5400 5175 5670 4275 5670 4275 5400 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 4320 5400 4320 5670 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 4365 5400 4365 5670 +4 1 0 50 -1 16 6 0.0000 4 105 465 4725 5580 http_resp\001 +-6 +6 4275 4050 5175 4320 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 5130 4050 5130 4320 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 5085 4050 5085 4320 +2 2 0 1 0 6 51 -1 20 0.000 1 0 -1 0 0 5 + 4275 4050 5175 4050 5175 4320 4275 4320 4275 4050 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 4320 4050 4320 4320 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 4365 4050 4365 4320 +4 1 0 50 -1 16 6 0.0000 4 105 420 4725 4230 http_req\001 +-6 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 5175 4185 5400 4185 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 4050 4185 4275 4185 +2 1 0 1 0 14 50 -1 -1 0.000 1 0 -1 1 1 2 + 0 0 1.00 30.00 45.00 + 0 0 1.00 30.00 45.00 + 4725 4635 4725 4320 +2 1 0 1 0 14 50 -1 -1 0.000 1 0 -1 1 1 2 + 0 0 1.00 30.00 45.00 + 0 0 1.00 30.00 45.00 + 4725 5400 4725 5085 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 4275 5535 4050 5535 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 5400 5535 5175 5535 +4 1 0 50 -1 16 6 0.0000 4 90 285 5625 3870 driver\001 +4 1 0 50 -1 16 6 0.0000 4 105 300 5625 3735 output\001 +4 1 0 50 -1 16 6 0.0000 4 90 285 3825 3870 driver\001 +4 1 0 50 -1 16 6 0.0000 4 105 240 3825 3735 input\001 +4 0 0 50 -1 16 6 0.0000 4 90 405 5040 4950 eg: HTTP\001 +4 1 0 50 -1 16 6 0.0000 4 90 285 5625 6075 driver\001 +4 1 0 50 -1 16 6 0.0000 4 90 285 3825 6075 driver\001 +4 1 0 50 -1 16 6 0.0000 4 105 300 3825 5940 output\001 +4 1 0 50 -1 16 6 0.0000 4 105 240 5625 5940 input\001 +4 0 0 50 -1 16 6 0.0000 4 105 690 5040 4815 flow processor\001 +-6 +6 6480 4050 8820 5400 +5 1 0 1 0 11 51 -1 20 0.000 0 0 0 0 7650.000 4725.000 7785 4545 7875 4725 7785 4905 +5 1 0 1 0 11 51 -1 20 0.000 0 1 0 0 7650.000 4725.000 7515 4545 7425 4725 7515 4905 +5 1 0 1 0 14 51 -1 20 0.000 0 0 0 0 7650.000 4725.000 7785 4905 7650 4950 7515 4905 +5 1 0 1 0 14 51 -1 20 0.000 0 1 0 0 7650.000 4725.000 7785 4545 7650 4500 7515 4545 +6 6480 4455 7020 4995 +5 1 0 1 0 11 51 -1 20 0.000 0 0 0 0 6750.000 4725.000 6750 4500 6975 4725 6750 4950 +5 1 0 1 0 4 51 -1 20 0.000 0 1 0 0 6750.000 4725.000 6750 4500 6525 4725 6750 4950 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 6750 4500 6750 4950 +-6 +6 8280 4455 8820 4995 +5 1 0 1 0 11 51 -1 20 0.000 0 1 0 0 8550.000 4725.000 8550 4500 8325 4725 8550 4950 +5 1 0 1 0 4 51 -1 20 0.000 0 0 0 0 8550.000 4725.000 8550 4500 8775 4725 8550 4950 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 8550 4500 8550 4950 +-6 +6 7200 4050 8100 4320 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 8055 4050 8055 4320 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 8010 4050 8010 4320 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 7245 4050 7245 4320 +2 2 0 1 0 6 51 -1 20 0.000 1 0 -1 0 0 5 + 7200 4050 8100 4050 8100 4320 7200 4320 7200 4050 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 7290 4050 7290 4320 +4 1 0 50 -1 16 6 0.0000 4 105 420 7650 4230 http_req\001 +-6 +6 7200 5130 8100 5400 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 8055 5130 8055 5400 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 8010 5130 8010 5400 +2 2 0 1 0 6 51 -1 20 0.000 1 0 -1 0 0 5 + 7200 5130 8100 5130 8100 5400 7200 5400 7200 5130 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 7245 5130 7245 5400 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 7290 5130 7290 5400 +4 1 0 50 -1 16 6 0.0000 4 105 465 7650 5310 http_resp\001 +-6 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 8100 4185 8370 4590 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 6930 4590 7200 4185 +2 1 0 1 0 14 50 -1 -1 0.000 1 0 -1 1 1 2 + 0 0 1.00 30.00 45.00 + 0 0 1.00 30.00 45.00 + 7650 4500 7650 4320 +2 1 0 1 0 14 50 -1 -1 0.000 1 0 -1 1 1 2 + 0 0 1.00 30.00 45.00 + 0 0 1.00 30.00 45.00 + 7650 5130 7650 4950 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 7200 5265 6930 4860 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 8370 4860 8100 5265 +2 2 0 1 14 14 52 -1 20 0.000 2 0 -1 0 0 5 + 7515 4545 7785 4545 7785 4905 7515 4905 7515 4545 +4 1 0 50 -1 16 6 0.0000 4 60 285 6750 4230 server\001 +4 1 0 50 -1 16 6 0.0000 4 90 285 6750 4365 socket\001 +4 1 0 50 -1 16 6 0.0000 4 90 255 8550 4230 client\001 +4 1 0 50 -1 16 6 0.0000 4 90 285 8550 4365 socket\001 +4 1 0 50 -1 16 6 0.0000 4 105 210 7650 4770 http\001 +-6 +6 630 4005 2970 5490 +6 630 4680 1170 5220 +5 1 0 1 0 11 51 -1 20 0.000 0 0 0 0 900.000 4950.000 900 4725 1125 4950 900 5175 +5 1 0 1 0 4 51 -1 20 0.000 0 1 0 0 900.000 4950.000 900 4725 675 4950 900 5175 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 900 4725 900 5175 +-6 +6 2430 4680 2970 5220 +5 1 0 1 0 11 51 -1 20 0.000 0 1 0 0 2700.000 4950.000 2700 4725 2475 4950 2700 5175 +5 1 0 1 0 4 51 -1 20 0.000 0 0 0 0 2700.000 4950.000 2700 4725 2925 4950 2700 5175 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 2700 4725 2700 5175 +-6 +6 1530 4005 2070 4545 +5 1 0 1 0 11 51 -1 20 0.000 0 0 0 0 1800.000 4275.000 1935 4095 2025 4275 1935 4455 +5 1 0 1 0 11 51 -1 20 0.000 0 1 0 0 1800.000 4275.000 1665 4095 1575 4275 1665 4455 +5 1 0 1 0 14 51 -1 20 0.000 0 0 0 0 1800.000 4275.000 1935 4455 1800 4500 1665 4455 +5 1 0 1 0 14 51 -1 20 0.000 0 1 0 0 1800.000 4275.000 1935 4095 1800 4050 1665 4095 +2 2 0 1 14 14 52 -1 20 0.000 2 0 -1 0 0 5 + 1665 4095 1935 4095 1935 4455 1665 4455 1665 4095 +-6 +6 1350 4815 2250 5085 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 2205 4815 2205 5085 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 2160 4815 2160 5085 +2 2 0 1 0 6 51 -1 20 0.000 1 0 -1 0 0 5 + 1350 4815 2250 4815 2250 5085 1350 5085 1350 4815 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 1395 4815 1395 5085 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 1440 4815 1440 5085 +4 1 0 50 -1 16 6 0.0000 4 105 420 1800 4995 http_req\001 +-6 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 2250 4950 2475 4950 +2 1 0 1 0 14 50 -1 -1 0.000 1 0 -1 1 1 2 + 0 0 1.00 30.00 45.00 + 0 0 1.00 30.00 45.00 + 1800 4815 1800 4500 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 1125 4950 1350 4950 +4 0 0 50 -1 16 6 0.0000 4 105 690 2115 4365 eg: HTTP_REQ\001 +4 1 0 50 -1 16 6 0.0000 4 105 300 2700 5355 output\001 +4 1 0 50 -1 16 6 0.0000 4 90 285 2700 5490 driver\001 +4 1 0 50 -1 16 6 0.0000 4 105 240 900 5355 input\001 +4 1 0 50 -1 16 6 0.0000 4 90 285 900 5490 driver\001 +4 0 0 50 -1 16 6 0.0000 4 105 690 2115 4230 flow processor\001 +-6 +6 675 11025 6615 12555 +5 1 0 1 0 11 51 -1 20 0.000 0 0 0 0 3645.000 11790.000 3780 11610 3870 11790 3780 11970 +5 1 0 1 0 11 51 -1 20 0.000 0 1 0 0 3645.000 11790.000 3510 11610 3420 11790 3510 11970 +5 1 0 1 0 14 51 -1 20 0.000 0 0 0 0 3645.000 11790.000 3780 11970 3645 12015 3510 11970 +5 1 0 1 0 14 51 -1 20 0.000 0 1 0 0 3645.000 11790.000 3780 11610 3645 11565 3510 11610 +6 675 11520 1215 12060 +5 1 0 1 0 11 51 -1 20 0.000 0 0 0 0 945.000 11790.000 945 11565 1170 11790 945 12015 +5 1 0 1 0 4 51 -1 20 0.000 0 1 0 0 945.000 11790.000 945 11565 720 11790 945 12015 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 945 11565 945 12015 +-6 +6 3195 11115 4095 11385 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 4050 11115 4050 11385 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 4005 11115 4005 11385 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 3240 11115 3240 11385 +2 2 0 1 0 6 51 -1 20 0.000 1 0 -1 0 0 5 + 3195 11115 4095 11115 4095 11385 3195 11385 3195 11115 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 3285 11115 3285 11385 +4 1 0 50 -1 16 6 0.0000 4 105 420 3645 11295 http_req\001 +-6 +6 3195 12195 4095 12465 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 4050 12195 4050 12465 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 4005 12195 4005 12465 +2 2 0 1 0 6 51 -1 20 0.000 1 0 -1 0 0 5 + 3195 12195 4095 12195 4095 12465 3195 12465 3195 12195 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 3240 12195 3240 12465 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 3285 12195 3285 12465 +4 1 0 50 -1 16 6 0.0000 4 105 465 3645 12375 http_resp\001 +-6 +6 4275 11520 4815 12060 +5 1 0 1 0 11 51 -1 20 0.000 0 1 0 0 4545.000 11790.000 4545 11565 4320 11790 4545 12015 +5 1 0 1 0 11 51 -1 20 0.000 0 0 0 0 4545.000 11790.000 4545 11565 4770 11790 4545 12015 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 4545 11565 4545 12015 +-6 +6 2475 11520 3015 12060 +5 1 0 1 0 11 51 -1 20 0.000 0 1 0 0 2745.000 11790.000 2745 11565 2520 11790 2745 12015 +5 1 0 1 0 11 51 -1 20 0.000 0 0 0 0 2745.000 11790.000 2745 11565 2970 11790 2745 12015 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 2745 11565 2745 12015 +-6 +6 4995 11115 5895 11385 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 5850 11115 5850 11385 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 5805 11115 5805 11385 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 5040 11115 5040 11385 +2 2 0 1 0 6 51 -1 20 0.000 1 0 -1 0 0 5 + 4995 11115 5895 11115 5895 11385 4995 11385 4995 11115 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 5085 11115 5085 11385 +4 1 0 50 -1 16 6 0.0000 4 105 465 5445 11295 https_req\001 +-6 +6 4995 12195 5895 12465 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 5850 12195 5850 12465 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 5805 12195 5805 12465 +2 2 0 1 0 6 51 -1 20 0.000 1 0 -1 0 0 5 + 4995 12195 5895 12195 5895 12465 4995 12465 4995 12195 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 5040 12195 5040 12465 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 5085 12195 5085 12465 +4 1 0 50 -1 16 6 0.0000 4 105 510 5445 12375 https_resp\001 +-6 +6 1395 11115 2295 11385 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 2250 11115 2250 11385 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 2205 11115 2205 11385 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 1440 11115 1440 11385 +2 2 0 1 0 6 51 -1 20 0.000 1 0 -1 0 0 5 + 1395 11115 2295 11115 2295 11385 1395 11385 1395 11115 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 1485 11115 1485 11385 +4 1 0 50 -1 16 6 0.0000 4 105 465 1845 11295 https_req\001 +-6 +6 1395 12195 2295 12465 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 2250 12195 2250 12465 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 2205 12195 2205 12465 +2 2 0 1 0 6 51 -1 20 0.000 1 0 -1 0 0 5 + 1395 12195 2295 12195 2295 12465 1395 12465 1395 12195 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 1440 12195 1440 12465 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 1485 12195 1485 12465 +4 1 0 50 -1 16 6 0.0000 4 105 510 1845 12375 https_resp\001 +-6 +6 6075 11520 6615 12060 +5 1 0 1 0 11 51 -1 20 0.000 0 1 0 0 6345.000 11790.000 6345 11565 6120 11790 6345 12015 +5 1 0 1 0 4 51 -1 20 0.000 0 0 0 0 6345.000 11790.000 6345 11565 6570 11790 6345 12015 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 6345 11565 6345 12015 +-6 +6 675 12195 1170 12555 +2 4 0 1 16 17 53 -1 -1 0.000 0 0 7 0 0 5 + 1170 12555 1170 12195 675 12195 675 12555 1170 12555 +4 1 0 50 -1 16 6 0.0000 4 90 450 900 12420 TCPv4_S\001 +-6 +6 6120 12195 6615 12555 +2 4 0 1 16 17 53 -1 -1 0.000 0 0 7 0 0 5 + 6615 12555 6615 12195 6120 12195 6120 12555 6615 12555 +4 1 0 50 -1 16 6 0.0000 4 90 450 6345 12420 TCPv4_S\001 +-6 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 1125 11655 1395 11250 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 1395 12330 1125 11925 +2 1 0 1 0 14 50 -1 -1 0.000 1 0 -1 1 1 2 + 0 0 1.00 30.00 45.00 + 0 0 1.00 30.00 45.00 + 3645 11565 3645 11385 +2 1 0 1 0 14 50 -1 -1 0.000 1 0 -1 1 1 2 + 0 0 1.00 30.00 45.00 + 0 0 1.00 30.00 45.00 + 3645 12195 3645 12015 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 4995 12330 4725 11925 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 4725 11655 4995 11250 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 4375 11925 4105 12330 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 4095 11250 4365 11655 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 3195 12330 2925 11925 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 2925 11655 3195 11250 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 2575 11925 2305 12330 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 2295 11250 2565 11655 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 5895 11250 6165 11655 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 6165 11925 5895 12330 +2 2 0 1 14 14 52 -1 20 0.000 2 0 -1 0 0 5 + 3510 11610 3780 11610 3780 11970 3510 11970 3510 11610 +2 4 0 1 16 17 54 -1 20 0.000 0 0 7 0 0 5 + 3150 12555 3150 11025 675 11025 675 12555 3150 12555 +2 3 0 1 28 30 53 -1 20 0.000 1 0 -1 0 0 11 + 6120 11070 5895 11565 5895 12015 6120 12510 4950 12510 4410 12510 + 4185 12015 4185 11565 4410 11070 4995 11070 6120 11070 +2 4 0 1 16 17 54 -1 20 0.000 0 0 7 0 0 5 + 6615 12555 6615 11025 4140 11025 4140 12555 6615 12555 +2 3 0 1 28 30 53 -1 20 0.000 0 0 -1 0 0 11 + 1170 11070 1395 11565 1395 12015 1170 12510 2340 12510 2880 12510 + 3105 12015 3105 11565 2880 11070 2295 11070 1170 11070 +4 1 0 50 -1 16 6 0.0000 4 60 285 945 11295 server\001 +4 1 0 50 -1 16 6 0.0000 4 90 285 945 11430 socket\001 +4 1 0 50 -1 16 6 0.0000 4 75 210 4545 11430 SSL\001 +4 1 0 50 -1 16 6 0.0000 4 75 210 2745 11430 SSL\001 +4 1 0 50 -1 16 6 0.0000 4 90 255 6345 11295 client\001 +4 1 0 50 -1 16 6 0.0000 4 90 285 6345 11430 socket\001 +4 1 0 50 -1 16 6 0.0000 4 105 210 3645 11835 http\001 +-6 +6 630 6750 2970 8100 +6 630 7155 1170 7695 +5 1 0 1 0 11 51 -1 20 0.000 0 0 0 0 900.000 7425.000 900 7200 1125 7425 900 7650 +5 1 0 1 0 4 51 -1 20 0.000 0 1 0 0 900.000 7425.000 900 7200 675 7425 900 7650 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 900 7200 900 7650 +-6 +6 2430 7155 2970 7695 +5 1 0 1 0 11 51 -1 20 0.000 0 1 0 0 2700.000 7425.000 2700 7200 2475 7425 2700 7650 +5 1 0 1 0 4 51 -1 20 0.000 0 0 0 0 2700.000 7425.000 2700 7200 2925 7425 2700 7650 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 2700 7200 2700 7650 +-6 +6 1350 6750 2250 7020 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 2205 6750 2205 7020 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 2160 6750 2160 7020 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 1395 6750 1395 7020 +2 2 0 1 0 6 51 -1 20 0.000 1 0 -1 0 0 5 + 1350 6750 2250 6750 2250 7020 1350 7020 1350 6750 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 1440 6750 1440 7020 +4 1 0 50 -1 16 6 0.0000 4 105 420 1800 6930 http_req\001 +-6 +6 1305 7020 1845 7830 +5 1 0 1 0 11 51 -1 20 0.000 0 0 0 0 1575.000 7425.000 1710 7245 1800 7425 1710 7605 +5 1 0 1 0 11 51 -1 20 0.000 0 1 0 0 1575.000 7425.000 1440 7245 1350 7425 1440 7605 +5 1 0 1 0 14 51 -1 20 0.000 0 0 0 0 1575.000 7425.000 1710 7605 1575 7650 1440 7605 +5 1 0 1 0 14 51 -1 20 0.000 0 1 0 0 1575.000 7425.000 1710 7245 1575 7200 1440 7245 +2 1 0 1 0 14 50 -1 -1 0.000 1 0 -1 1 1 2 + 0 0 1.00 30.00 45.00 + 0 0 1.00 30.00 45.00 + 1575 7200 1575 7020 +2 1 0 1 0 14 50 -1 -1 0.000 1 0 -1 1 1 2 + 0 0 1.00 30.00 45.00 + 0 0 1.00 30.00 45.00 + 1575 7830 1575 7650 +2 2 0 1 14 14 52 -1 20 0.000 2 0 -1 0 0 5 + 1440 7245 1710 7245 1710 7605 1440 7605 1440 7245 +4 1 0 50 -1 16 6 0.0000 4 105 210 1575 7470 http\001 +-6 +6 1755 7020 2295 7830 +5 1 0 1 0 14 51 -1 20 0.000 0 0 0 0 2025.000 7425.000 2160 7605 2025 7650 1890 7605 +5 1 0 1 0 14 51 -1 20 0.000 0 1 0 0 2025.000 7425.000 2160 7245 2025 7200 1890 7245 +5 1 0 1 0 11 51 -1 20 0.000 0 1 0 0 2025.000 7425.000 1890 7245 1800 7425 1890 7605 +5 1 0 1 0 11 51 -1 20 0.000 0 0 0 0 2025.000 7425.000 2160 7245 2250 7425 2160 7605 +2 1 0 1 0 14 50 -1 -1 0.000 1 0 -1 1 1 2 + 0 0 1.00 30.00 45.00 + 0 0 1.00 30.00 45.00 + 2025 7200 2025 7020 +2 1 0 1 0 14 50 -1 -1 0.000 1 0 -1 1 1 2 + 0 0 1.00 30.00 45.00 + 0 0 1.00 30.00 45.00 + 2025 7830 2025 7650 +2 2 0 1 14 14 52 -1 20 0.000 2 0 -1 0 0 5 + 1890 7245 2160 7245 2160 7605 1890 7605 1890 7245 +4 1 0 50 -1 16 6 0.0000 4 105 390 2025 7470 filtering\001 +-6 +6 1350 7830 2250 8100 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 2205 7830 2205 8100 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 2160 7830 2160 8100 +2 2 0 1 0 6 51 -1 20 0.000 1 0 -1 0 0 5 + 1350 7830 2250 7830 2250 8100 1350 8100 1350 7830 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 1395 7830 1395 8100 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 1440 7830 1440 8100 +4 1 0 50 -1 16 6 0.0000 4 105 465 1800 8010 http_resp\001 +-6 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 2250 6885 2520 7290 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 1080 7290 1350 6885 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 1350 7965 1080 7560 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 2520 7560 2250 7965 +4 1 0 50 -1 16 6 0.0000 4 60 285 900 6930 server\001 +4 1 0 50 -1 16 6 0.0000 4 90 285 900 7065 socket\001 +4 1 0 50 -1 16 6 0.0000 4 90 255 2700 6930 client\001 +4 1 0 50 -1 16 6 0.0000 4 90 285 2700 7065 socket\001 +-6 +6 4680 9000 8820 10530 +5 1 0 1 0 11 51 -1 20 0.000 0 0 0 0 7650.000 9765.000 7785 9585 7875 9765 7785 9945 +5 1 0 1 0 11 51 -1 20 0.000 0 1 0 0 7650.000 9765.000 7515 9585 7425 9765 7515 9945 +5 1 0 1 0 14 51 -1 20 0.000 0 0 0 0 7650.000 9765.000 7785 9945 7650 9990 7515 9945 +5 1 0 1 0 14 51 -1 20 0.000 0 1 0 0 7650.000 9765.000 7785 9585 7650 9540 7515 9585 +6 4680 9495 5220 10035 +5 1 0 1 0 11 51 -1 20 0.000 0 0 0 0 4950.000 9765.000 4950 9540 5175 9765 4950 9990 +5 1 0 1 0 4 51 -1 20 0.000 0 1 0 0 4950.000 9765.000 4950 9540 4725 9765 4950 9990 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 4950 9540 4950 9990 +-6 +6 8280 9495 8820 10035 +5 1 0 1 0 11 51 -1 20 0.000 0 1 0 0 8550.000 9765.000 8550 9540 8325 9765 8550 9990 +5 1 0 1 0 4 51 -1 20 0.000 0 0 0 0 8550.000 9765.000 8550 9540 8775 9765 8550 9990 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 8550 9540 8550 9990 +-6 +6 7200 9090 8100 9360 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 8055 9090 8055 9360 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 8010 9090 8010 9360 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 7245 9090 7245 9360 +2 2 0 1 0 6 51 -1 20 0.000 1 0 -1 0 0 5 + 7200 9090 8100 9090 8100 9360 7200 9360 7200 9090 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 7290 9090 7290 9360 +4 1 0 50 -1 16 6 0.0000 4 105 420 7650 9270 http_req\001 +-6 +6 7200 10170 8100 10440 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 8055 10170 8055 10440 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 8010 10170 8010 10440 +2 2 0 1 0 6 51 -1 20 0.000 1 0 -1 0 0 5 + 7200 10170 8100 10170 8100 10440 7200 10440 7200 10170 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 7245 10170 7245 10440 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 7290 10170 7290 10440 +4 1 0 50 -1 16 6 0.0000 4 105 465 7650 10350 http_resp\001 +-6 +6 6480 9495 7020 10035 +5 1 0 1 0 11 51 -1 20 0.000 0 1 0 0 6750.000 9765.000 6750 9540 6525 9765 6750 9990 +5 1 0 1 0 11 51 -1 20 0.000 0 0 0 0 6750.000 9765.000 6750 9540 6975 9765 6750 9990 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 6750 9540 6750 9990 +-6 +6 5400 10170 6300 10440 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 6255 10170 6255 10440 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 6210 10170 6210 10440 +2 2 0 1 0 6 51 -1 20 0.000 1 0 -1 0 0 5 + 5400 10170 6300 10170 6300 10440 5400 10440 5400 10170 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 5445 10170 5445 10440 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 5490 10170 5490 10440 +4 1 0 50 -1 16 6 0.0000 4 105 510 5850 10350 https_resp\001 +-6 +6 5400 9090 6300 9360 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 6255 9090 6255 9360 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 6210 9090 6210 9360 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 5445 9090 5445 9360 +2 2 0 1 0 6 51 -1 20 0.000 1 0 -1 0 0 5 + 5400 9090 6300 9090 6300 9360 5400 9360 5400 9090 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 5490 9090 5490 9360 +4 1 0 50 -1 16 6 0.0000 4 105 465 5850 9270 https_req\001 +-6 +6 4680 10170 5175 10530 +2 4 0 1 16 17 53 -1 -1 0.000 0 0 7 0 0 5 + 5175 10530 5175 10170 4680 10170 4680 10530 5175 10530 +4 1 0 50 -1 16 6 0.0000 4 90 450 4905 10395 TCPv4_S\001 +-6 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 5130 9630 5400 9225 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 5400 10305 5130 9900 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 8100 9225 8370 9630 +2 1 0 1 0 14 50 -1 -1 0.000 1 0 -1 1 1 2 + 0 0 1.00 30.00 45.00 + 0 0 1.00 30.00 45.00 + 7650 9540 7650 9360 +2 1 0 1 0 14 50 -1 -1 0.000 1 0 -1 1 1 2 + 0 0 1.00 30.00 45.00 + 0 0 1.00 30.00 45.00 + 7650 10170 7650 9990 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 7200 10305 6930 9900 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 8370 9900 8100 10305 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 6930 9630 7200 9225 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 6580 9900 6310 10305 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 6300 9225 6570 9630 +2 2 0 1 14 14 52 -1 20 0.000 2 0 -1 0 0 5 + 7515 9585 7785 9585 7785 9945 7515 9945 7515 9585 +2 4 0 1 16 17 54 -1 20 0.000 0 0 7 0 0 5 + 7155 10530 7155 9000 4680 9000 4680 10530 7155 10530 +2 3 0 1 28 30 53 -1 20 0.000 1 0 -1 0 0 11 + 5175 9045 5400 9540 5400 9990 5175 10485 6345 10485 6885 10485 + 7110 9990 7110 9540 6885 9045 6300 9045 5175 9045 +4 1 0 50 -1 16 6 0.0000 4 60 285 4950 9270 server\001 +4 1 0 50 -1 16 6 0.0000 4 90 285 4950 9405 socket\001 +4 1 0 50 -1 16 6 0.0000 4 90 255 8550 9270 client\001 +4 1 0 50 -1 16 6 0.0000 4 90 285 8550 9405 socket\001 +4 1 0 50 -1 16 6 0.0000 4 75 210 6750 9405 SSL\001 +4 1 0 50 -1 16 6 0.0000 4 105 210 7650 9810 http\001 +-6 +6 675 9000 4275 10575 +6 1800 9900 2250 10125 +2 2 0 1 29 30 51 -1 20 0.000 0 0 -1 0 0 5 + 1800 9900 2250 9900 2250 10125 1800 10125 1800 9900 +4 1 0 50 -1 16 6 0.0000 4 75 210 2025 10035 SSL\001 +-6 +6 2700 9900 3150 10125 +2 2 0 1 29 30 51 -1 20 0.000 0 0 -1 0 0 5 + 2700 9900 3150 9900 3150 10125 2700 10125 2700 9900 +4 1 0 50 -1 16 6 0.0000 4 75 210 2925 10035 SSL\001 +-6 +6 3600 9900 4050 10125 +2 2 0 1 29 30 51 -1 20 0.000 0 0 -1 0 0 5 + 3600 9900 4050 9900 4050 10125 3600 10125 3600 9900 +4 1 0 50 -1 16 6 0.0000 4 75 210 3825 10035 SSL\001 +-6 +6 900 9900 1350 10125 +2 2 0 1 29 30 51 -1 20 0.000 0 0 -1 0 0 5 + 900 9900 1350 9900 1350 10125 900 10125 900 9900 +4 1 0 50 -1 16 6 0.0000 4 75 210 1125 10035 SSL\001 +-6 +6 2520 10350 3330 10575 +2 2 0 1 16 17 51 -1 20 0.000 0 0 -1 0 0 5 + 2520 10350 3330 10350 3330 10575 2520 10575 2520 10350 +4 1 0 50 -1 16 6 0.0000 4 90 510 2925 10485 UNIX_STR\001 +-6 +6 1125 9450 1575 9675 +2 2 0 1 9 11 51 -1 20 0.000 0 0 -1 0 0 5 + 1125 9450 1575 9450 1575 9675 1125 9675 1125 9450 +4 1 0 50 -1 16 6 0.0000 4 90 315 1350 9585 deflate\001 +-6 +6 2025 9450 2475 9675 +2 2 0 1 9 11 51 -1 20 0.000 0 0 -1 0 0 5 + 2025 9450 2475 9450 2475 9675 2025 9675 2025 9450 +4 1 0 50 -1 16 6 0.0000 4 90 315 2250 9585 deflate\001 +-6 +6 2925 9450 3375 9675 +2 2 0 1 9 11 51 -1 20 0.000 0 0 -1 0 0 5 + 2925 9450 3375 9450 3375 9675 2925 9675 2925 9450 +4 1 0 50 -1 16 6 0.0000 4 90 315 3150 9585 deflate\001 +-6 +6 3825 9450 4275 9675 +2 2 0 1 9 11 51 -1 20 0.000 0 0 -1 0 0 5 + 3825 9450 4275 9450 4275 9675 3825 9675 3825 9450 +4 1 0 50 -1 16 6 0.0000 4 90 315 4050 9585 deflate\001 +-6 +6 675 10350 1530 10575 +2 2 0 1 16 17 51 -1 20 0.000 0 0 -1 0 0 5 + 675 10350 1530 10350 1530 10575 675 10575 675 10350 +4 1 0 50 -1 16 6 0.0000 4 75 315 1125 10485 TCPv4\001 +-6 +6 1620 10350 2430 10575 +2 2 0 1 16 17 51 -1 20 0.000 0 0 -1 0 0 5 + 1620 10350 2430 10350 2430 10575 1620 10575 1620 10350 +4 1 0 50 -1 16 6 0.0000 4 75 315 2025 10485 TCPv6\001 +-6 +6 3420 10350 4275 10575 +2 2 0 1 16 17 51 -1 20 0.000 0 0 -1 0 0 5 + 3420 10350 4275 10350 4275 10575 3420 10575 3420 10350 +4 1 0 50 -1 16 6 0.0000 4 75 330 3825 10485 PIPES\001 +-6 +6 675 9000 4275 9225 +2 2 0 1 12 14 51 -1 20 0.000 0 0 -1 0 0 5 + 675 9000 4275 9000 4275 9225 675 9225 675 9000 +4 1 0 50 -1 16 6 0.0000 4 105 1950 2475 9135 HTTP flow analyzer using stream interfaces\001 +-6 +2 1 0 1 20 -1 50 -1 -1 0.000 1 0 -1 0 1 2 + 0 0 1.00 30.00 45.00 + 765 10350 765 9225 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 810 10350 810 9225 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 0 1 2 + 0 0 1.00 30.00 45.00 + 990 9900 990 9225 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 1035 9900 1035 9225 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 1170 10350 1170 10125 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 0 1 2 + 0 0 1.00 30.00 45.00 + 1215 9900 1215 9675 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 1260 9900 1260 9675 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 1395 9450 1395 9225 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 0 1 2 + 0 0 1.00 30.00 45.00 + 1440 10350 1440 9675 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 1485 10350 1485 9675 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 0 1 2 + 0 0 1.00 30.00 45.00 + 1665 10350 1665 9225 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 1710 10350 1710 9225 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 0 1 2 + 0 0 1.00 30.00 45.00 + 1890 9900 1890 9225 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 1935 9900 1935 9225 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 2070 10350 2070 10125 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 0 1 2 + 0 0 1.00 30.00 45.00 + 2115 9900 2115 9675 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 2160 9900 2160 9675 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 2295 9450 2295 9225 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 0 1 2 + 0 0 1.00 30.00 45.00 + 2340 10350 2340 9675 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 2385 10350 2385 9675 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 0 1 2 + 0 0 1.00 30.00 45.00 + 2565 10350 2565 9225 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 2610 10350 2610 9225 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 0 1 2 + 0 0 1.00 30.00 45.00 + 2790 9900 2790 9225 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 2835 9900 2835 9225 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 0 1 2 + 0 0 1.00 30.00 45.00 + 3015 9900 3015 9675 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 3060 9900 3060 9675 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 2970 10350 2970 10125 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 0 1 2 + 0 0 1.00 30.00 45.00 + 3240 10350 3240 9675 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 3285 10350 3285 9675 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 3195 9450 3195 9225 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 0 1 2 + 0 0 1.00 30.00 45.00 + 3465 10350 3465 9225 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 3510 10350 3510 9225 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 0 1 2 + 0 0 1.00 30.00 45.00 + 3690 9900 3690 9225 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 3735 9900 3735 9225 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 3870 10350 3870 10125 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 0 1 2 + 0 0 1.00 30.00 45.00 + 3915 9900 3915 9675 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 3960 9900 3960 9675 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 0 1 2 + 0 0 1.00 30.00 45.00 + 4140 10350 4140 9675 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 4185 10350 4185 9675 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 4095 9450 4095 9225 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 0 1 2 + 0 0 1.00 30.00 45.00 + 1350 9450 1350 9225 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 0 1 2 + 0 0 1.00 30.00 45.00 + 2250 9450 2250 9225 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 0 1 2 + 0 0 1.00 30.00 45.00 + 3150 9450 3150 9225 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 0 1 2 + 0 0 1.00 30.00 45.00 + 4050 9450 4050 9225 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 0 1 2 + 0 0 1.00 30.00 45.00 + 3825 10350 3825 10125 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 0 1 2 + 0 0 1.00 30.00 45.00 + 2925 10350 2925 10125 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 0 1 2 + 0 0 1.00 30.00 45.00 + 2025 10350 2025 10125 +2 1 0 1 20 14 50 -1 -1 0.000 1 0 -1 0 1 2 + 0 0 1.00 30.00 45.00 + 1125 10350 1125 10125 +-6 +6 3780 6750 6120 8100 +5 1 0 1 0 11 51 -1 20 0.000 0 0 0 0 4725.000 7425.000 4860 7245 4950 7425 4860 7605 +5 1 0 1 0 11 51 -1 20 0.000 0 1 0 0 4725.000 7425.000 4590 7245 4500 7425 4590 7605 +5 1 0 1 0 14 51 -1 20 0.000 0 0 0 0 4725.000 7425.000 4860 7605 4725 7650 4590 7605 +5 1 0 1 0 14 51 -1 20 0.000 0 1 0 0 4725.000 7425.000 4860 7245 4725 7200 4590 7245 +5 1 0 1 0 14 51 -1 20 0.000 0 0 0 0 5175.000 7425.000 5310 7605 5175 7650 5040 7605 +5 1 0 1 0 14 51 -1 20 0.000 0 1 0 0 5175.000 7425.000 5310 7245 5175 7200 5040 7245 +5 1 0 1 0 11 51 -1 20 0.000 0 1 0 0 5175.000 7425.000 5040 7245 4950 7425 5040 7605 +5 1 0 1 0 11 51 -1 20 0.000 0 0 0 0 5175.000 7425.000 5310 7245 5400 7425 5310 7605 +6 3780 7155 4320 7695 +5 1 0 1 0 11 51 -1 20 0.000 0 0 0 0 4050.000 7425.000 4050 7200 4275 7425 4050 7650 +5 1 0 1 0 4 51 -1 20 0.000 0 1 0 0 4050.000 7425.000 4050 7200 3825 7425 4050 7650 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 4050 7200 4050 7650 +-6 +6 5580 7155 6120 7695 +5 1 0 1 0 11 51 -1 20 0.000 0 1 0 0 5850.000 7425.000 5850 7200 5625 7425 5850 7650 +5 1 0 1 0 4 51 -1 20 0.000 0 0 0 0 5850.000 7425.000 5850 7200 6075 7425 5850 7650 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 5850 7200 5850 7650 +-6 +6 4500 6750 5400 7020 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 5355 6750 5355 7020 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 5310 6750 5310 7020 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 4545 6750 4545 7020 +2 2 0 1 0 6 51 -1 20 0.000 1 0 -1 0 0 5 + 4500 6750 5400 6750 5400 7020 4500 7020 4500 6750 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 4590 6750 4590 7020 +4 1 0 50 -1 16 6 0.0000 4 105 420 4950 6930 http_req\001 +-6 +6 4500 7830 5400 8100 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 5355 7830 5355 8100 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 5310 7830 5310 8100 +2 2 0 1 0 6 51 -1 20 0.000 1 0 -1 0 0 5 + 4500 7830 5400 7830 5400 8100 4500 8100 4500 7830 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 4545 7830 4545 8100 +2 1 0 1 0 14 50 -1 -1 0.000 0 0 -1 0 0 2 + 4590 7830 4590 8100 +4 1 0 50 -1 16 6 0.0000 4 105 465 4950 8010 http_resp\001 +-6 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 5400 6885 5670 7290 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 4230 7290 4500 6885 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 4500 7965 4230 7560 +2 1 0 1 0 0 50 -1 -1 0.000 1 0 -1 1 0 2 + 0 0 1.00 30.00 45.00 + 5670 7560 5400 7965 +2 1 0 1 0 14 50 -1 -1 0.000 1 0 -1 1 1 2 + 0 0 1.00 30.00 45.00 + 0 0 1.00 30.00 45.00 + 4725 7200 4725 7020 +2 2 0 1 14 14 52 -1 20 0.000 2 0 -1 0 0 5 + 4590 7245 4860 7245 4860 7605 4590 7605 4590 7245 +2 1 0 1 0 14 50 -1 -1 0.000 1 0 -1 1 1 2 + 0 0 1.00 30.00 45.00 + 0 0 1.00 30.00 45.00 + 5175 7830 5175 7650 +2 2 0 1 14 14 52 -1 20 0.000 2 0 -1 0 0 5 + 5040 7245 5310 7245 5310 7605 5040 7605 5040 7245 +4 1 0 50 -1 16 6 0.0000 4 60 285 4050 6930 server\001 +4 1 0 50 -1 16 6 0.0000 4 90 285 4050 7065 socket\001 +4 1 0 50 -1 16 6 0.0000 4 90 255 5850 6930 client\001 +4 1 0 50 -1 16 6 0.0000 4 90 285 5850 7065 socket\001 +4 1 0 50 -1 16 6 0.0000 4 105 210 4725 7425 http\001 +4 1 0 50 -1 16 6 0.0000 4 75 150 4725 7515 req\001 +4 1 0 50 -1 16 6 0.0000 4 105 210 5175 7425 http\001 +4 1 0 50 -1 16 6 0.0000 4 75 195 5175 7515 resp\001 +-6 +4 0 0 53 -1 19 11 0.0000 4 180 5520 675 8775 Multi-layer protocol encapsulation using intermediate buffers :\001 +4 0 0 53 -1 19 11 0.0000 4 180 4200 675 6525 Multiple protocol processing on the same flow :\001 +4 0 0 53 -1 19 11 0.0000 4 180 2085 675 3600 Principles of operation :\001 diff --git a/doc/how-it-works.txt b/doc/how-it-works.txt new file mode 100644 index 000000000..2d1cb89a0 --- /dev/null +++ b/doc/how-it-works.txt @@ -0,0 +1,60 @@ +How it works ? (unfinished and inexact) + +For TCP and HTTP : + +- listeners create listening sockets with a READ callback pointing to the + protocol-specific accept() function. + +- the protocol-specific accept() function then accept()'s the connection and + instantiates a "server TCP socket" (which is dedicated to the client side), + and configures it (non_block, get_original_dst, ...). + +For TCP : +- in case of pure TCP, a request buffer is created, as well as a "client TCP + socket", which tries to connect to the server. + +- once the connection is established, the response buffer is allocated and + connected to both ends. + +- both sockets are set to "autonomous mode" so that they only wake up their + supervising session when they encounter a special condition (error or close). + + +For HTTP : +- in case of HTTP, a request buffer is created with the "HOLD" flag set and + a read limit to support header rewriting (may be this one will be removed + eventually because it's better to limit only to the buffer size and report + an error when rewritten data overflows) + +- a "flow analyzer" is attached to the buffer (or possibly multiple flow + analyzers). For the request, the flow analyzer is "http_lb_req". The flow + analyzer is a function which gets called when new data is present and + blocked. It has a timeout (request timeout). It can also be bypassed on + demand. + +- when the "http_lb_req" has received the whole request, it creates a client + socket with all the parameters needed to try to connect to the server. When + the connection establishes, the response buffer is allocated on the fly, + put to HOLD mode, and a an "http_lb_resp" flow analyzer is attached to the + buffer. + + +For client-side HTTPS : + +- the accept() function must completely instantiate a TCP socket + an SSL + reader. It is when the SSL session is complete that we call the + protocol-specific accept(), and create its buffer. + + + + +Conclusions +----------- + +- we need a generic TCP accept() function with a lot of flags set by the + listener, to tell it what info we need to get at the accept() time, and + what flags will have to be set on the socket. + +- once the TCP accept() function ends, it wakes up the protocol supervisor + which is in charge of creating the buffers, etc, switch states, etc... + diff --git a/haproxy.c b/haproxy.c deleted file mode 100644 index 34cb0bb76..000000000 --- a/haproxy.c +++ /dev/null @@ -1,10434 +0,0 @@ -/* - * HA-Proxy : High Availability-enabled HTTP/TCP proxy - * 2000-2006 - Willy Tarreau - willy AT meta-x DOT org. - * - * 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. - * - * Please refer to RFC2068 or RFC2616 for informations about HTTP protocol, and - * RFC2965 for informations about cookies usage. More generally, the IETF HTTP - * Working Group's web site should be consulted for protocol related changes : - * - * http://ftp.ics.uci.edu/pub/ietf/http/ - * - * Pending bugs (may be not fixed because never reproduced) : - * - solaris only : sometimes, an HTTP proxy with only a dispatch address causes - * the proxy to terminate (no core) if the client breaks the connection during - * the response. Seen on 1.1.8pre4, but never reproduced. May not be related to - * the snprintf() bug since requests were simple (GET / HTTP/1.0), but may be - * related to missing setsid() (fixed in 1.1.15) - * - a proxy with an invalid config will prevent the startup even if disabled. - * - * ChangeLog has moved to the CHANGELOG file. - * - * TODO: - * - handle properly intermediate incomplete server headers. Done ? - * - handle hot-reconfiguration - * - fix client/server state transition when server is in connect or headers state - * and client suddenly disconnects. The server *should* switch to SHUT_WR, but - * still handle HTTP headers. - * - remove MAX_NEWHDR - * - cut this huge file into several ones - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef USE_PCRE -#include -#include -#else -#include -#endif - -#if defined(TPROXY) && defined(NETFILTER) -#include -#endif - -#if defined(__dietlibc__) -#include -#endif - -#if defined(ENABLE_POLL) -#include -#endif - -#if defined(ENABLE_EPOLL) -#if !defined(USE_MY_EPOLL) -#include -#else -#include "include/epoll.h" -#endif -#endif - -#ifdef DEBUG_FULL -#include -#endif - -#include -#include -#include "include/appsession.h" -#include "include/mini-clist.h" - -#ifndef HAPROXY_VERSION -#define HAPROXY_VERSION "1.2.14" -#endif - -#ifndef HAPROXY_DATE -#define HAPROXY_DATE "2006/05/21" -#endif - -/* this is for libc5 for example */ -#ifndef TCP_NODELAY -#define TCP_NODELAY 1 -#endif - -#ifndef SHUT_RD -#define SHUT_RD 0 -#endif - -#ifndef SHUT_WR -#define SHUT_WR 1 -#endif - -/* - * BUFSIZE defines the size of a read and write buffer. It is the maximum - * amount of bytes which can be stored by the proxy for each session. However, - * when reading HTTP headers, the proxy needs some spare space to add or rewrite - * headers if needed. The size of this spare is defined with MAXREWRITE. So it - * is not possible to process headers longer than BUFSIZE-MAXREWRITE bytes. By - * default, BUFSIZE=16384 bytes and MAXREWRITE=BUFSIZE/2, so the maximum length - * of headers accepted is 8192 bytes, which is in line with Apache's limits. - */ -#ifndef BUFSIZE -#define BUFSIZE 16384 -#endif - -// reserved buffer space for header rewriting -#ifndef MAXREWRITE -#define MAXREWRITE (BUFSIZE / 2) -#endif - -#define REQURI_LEN 1024 -#define CAPTURE_LEN 64 - -// max # args on a configuration line -#define MAX_LINE_ARGS 40 - -// max # of added headers per request -#define MAX_NEWHDR 10 - -// max # of matches per regexp -#define MAX_MATCH 10 - -// cookie delimitor in "prefix" mode. This character is inserted between the -// persistence cookie and the original value. The '~' is allowed by RFC2965, -// and should not be too common in server names. -#ifndef COOKIE_DELIM -#define COOKIE_DELIM '~' -#endif - -#define CONN_RETRIES 3 - -#define CHK_CONNTIME 2000 -#define DEF_CHKINTR 2000 -#define DEF_FALLTIME 3 -#define DEF_RISETIME 2 -#define DEF_CHECK_REQ "OPTIONS / HTTP/1.0\r\n\r\n" - -/* Default connections limit. - * - * A system limit can be enforced at build time in order to avoid using haproxy - * beyond reasonable system limits. For this, just define SYSTEM_MAXCONN to the - * absolute limit accepted by the system. If the configuration specifies a - * higher value, it will be capped to SYSTEM_MAXCONN and a warning will be - * emitted. The only way to override this limit will be to set it via the - * command-line '-n' argument. - */ -#ifndef SYSTEM_MAXCONN -#define DEFAULT_MAXCONN 2000 -#else -#define DEFAULT_MAXCONN SYSTEM_MAXCONN -#endif - -#ifdef CONFIG_PRODUCT_NAME -#define PRODUCT_NAME CONFIG_PRODUCT_NAME -#else -#define PRODUCT_NAME "HAProxy" -#endif - -/* how many bits are needed to code the size of an int (eg: 32bits -> 5) */ -#define INTBITS 5 - -/* show stats this every millisecond, 0 to disable */ -#ifndef STATTIME -#define STATTIME 2000 -#endif - -/* this reduces the number of calls to select() by choosing appropriate - * sheduler precision in milliseconds. It should be near the minimum - * time that is needed by select() to collect all events. All timeouts - * are rounded up by adding this value prior to pass it to select(). - */ -#define SCHEDULER_RESOLUTION 9 - -#define TIME_ETERNITY -1 -/* returns the lowest delay amongst and , and respects TIME_ETERNITY */ -#define MINTIME(old, new) (((new)<0)?(old):(((old)<0||(new)<(old))?(new):(old))) -#define SETNOW(a) (*a=now) - -/****** string-specific macros and functions ******/ -/* if a > max, then bound to . The macro returns the new */ -#define UBOUND(a, max) ({ typeof(a) b = (max); if ((a) > b) (a) = b; (a); }) - -/* if a < min, then bound to . The macro returns the new */ -#define LBOUND(a, min) ({ typeof(a) b = (min); if ((a) < b) (a) = b; (a); }) - -/* returns 1 only if only zero or one bit is set in X, which means that X is a - * power of 2, and 0 otherwise */ -#define POWEROF2(x) (((x) & ((x)-1)) == 0) -/* - * copies at most chars from to . Last char is always - * set to 0, unless is 0. The number of chars copied is returned - * (excluding the terminating zero). - * This code has been optimized for size and speed : on x86, it's 45 bytes - * long, uses only registers, and consumes only 4 cycles per char. - */ -int strlcpy2(char *dst, const char *src, int size) { - char *orig = dst; - if (size) { - while (--size && (*dst = *src)) { - src++; dst++; - } - *dst = 0; - } - return dst - orig; -} - -/* - * This function simply returns a statically allocated string containing - * the ascii representation for number 'n' in decimal. - */ -char *ultoa(unsigned long n) { - /* enough to store 2^63=18446744073709551615 */ - static char itoa_str[21]; - char *pos; - - pos = itoa_str + sizeof(itoa_str) - 1; - *pos-- = '\0'; - - do { - *pos-- = '0' + n % 10; - n /= 10; - } while (n && pos >= itoa_str); - return pos + 1; -} - -/* - * Returns a pointer to an area of <__len> bytes taken from the pool or - * dynamically allocated. In the first case, <__pool> is updated to point to - * the next element in the list. - */ -#define pool_alloc_from(__pool, __len) ({ \ - void *__p; \ - if ((__p = (__pool)) == NULL) \ - __p = malloc(((__len) >= sizeof (void *)) ? (__len) : sizeof(void *)); \ - else { \ - __pool = *(void **)(__pool); \ - } \ - __p; \ -}) - -/* - * Puts a memory area back to the corresponding pool. - * Items are chained directly through a pointer that - * is written in the beginning of the memory area, so - * there's no need for any carrier cell. This implies - * that each memory area is at least as big as one - * pointer. - */ -#define pool_free_to(__pool, __ptr) ({ \ - *(void **)(__ptr) = (void *)(__pool); \ - __pool = (void *)(__ptr); \ -}) - - -#define MEM_OPTIM -#ifdef MEM_OPTIM -/* - * Returns a pointer to type taken from the - * pool or dynamically allocated. In the - * first case, is updated to point to the - * next element in the list. - */ -#define pool_alloc(type) ({ \ - void *__p; \ - if ((__p = pool_##type) == NULL) \ - __p = malloc(sizeof_##type); \ - else { \ - pool_##type = *(void **)pool_##type; \ - } \ - __p; \ -}) - -/* - * Puts a memory area back to the corresponding pool. - * Items are chained directly through a pointer that - * is written in the beginning of the memory area, so - * there's no need for any carrier cell. This implies - * that each memory area is at least as big as one - * pointer. - */ -#define pool_free(type, ptr) ({ \ - *(void **)ptr = (void *)pool_##type; \ - pool_##type = (void *)ptr; \ -}) - -#else -#define pool_alloc(type) (calloc(1,sizeof_##type)); -#define pool_free(type, ptr) (free(ptr)); -#endif /* MEM_OPTIM */ - -#define sizeof_task sizeof(struct task) -#define sizeof_session sizeof(struct session) -#define sizeof_pendconn sizeof(struct pendconn) -#define sizeof_buffer sizeof(struct buffer) -#define sizeof_fdtab sizeof(struct fdtab) -#define sizeof_requri REQURI_LEN -#define sizeof_capture CAPTURE_LEN -#define sizeof_curappsession CAPTURE_LEN /* current_session pool */ -#define sizeof_appsess sizeof(struct appsessions) - -/* different possible states for the sockets */ -#define FD_STCLOSE 0 -#define FD_STLISTEN 1 -#define FD_STCONN 2 -#define FD_STREADY 3 -#define FD_STERROR 4 - -/* values for task->state */ -#define TASK_IDLE 0 -#define TASK_RUNNING 1 - -/* values for proxy->state */ -#define PR_STNEW 0 -#define PR_STIDLE 1 -#define PR_STRUN 2 -#define PR_STSTOPPED 3 -#define PR_STPAUSED 4 -#define PR_STERROR 5 - -/* values for proxy->mode */ -#define PR_MODE_TCP 0 -#define PR_MODE_HTTP 1 -#define PR_MODE_HEALTH 2 - -/* possible actions for the *poll() loops */ -#define POLL_LOOP_ACTION_INIT 0 -#define POLL_LOOP_ACTION_RUN 1 -#define POLL_LOOP_ACTION_CLEAN 2 - -/* poll mechanisms available */ -#define POLL_USE_SELECT (1<<0) -#define POLL_USE_POLL (1<<1) -#define POLL_USE_EPOLL (1<<2) - -/* bits for proxy->options */ -#define PR_O_REDISP 0x00000001 /* allow reconnection to dispatch in case of errors */ -#define PR_O_TRANSP 0x00000002 /* transparent mode : use original DEST as dispatch */ -#define PR_O_COOK_RW 0x00000004 /* rewrite all direct cookies with the right serverid */ -#define PR_O_COOK_IND 0x00000008 /* keep only indirect cookies */ -#define PR_O_COOK_INS 0x00000010 /* insert cookies when not accessing a server directly */ -#define PR_O_COOK_PFX 0x00000020 /* rewrite all cookies by prefixing the right serverid */ -#define PR_O_COOK_ANY (PR_O_COOK_RW | PR_O_COOK_IND | PR_O_COOK_INS | PR_O_COOK_PFX) -#define PR_O_BALANCE_RR 0x00000040 /* balance in round-robin mode */ -#define PR_O_KEEPALIVE 0x00000080 /* follow keep-alive sessions */ -#define PR_O_FWDFOR 0x00000100 /* insert x-forwarded-for with client address */ -#define PR_O_BIND_SRC 0x00000200 /* bind to a specific source address when connect()ing */ -#define PR_O_NULLNOLOG 0x00000400 /* a connect without request will not be logged */ -#define PR_O_COOK_NOC 0x00000800 /* add a 'Cache-control' header with the cookie */ -#define PR_O_COOK_POST 0x00001000 /* don't insert cookies for requests other than a POST */ -#define PR_O_HTTP_CHK 0x00002000 /* use HTTP 'OPTIONS' method to check server health */ -#define PR_O_PERSIST 0x00004000 /* server persistence stays effective even when server is down */ -#define PR_O_LOGASAP 0x00008000 /* log as soon as possible, without waiting for the session to complete */ -#define PR_O_HTTP_CLOSE 0x00010000 /* force 'connection: close' in both directions */ -#define PR_O_CHK_CACHE 0x00020000 /* require examination of cacheability of the 'set-cookie' field */ -#define PR_O_TCP_CLI_KA 0x00040000 /* enable TCP keep-alive on client-side sessions */ -#define PR_O_TCP_SRV_KA 0x00080000 /* enable TCP keep-alive on server-side sessions */ -#define PR_O_USE_ALL_BK 0x00100000 /* load-balance between backup servers */ -#define PR_O_FORCE_CLO 0x00200000 /* enforce the connection close immediately after server response */ -#define PR_O_BALANCE_SH 0x00400000 /* balance on source IP hash */ -#define PR_O_BALANCE (PR_O_BALANCE_RR | PR_O_BALANCE_SH) -#define PR_O_ABRT_CLOSE 0x00800000 /* immediately abort request when client closes */ - -/* various session flags, bits values 0x01 to 0x20 (shift 0) */ -#define SN_DIRECT 0x00000001 /* connection made on the server matching the client cookie */ -#define SN_CLDENY 0x00000002 /* a client header matches a deny regex */ -#define SN_CLALLOW 0x00000004 /* a client header matches an allow regex */ -#define SN_SVDENY 0x00000008 /* a server header matches a deny regex */ -#define SN_SVALLOW 0x00000010 /* a server header matches an allow regex */ -#define SN_POST 0x00000020 /* the request was an HTTP POST */ - -/* session flags dedicated to cookies : bits values 0x40, 0x80 (0-3 shift 6) */ -#define SN_CK_NONE 0x00000000 /* this session had no cookie */ -#define SN_CK_INVALID 0x00000040 /* this session had a cookie which matches no server */ -#define SN_CK_DOWN 0x00000080 /* this session had cookie matching a down server */ -#define SN_CK_VALID 0x000000C0 /* this session had cookie matching a valid server */ -#define SN_CK_MASK 0x000000C0 /* mask to get this session's cookie flags */ -#define SN_CK_SHIFT 6 /* bit shift */ - -/* session termination conditions, bits values 0x100 to 0x700 (0-7 shift 8) */ -#define SN_ERR_NONE 0x00000000 -#define SN_ERR_CLITO 0x00000100 /* client time-out */ -#define SN_ERR_CLICL 0x00000200 /* client closed (read/write error) */ -#define SN_ERR_SRVTO 0x00000300 /* server time-out, connect time-out */ -#define SN_ERR_SRVCL 0x00000400 /* server closed (connect/read/write error) */ -#define SN_ERR_PRXCOND 0x00000500 /* the proxy decided to close (deny...) */ -#define SN_ERR_RESOURCE 0x00000600 /* the proxy encountered a lack of a local resources (fd, mem, ...) */ -#define SN_ERR_INTERNAL 0x00000700 /* the proxy encountered an internal error */ -#define SN_ERR_MASK 0x00000700 /* mask to get only session error flags */ -#define SN_ERR_SHIFT 8 /* bit shift */ - -/* session state at termination, bits values 0x1000 to 0x7000 (0-7 shift 12) */ -#define SN_FINST_R 0x00001000 /* session ended during client request */ -#define SN_FINST_C 0x00002000 /* session ended during server connect */ -#define SN_FINST_H 0x00003000 /* session ended during server headers */ -#define SN_FINST_D 0x00004000 /* session ended during data phase */ -#define SN_FINST_L 0x00005000 /* session ended while pushing last data to client */ -#define SN_FINST_Q 0x00006000 /* session ended while waiting in queue for a server slot */ -#define SN_FINST_MASK 0x00007000 /* mask to get only final session state flags */ -#define SN_FINST_SHIFT 12 /* bit shift */ - -/* cookie information, bits values 0x10000 to 0x80000 (0-8 shift 16) */ -#define SN_SCK_NONE 0x00000000 /* no set-cookie seen for the server cookie */ -#define SN_SCK_DELETED 0x00010000 /* existing set-cookie deleted or changed */ -#define SN_SCK_INSERTED 0x00020000 /* new set-cookie inserted or changed existing one */ -#define SN_SCK_SEEN 0x00040000 /* set-cookie seen for the server cookie */ -#define SN_SCK_MASK 0x00070000 /* mask to get the set-cookie field */ -#define SN_SCK_ANY 0x00080000 /* at least one set-cookie seen (not to be counted) */ -#define SN_SCK_SHIFT 16 /* bit shift */ - -/* cacheability management, bits values 0x100000 to 0x300000 (0-3 shift 20) */ -#define SN_CACHEABLE 0x00100000 /* at least part of the response is cacheable */ -#define SN_CACHE_COOK 0x00200000 /* a cookie in the response is cacheable */ -#define SN_CACHE_SHIFT 20 /* bit shift */ - -/* various other session flags, bits values 0x400000 and above */ -#define SN_MONITOR 0x00400000 /* this session comes from a monitoring system */ -#define SN_ASSIGNED 0x00800000 /* no need to assign a server to this session */ -#define SN_ADDR_SET 0x01000000 /* this session's server address has been set */ -#define SN_SELF_GEN 0x02000000 /* the proxy generates data for the client (eg: stats) */ - -/* various data sources for the responses */ -#define DATA_SRC_NONE 0 -#define DATA_SRC_STATS 1 - -/* data transmission states for the responses */ -#define DATA_ST_INIT 0 -#define DATA_ST_DATA 1 - -/* different possible states for the client side */ -#define CL_STHEADERS 0 -#define CL_STDATA 1 -#define CL_STSHUTR 2 -#define CL_STSHUTW 3 -#define CL_STCLOSE 4 - -/* different possible states for the server side */ -#define SV_STIDLE 0 -#define SV_STCONN 1 -#define SV_STHEADERS 2 -#define SV_STDATA 3 -#define SV_STSHUTR 4 -#define SV_STSHUTW 5 -#define SV_STCLOSE 6 - -/* result of an I/O event */ -#define RES_SILENT 0 /* didn't happen */ -#define RES_DATA 1 /* data were sent or received */ -#define RES_NULL 2 /* result is 0 (read == 0), or connect without need for writing */ -#define RES_ERROR 3 /* result -1 or error on the socket (eg: connect()) */ - -/* modes of operation (global.mode) */ -#define MODE_DEBUG 1 -#define MODE_STATS 2 -#define MODE_LOG 4 -#define MODE_DAEMON 8 -#define MODE_QUIET 16 -#define MODE_CHECK 32 -#define MODE_VERBOSE 64 -#define MODE_STARTING 128 -#define MODE_FOREGROUND 256 - -/* server flags */ -#define SRV_RUNNING 1 /* the server is UP */ -#define SRV_BACKUP 2 /* this server is a backup server */ -#define SRV_MAPPORTS 4 /* this server uses mapped ports */ -#define SRV_BIND_SRC 8 /* this server uses a specific source address */ -#define SRV_CHECKED 16 /* this server needs to be checked */ - -/* function which act on servers need to return various errors */ -#define SRV_STATUS_OK 0 /* everything is OK. */ -#define SRV_STATUS_INTERNAL 1 /* other unrecoverable errors. */ -#define SRV_STATUS_NOSRV 2 /* no server is available */ -#define SRV_STATUS_FULL 3 /* the/all server(s) are saturated */ -#define SRV_STATUS_QUEUED 4 /* the/all server(s) are saturated but the connection was queued */ - -/* what to do when a header matches a regex */ -#define ACT_ALLOW 0 /* allow the request */ -#define ACT_REPLACE 1 /* replace the matching header */ -#define ACT_REMOVE 2 /* remove the matching header */ -#define ACT_DENY 3 /* deny the request */ -#define ACT_PASS 4 /* pass this header without allowing or denying the request */ - -/* configuration sections */ -#define CFG_NONE 0 -#define CFG_GLOBAL 1 -#define CFG_LISTEN 2 - -/* fields that need to be logged. They appear as flags in session->logs.logwait */ -#define LW_DATE 1 /* date */ -#define LW_CLIP 2 /* CLient IP */ -#define LW_SVIP 4 /* SerVer IP */ -#define LW_SVID 8 /* server ID */ -#define LW_REQ 16 /* http REQuest */ -#define LW_RESP 32 /* http RESPonse */ -#define LW_PXIP 64 /* proxy IP */ -#define LW_PXID 128 /* proxy ID */ -#define LW_BYTES 256 /* bytes read from server */ -#define LW_COOKIE 512 /* captured cookie */ -#define LW_REQHDR 1024 /* request header(s) */ -#define LW_RSPHDR 2048 /* response header(s) */ - -#define ERR_NONE 0 /* no error */ -#define ERR_RETRYABLE 1 /* retryable error, may be cumulated */ -#define ERR_FATAL 2 /* fatal error, may be cumulated */ - -/*********************************************************************/ - -#define LIST_HEAD(a) ((void *)(&(a))) - -/*********************************************************************/ - -/* describes a chunk of string */ -struct chunk { - char *str; /* beginning of the string itself. Might not be 0-terminated */ - int len; /* size of the string from first to last char. <0 = uninit. */ -}; - -struct cap_hdr { - struct cap_hdr *next; - char *name; /* header name, case insensitive */ - int namelen; /* length of the header name, to speed-up lookups */ - int len; /* capture length, not including terminal zero */ - int index; /* index in the output array */ - void *pool; /* pool of pre-allocated memory area of (len+1) bytes */ -}; - -struct hdr_exp { - struct hdr_exp *next; - regex_t *preg; /* expression to look for */ - int action; /* ACT_ALLOW, ACT_REPLACE, ACT_REMOVE, ACT_DENY */ - char *replace; /* expression to set instead */ -}; - -struct buffer { - unsigned int l; /* data length */ - char *r, *w, *h, *lr; /* read ptr, write ptr, last header ptr, last read */ - char *rlim; /* read limit, used for header rewriting */ - unsigned long long total; /* total data read */ - char data[BUFSIZE]; -}; - -struct pendconn { - struct list list; /* chaining ... */ - struct session *sess; /* the session waiting for a connection */ - struct server *srv; /* the server we are waiting for */ -}; - -struct server { - struct server *next; - int state; /* server state (SRV_*) */ - int cklen; /* the len of the cookie, to speed up checks */ - char *cookie; /* the id set in the cookie */ - char *id; /* just for identification */ - struct list pendconns; /* pending connections */ - int nbpend, nbpend_max; /* number of pending connections */ - struct task *queue_mgt; /* the task associated to the queue processing */ - struct sockaddr_in addr; /* the address to connect to */ - struct sockaddr_in source_addr; /* the address to which we want to bind for connect() */ - short check_port; /* the port to use for the health checks */ - int health; /* 0->rise-1 = bad; rise->rise+fall-1 = good */ - int rise, fall; /* time in iterations */ - int inter; /* time in milliseconds */ - int result; /* 0 = connect OK, -1 = connect KO */ - int curfd; /* file desc used for current test, or -1 if not in test */ - unsigned char uweight, eweight; /* user-specified weight-1, and effective weight-1 */ - unsigned int wscore; /* weight score, used during srv map computation */ - int cur_sess, cur_sess_max; /* number of currently active sessions (including syn_sent) */ - unsigned int cum_sess; /* cumulated number of sessions really sent to this server */ - unsigned int maxconn, minconn; /* max # of active sessions (0 = unlimited), min# for dynamic limit. */ - unsigned failed_checks, down_trans; /* failed checks and up-down transitions */ - unsigned failed_conns, failed_resp; /* failed connect() and responses */ - unsigned failed_secu; /* blocked responses because of security concerns */ - struct proxy *proxy; /* the proxy this server belongs to */ -}; - -/* The base for all tasks */ -struct task { - struct task *next, *prev; /* chaining ... */ - struct task *rqnext; /* chaining in run queue ... */ - struct task *wq; /* the wait queue this task is in */ - int state; /* task state : IDLE or RUNNING */ - struct timeval expire; /* next expiration time for this task, use only for fast sorting */ - int (*process)(struct task *t); /* the function which processes the task */ - void *context; /* the task's context */ -}; - -/* WARNING: if new fields are added, they must be initialized in event_accept() */ -struct session { - struct task *task; /* the task associated with this session */ - /* application specific below */ - struct timeval crexpire; /* expiration date for a client read */ - struct timeval cwexpire; /* expiration date for a client write */ - struct timeval srexpire; /* expiration date for a server read */ - struct timeval swexpire; /* expiration date for a server write */ - struct timeval cnexpire; /* expiration date for a connect */ - char res_cr, res_cw, res_sr, res_sw;/* results of some events */ - struct proxy *proxy; /* the proxy this socket belongs to */ - int cli_fd; /* the client side fd */ - int srv_fd; /* the server side fd */ - int cli_state; /* state of the client side */ - int srv_state; /* state of the server side */ - int conn_retries; /* number of connect retries left */ - int flags; /* some flags describing the session */ - struct buffer *req; /* request buffer */ - struct buffer *rep; /* response buffer */ - struct sockaddr_storage cli_addr; /* the client address */ - struct sockaddr_in srv_addr; /* the address to connect to */ - struct server *srv; /* the server being used */ - struct pendconn *pend_pos; /* if not NULL, points to the position in the pending queue */ - char **req_cap; /* array of captured request headers (may be NULL) */ - char **rsp_cap; /* array of captured response headers (may be NULL) */ - struct chunk req_line; /* points to first line */ - struct chunk auth_hdr; /* points to 'Authorization:' header */ - struct { - int logwait; /* log fields waiting to be collected : LW_* */ - struct timeval tv_accept; /* date of the accept() (beginning of the session) */ - long t_request; /* delay before the end of the request arrives, -1 if never occurs */ - long t_queue; /* delay before the session gets out of the connect queue, -1 if never occurs */ - long t_connect; /* delay before the connect() to the server succeeds, -1 if never occurs */ - long t_data; /* delay before the first data byte from the server ... */ - unsigned long t_close; /* total session duration */ - unsigned long srv_queue_size; /* number of sessions waiting for a connect slot on this server at accept() time (in direct assignment) */ - unsigned long prx_queue_size; /* overall number of sessions waiting for a connect slot on this instance at accept() time */ - char *uri; /* first line if log needed, NULL otherwise */ - char *cli_cookie; /* cookie presented by the client, in capture mode */ - char *srv_cookie; /* cookie presented by the server, in capture mode */ - int status; /* HTTP status from the server, negative if from proxy */ - long long bytes; /* number of bytes transferred from the server */ - } logs; - short int data_source; /* where to get the data we generate ourselves */ - short int data_state; /* where to get the data we generate ourselves */ - union { - struct { - struct proxy *px; - struct server *sv; - short px_st, sv_st; /* DATA_ST_INIT or DATA_ST_DATA */ - } stats; - } data_ctx; - unsigned int uniq_id; /* unique ID used for the traces */ -}; - -struct listener { - int fd; /* the listen socket */ - struct sockaddr_storage addr; /* the address we listen to */ - struct listener *next; /* next address or NULL */ -}; - -struct proxy { - struct listener *listen; /* the listen addresses and sockets */ - struct in_addr mon_net, mon_mask; /* don't forward connections from this net (network order) FIXME: should support IPv6 */ - int state; /* proxy state */ - struct sockaddr_in dispatch_addr; /* the default address to connect to */ - struct server *srv; /* known servers */ - int srv_act, srv_bck; /* # of running servers */ - int tot_wact, tot_wbck; /* total weights of active and backup servers */ - struct server **srv_map; /* the server map used to apply weights */ - int srv_map_sz; /* the size of the effective server map */ - int srv_rr_idx; /* next server to be elected in round robin mode */ - char *cookie_name; /* name of the cookie to look for */ - int cookie_len; /* strlen(cookie_name), computed only once */ - char *appsession_name; /* name of the cookie to look for */ - int appsession_name_len; /* strlen(appsession_name), computed only once */ - int appsession_len; /* length of the appsession cookie value to be used */ - int appsession_timeout; - CHTbl htbl_proxy; /* Per Proxy hashtable */ - char *capture_name; /* beginning of the name of the cookie to capture */ - int capture_namelen; /* length of the cookie name to match */ - int capture_len; /* length of the string to be captured */ - struct uri_auth *uri_auth; /* if non-NULL, the (list of) per-URI authentications */ - int clitimeout; /* client I/O timeout (in milliseconds) */ - int srvtimeout; /* server I/O timeout (in milliseconds) */ - int contimeout; /* connect timeout (in milliseconds) */ - char *id; /* proxy id */ - struct list pendconns; /* pending connections with no server assigned yet */ - int nbpend, nbpend_max; /* number of pending connections with no server assigned yet */ - int totpend; /* total number of pending connections on this instance (for stats) */ - unsigned int nbconn, nbconn_max; /* # of active sessions */ - unsigned int cum_conn; /* cumulated number of processed sessions */ - unsigned int maxconn; /* max # of active sessions */ - unsigned failed_conns, failed_resp; /* failed connect() and responses */ - unsigned failed_secu; /* blocked responses because of security concerns */ - int conn_retries; /* maximum number of connect retries */ - int options; /* PR_O_REDISP, PR_O_TRANSP, ... */ - int mode; /* mode = PR_MODE_TCP, PR_MODE_HTTP or PR_MODE_HEALTH */ - struct sockaddr_in source_addr; /* the address to which we want to bind for connect() */ - struct proxy *next; - struct sockaddr_in logsrv1, logsrv2; /* 2 syslog servers */ - signed char logfac1, logfac2; /* log facility for both servers. -1 = disabled */ - int loglev1, loglev2; /* log level for each server, 7 by default */ - int to_log; /* things to be logged (LW_*) */ - struct timeval stop_time; /* date to stop listening, when stopping != 0 */ - int nb_reqadd, nb_rspadd; - struct hdr_exp *req_exp; /* regular expressions for request headers */ - struct hdr_exp *rsp_exp; /* regular expressions for response headers */ - int nb_req_cap, nb_rsp_cap; /* # of headers to be captured */ - struct cap_hdr *req_cap; /* chained list of request headers to be captured */ - struct cap_hdr *rsp_cap; /* chained list of response headers to be captured */ - void *req_cap_pool, *rsp_cap_pool; /* pools of pre-allocated char ** used to build the sessions */ - char *req_add[MAX_NEWHDR], *rsp_add[MAX_NEWHDR]; /* headers to be added */ - int grace; /* grace time after stop request */ - char *check_req; /* HTTP request to use if PR_O_HTTP_CHK is set, else NULL */ - int check_len; /* Length of the HTTP request */ - struct { - char *msg400; /* message for error 400 */ - int len400; /* message length for error 400 */ - char *msg403; /* message for error 403 */ - int len403; /* message length for error 403 */ - char *msg408; /* message for error 408 */ - int len408; /* message length for error 408 */ - char *msg500; /* message for error 500 */ - int len500; /* message length for error 500 */ - char *msg502; /* message for error 502 */ - int len502; /* message length for error 502 */ - char *msg503; /* message for error 503 */ - int len503; /* message length for error 503 */ - char *msg504; /* message for error 504 */ - int len504; /* message length for error 504 */ - } errmsg; -}; - -/* info about one given fd */ -struct fdtab { - int (*read)(int fd); /* read function */ - int (*write)(int fd); /* write function */ - struct task *owner; /* the session (or proxy) associated with this fd */ - int state; /* the state of this fd */ -}; - -/*********************************************************************/ - -int cfg_maxpconn = DEFAULT_MAXCONN; /* # of simultaneous connections per proxy (-N) */ -int cfg_maxconn = 0; /* # of simultaneous connections, (-n) */ -char *cfg_cfgfile = NULL; /* configuration file */ -char *progname = NULL; /* program name */ -int pid; /* current process id */ - -/* global options */ -static struct { - int uid; - int gid; - int nbproc; - int maxconn; - int maxsock; /* max # of sockets */ - int rlimit_nofile; /* default ulimit-n value : 0=unset */ - int rlimit_memmax; /* default ulimit-d in megs value : 0=unset */ - int mode; - char *chroot; - char *pidfile; - int logfac1, logfac2; - int loglev1, loglev2; - struct sockaddr_in logsrv1, logsrv2; -} global = { - logfac1 : -1, - logfac2 : -1, - loglev1 : 7, /* max syslog level : debug */ - loglev2 : 7, - /* others NULL OK */ -}; - -/*********************************************************************/ - -fd_set *StaticReadEvent, - *StaticWriteEvent; - -int cfg_polling_mechanism = 0; /* POLL_USE_{SELECT|POLL|EPOLL} */ - -void **pool_session = NULL, - **pool_pendconn = NULL, - **pool_buffer = NULL, - **pool_fdtab = NULL, - **pool_requri = NULL, - **pool_task = NULL, - **pool_capture = NULL, - **pool_appsess = NULL; - -struct proxy *proxy = NULL; /* list of all existing proxies */ -struct fdtab *fdtab = NULL; /* array of all the file descriptors */ -struct task *rq = NULL; /* global run queue */ -struct task wait_queue[2] = { /* global wait queue */ - { - prev:LIST_HEAD(wait_queue[0]), /* expirable tasks */ - next:LIST_HEAD(wait_queue[0]), - }, - { - prev:LIST_HEAD(wait_queue[1]), /* non-expirable tasks */ - next:LIST_HEAD(wait_queue[1]), - }, -}; - -static int totalconn = 0; /* total # of terminated sessions */ -static int actconn = 0; /* # of active sessions */ -static int maxfd = 0; /* # of the highest fd + 1 */ -static int listeners = 0; /* # of listeners */ -static int stopping = 0; /* non zero means stopping in progress */ -static struct timeval now = {0,0}; /* the current date at any moment */ -static struct timeval start_date; /* the process's start date */ -static struct proxy defproxy; /* fake proxy used to assign default values on all instances */ - -/* Here we store informations about the pids of the processes we may pause - * or kill. We will send them a signal every 10 ms until we can bind to all - * our ports. With 200 retries, that's about 2 seconds. - */ -#define MAX_START_RETRIES 200 -static int nb_oldpids = 0; -static int *oldpids = NULL; -static int oldpids_sig; /* use USR1 or TERM */ - -#if defined(ENABLE_EPOLL) -/* FIXME: this is dirty, but at the moment, there's no other solution to remove - * the old FDs from outside the loop. Perhaps we should export a global 'poll' - * structure with pointers to functions such as init_fd() and close_fd(), plus - * a private structure with several pointers to places such as below. - */ - -static fd_set *PrevReadEvent = NULL, *PrevWriteEvent = NULL; -#endif - -static regmatch_t pmatch[MAX_MATCH]; /* rm_so, rm_eo for regular expressions */ -/* this is used to drain data, and as a temporary buffer for sprintf()... */ -static char trash[BUFSIZE]; - -const int zero = 0; -const int one = 1; - -/* - * Syslog facilities and levels. Conforming to RFC3164. - */ - -#define MAX_SYSLOG_LEN 1024 -#define NB_LOG_FACILITIES 24 -const char *log_facilities[NB_LOG_FACILITIES] = { - "kern", "user", "mail", "daemon", - "auth", "syslog", "lpr", "news", - "uucp", "cron", "auth2", "ftp", - "ntp", "audit", "alert", "cron2", - "local0", "local1", "local2", "local3", - "local4", "local5", "local6", "local7" -}; - - -#define NB_LOG_LEVELS 8 -const char *log_levels[NB_LOG_LEVELS] = { - "emerg", "alert", "crit", "err", - "warning", "notice", "info", "debug" -}; - -#define SYSLOG_PORT 514 - -const char *monthname[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; - -const char sess_term_cond[8] = "-cCsSPRI"; /* normal, CliTo, CliErr, SrvTo, SrvErr, PxErr, Resource, Internal */ -const char sess_fin_state[8] = "-RCHDLQ7"; /* cliRequest, srvConnect, srvHeader, Data, Last, Queue, unknown */ -const char sess_cookie[4] = "NIDV"; /* No cookie, Invalid cookie, cookie for a Down server, Valid cookie */ -const char sess_set_cookie[8] = "N1I3PD5R"; /* No set-cookie, unknown, Set-Cookie Inserted, unknown, - Set-cookie seen and left unchanged (passive), Set-cookie Deleted, - unknown, Set-cookie Rewritten */ - -#define MAX_HOSTNAME_LEN 32 -static char hostname[MAX_HOSTNAME_LEN] = ""; - -const char *HTTP_302 = - "HTTP/1.0 302 Found\r\n" - "Cache-Control: no-cache\r\n" - "Connection: close\r\n" - "Location: "; /* not terminated since it will be concatenated with the URL */ - -/* same as 302 except that the browser MUST retry with the GET method */ -const char *HTTP_303 = - "HTTP/1.0 303 See Other\r\n" - "Cache-Control: no-cache\r\n" - "Connection: close\r\n" - "Location: "; /* not terminated since it will be concatenated with the URL */ - -const char *HTTP_400 = - "HTTP/1.0 400 Bad request\r\n" - "Cache-Control: no-cache\r\n" - "Connection: close\r\n" - "\r\n" - "

400 Bad request

\nYour browser sent an invalid request.\n\n"; - -/* Warning: this one is an sprintf() fmt string, with as its only argument */ -const char *HTTP_401_fmt = - "HTTP/1.0 401 Unauthorized\r\n" - "Cache-Control: no-cache\r\n" - "Connection: close\r\n" - "WWW-Authenticate: Basic realm=\"%s\"\r\n" - "\r\n" - "

401 Unauthorized

\nYou need a valid user and password to access this content.\n\n"; - -const char *HTTP_403 = - "HTTP/1.0 403 Forbidden\r\n" - "Cache-Control: no-cache\r\n" - "Connection: close\r\n" - "\r\n" - "

403 Forbidden

\nRequest forbidden by administrative rules.\n\n"; - -const char *HTTP_408 = - "HTTP/1.0 408 Request Time-out\r\n" - "Cache-Control: no-cache\r\n" - "Connection: close\r\n" - "\r\n" - "

408 Request Time-out

\nYour browser didn't send a complete request in time.\n\n"; - -const char *HTTP_500 = - "HTTP/1.0 500 Server Error\r\n" - "Cache-Control: no-cache\r\n" - "Connection: close\r\n" - "\r\n" - "

500 Server Error

\nAn internal server error occured.\n\n"; - -const char *HTTP_502 = - "HTTP/1.0 502 Bad Gateway\r\n" - "Cache-Control: no-cache\r\n" - "Connection: close\r\n" - "\r\n" - "

502 Bad Gateway

\nThe server returned an invalid or incomplete response.\n\n"; - -const char *HTTP_503 = - "HTTP/1.0 503 Service Unavailable\r\n" - "Cache-Control: no-cache\r\n" - "Connection: close\r\n" - "\r\n" - "

503 Service Unavailable

\nNo server is available to handle this request.\n\n"; - -const char *HTTP_504 = - "HTTP/1.0 504 Gateway Time-out\r\n" - "Cache-Control: no-cache\r\n" - "Connection: close\r\n" - "\r\n" - "

504 Gateway Time-out

\nThe server didn't respond in time.\n\n"; - -/*********************************************************************/ -/* statistics ******************************************************/ -/*********************************************************************/ - -#if STATTIME > 0 -static int stats_tsk_lsrch, stats_tsk_rsrch, - stats_tsk_good, stats_tsk_right, stats_tsk_left, - stats_tsk_new, stats_tsk_nsrch; -#endif - - -/*********************************************************************/ -/* debugging *******************************************************/ -/*********************************************************************/ -#ifdef DEBUG_FULL -static char *cli_stnames[5] = {"HDR", "DAT", "SHR", "SHW", "CLS" }; -static char *srv_stnames[7] = {"IDL", "CON", "HDR", "DAT", "SHR", "SHW", "CLS" }; -#endif - -/*********************************************************************/ -/* function prototypes *********************************************/ -/*********************************************************************/ - -int event_accept(int fd); -int event_cli_read(int fd); -int event_cli_write(int fd); -int event_srv_read(int fd); -int event_srv_write(int fd); -int process_session(struct task *t); - -static int appsession_task_init(void); -static int appsession_init(void); -static int appsession_refresh(struct task *t); - -/*********************************************************************/ -/* general purpose functions ***************************************/ -/*********************************************************************/ - -void display_version() { - printf("HA-Proxy version " HAPROXY_VERSION " " HAPROXY_DATE"\n"); - printf("Copyright 2000-2006 Willy Tarreau \n\n"); -} - -/* - * This function prints the command line usage and exits - */ -void usage(char *name) { - display_version(); - fprintf(stderr, - "Usage : %s -f [ -vdV" -#if STATTIME > 0 - "sl" -#endif - "D ] [ -n ] [ -N ]\n" - " [ -p ] [ -m ]\n" - " -v displays version\n" - " -d enters debug mode ; -db only disables background mode.\n" - " -V enters verbose mode (disables quiet mode)\n" -#if STATTIME > 0 - " -s enables statistics output\n" - " -l enables long statistics format\n" -#endif - " -D goes daemon ; implies -q\n" - " -q quiet mode : don't display messages\n" - " -c check mode : only check config file and exit\n" - " -n sets the maximum total # of connections (%d)\n" - " -m limits the usable amount of memory (in MB)\n" - " -N sets the default, per-proxy maximum # of connections (%d)\n" - " -p writes pids of all children to this file\n" -#if defined(ENABLE_EPOLL) - " -de disables epoll() usage even when available\n" -#endif -#if defined(ENABLE_POLL) - " -dp disables poll() usage even when available\n" -#endif - " -sf/-st [pid ]* finishes/terminates old pids. Must be last arguments.\n" - "\n", - name, DEFAULT_MAXCONN, cfg_maxpconn); - exit(1); -} - - -/* - * Displays the message on stderr with the date and pid. Overrides the quiet - * mode during startup. - */ -void Alert(char *fmt, ...) { - va_list argp; - struct timeval tv; - struct tm *tm; - - if (!(global.mode & MODE_QUIET) || (global.mode & (MODE_VERBOSE | MODE_STARTING))) { - va_start(argp, fmt); - - gettimeofday(&tv, NULL); - tm=localtime(&tv.tv_sec); - fprintf(stderr, "[ALERT] %03d/%02d%02d%02d (%d) : ", - tm->tm_yday, tm->tm_hour, tm->tm_min, tm->tm_sec, (int)getpid()); - vfprintf(stderr, fmt, argp); - fflush(stderr); - va_end(argp); - } -} - - -/* - * Displays the message on stderr with the date and pid. - */ -void Warning(char *fmt, ...) { - va_list argp; - struct timeval tv; - struct tm *tm; - - if (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE)) { - va_start(argp, fmt); - - gettimeofday(&tv, NULL); - tm=localtime(&tv.tv_sec); - fprintf(stderr, "[WARNING] %03d/%02d%02d%02d (%d) : ", - tm->tm_yday, tm->tm_hour, tm->tm_min, tm->tm_sec, (int)getpid()); - vfprintf(stderr, fmt, argp); - fflush(stderr); - va_end(argp); - } -} - -/* - * Displays the message on only if quiet mode is not set. - */ -void qfprintf(FILE *out, char *fmt, ...) { - va_list argp; - - if (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE)) { - va_start(argp, fmt); - vfprintf(out, fmt, argp); - fflush(out); - va_end(argp); - } -} - - -/* - * converts to a struct sockaddr_in* which is locally allocated. - * The format is "addr:port", where "addr" can be empty or "*" to indicate - * INADDR_ANY. - */ -struct sockaddr_in *str2sa(char *str) { - static struct sockaddr_in sa; - char *c; - int port; - - memset(&sa, 0, sizeof(sa)); - str=strdup(str); - - if ((c=strrchr(str,':')) != NULL) { - *c++=0; - port=atol(c); - } - else - port=0; - - if (*str == '*' || *str == '\0') { /* INADDR_ANY */ - sa.sin_addr.s_addr = INADDR_ANY; - } - else if (!inet_pton(AF_INET, str, &sa.sin_addr)) { - struct hostent *he; - - if ((he = gethostbyname(str)) == NULL) { - Alert("Invalid server name: '%s'\n", str); - } - else - sa.sin_addr = *(struct in_addr *) *(he->h_addr_list); - } - sa.sin_port=htons(port); - sa.sin_family=AF_INET; - - free(str); - return &sa; -} - -/* - * converts to a two struct in_addr* which are locally allocated. - * The format is "addr[/mask]", where "addr" cannot be empty, and mask - * is optionnal and either in the dotted or CIDR notation. - * Note: "addr" can also be a hostname. Returns 1 if OK, 0 if error. - */ -int str2net(char *str, struct in_addr *addr, struct in_addr *mask) { - char *c; - unsigned long len; - - memset(mask, 0, sizeof(*mask)); - memset(addr, 0, sizeof(*addr)); - str=strdup(str); - - if ((c = strrchr(str, '/')) != NULL) { - *c++ = 0; - /* c points to the mask */ - if (strchr(c, '.') != NULL) { /* dotted notation */ - if (!inet_pton(AF_INET, c, mask)) - return 0; - } - else { /* mask length */ - char *err; - len = strtol(c, &err, 10); - if (!*c || (err && *err) || (unsigned)len > 32) - return 0; - if (len) - mask->s_addr = htonl(0xFFFFFFFFUL << (32 - len)); - else - mask->s_addr = 0; - } - } - else { - mask->s_addr = 0xFFFFFFFF; - } - if (!inet_pton(AF_INET, str, addr)) { - struct hostent *he; - - if ((he = gethostbyname(str)) == NULL) { - return 0; - } - else - *addr = *(struct in_addr *) *(he->h_addr_list); - } - free(str); - return 1; -} - - -/* - * converts to a list of listeners which are dynamically allocated. - * The format is "{addr|'*'}:port[-end][,{addr|'*'}:port[-end]]*", where : - * - can be empty or "*" to indicate INADDR_ANY ; - * - is a numerical port from 1 to 65535 ; - * - indicates to use the range from to instead (inclusive). - * This can be repeated as many times as necessary, separated by a coma. - * The argument is a pointer to a current list which should be appended - * to the tail of the new list. The pointer to the new list is returned. - */ -struct listener *str2listener(char *str, struct listener *tail) { - struct listener *l; - char *c, *next, *range, *dupstr; - int port, end; - - next = dupstr = strdup(str); - - while (next && *next) { - struct sockaddr_storage ss; - - str = next; - /* 1) look for the end of the first address */ - if ((next = strrchr(str, ',')) != NULL) { - *next++ = 0; - } - - /* 2) look for the addr/port delimiter, it's the last colon. */ - if ((range = strrchr(str, ':')) == NULL) { - Alert("Missing port number: '%s'\n", str); - goto fail; - } - - *range++ = 0; - - if (strrchr(str, ':') != NULL) { - /* IPv6 address contains ':' */ - memset(&ss, 0, sizeof(ss)); - ss.ss_family = AF_INET6; - - if (!inet_pton(ss.ss_family, str, &((struct sockaddr_in6 *)&ss)->sin6_addr)) { - Alert("Invalid server address: '%s'\n", str); - goto fail; - } - } - else { - memset(&ss, 0, sizeof(ss)); - ss.ss_family = AF_INET; - - if (*str == '*' || *str == '\0') { /* INADDR_ANY */ - ((struct sockaddr_in *)&ss)->sin_addr.s_addr = INADDR_ANY; - } - else if (!inet_pton(ss.ss_family, str, &((struct sockaddr_in *)&ss)->sin_addr)) { - struct hostent *he; - - if ((he = gethostbyname(str)) == NULL) { - Alert("Invalid server name: '%s'\n", str); - goto fail; - } - else - ((struct sockaddr_in *)&ss)->sin_addr = - *(struct in_addr *) *(he->h_addr_list); - } - } - - /* 3) look for the port-end delimiter */ - if ((c = strchr(range, '-')) != NULL) { - *c++ = 0; - end = atol(c); - } - else { - end = atol(range); - } - - port = atol(range); - - if (port < 1 || port > 65535) { - Alert("Invalid port '%d' specified for address '%s'.\n", port, str); - goto fail; - } - - if (end < 1 || end > 65535) { - Alert("Invalid port '%d' specified for address '%s'.\n", end, str); - goto fail; - } - - for (; port <= end; port++) { - l = (struct listener *)calloc(1, sizeof(struct listener)); - l->next = tail; - tail = l; - - l->fd = -1; - l->addr = ss; - if (ss.ss_family == AF_INET6) - ((struct sockaddr_in6 *)(&l->addr))->sin6_port = htons(port); - else - ((struct sockaddr_in *)(&l->addr))->sin_port = htons(port); - - } /* end for(port) */ - } /* end while(next) */ - free(dupstr); - return tail; - fail: - free(dupstr); - return NULL; -} - - -#define FD_SETS_ARE_BITFIELDS -#ifdef FD_SETS_ARE_BITFIELDS -/* - * This map is used with all the FD_* macros to check whether a particular bit - * is set or not. Each bit represents an ACSII code. FD_SET() sets those bytes - * which should be encoded. When FD_ISSET() returns non-zero, it means that the - * byte should be encoded. Be careful to always pass bytes from 0 to 255 - * exclusively to the macros. - */ -fd_set hdr_encode_map[(sizeof(fd_set) > (256/8)) ? 1 : ((256/8) / sizeof(fd_set))]; -fd_set url_encode_map[(sizeof(fd_set) > (256/8)) ? 1 : ((256/8) / sizeof(fd_set))]; - -#else -#error "Check if your OS uses bitfields for fd_sets" -#endif - -/* will try to encode the string replacing all characters tagged in - * with the hexadecimal representation of their ASCII-code (2 digits) - * prefixed by , and will store the result between (included - *) and (excluded), and will always terminate the string with a '\0' - * before . The position of the '\0' is returned if the conversion - * completes. If bytes are missing between and , then the - * conversion will be incomplete and truncated. If <= , the '\0' - * cannot even be stored so we return without writing the 0. - * The input string must also be zero-terminated. - */ -char hextab[16] = "0123456789ABCDEF"; -char *encode_string(char *start, char *stop, - const char escape, const fd_set *map, - const char *string) -{ - if (start < stop) { - stop--; /* reserve one byte for the final '\0' */ - while (start < stop && *string != 0) { - if (!FD_ISSET((unsigned char)(*string), map)) - *start++ = *string; - else { - if (start + 3 >= stop) - break; - *start++ = escape; - *start++ = hextab[(*string >> 4) & 15]; - *start++ = hextab[*string & 15]; - } - string++; - } - *start = '\0'; - } - return start; -} - -/* - * This function sends a syslog message to both log servers of a proxy, - * or to global log servers if the proxy is NULL. - * It also tries not to waste too much time computing the message header. - * It doesn't care about errors nor does it report them. - */ -void send_log(struct proxy *p, int level, char *message, ...) { - static int logfd = -1; /* syslog UDP socket */ - static long tvsec = -1; /* to force the string to be initialized */ - struct timeval tv; - va_list argp; - static char logmsg[MAX_SYSLOG_LEN]; - static char *dataptr = NULL; - int fac_level; - int hdr_len, data_len; - struct sockaddr_in *sa[2]; - int facilities[2], loglevel[2]; - int nbloggers = 0; - char *log_ptr; - - if (logfd < 0) { - if ((logfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) - return; - } - - if (level < 0 || progname == NULL || message == NULL) - return; - - gettimeofday(&tv, NULL); - if (tv.tv_sec != tvsec || dataptr == NULL) { - /* this string is rebuild only once a second */ - struct tm *tm = localtime(&tv.tv_sec); - tvsec = tv.tv_sec; - - hdr_len = snprintf(logmsg, sizeof(logmsg), - "<<<<>%s %2d %02d:%02d:%02d %s[%d]: ", - monthname[tm->tm_mon], - tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, - progname, pid); - /* WARNING: depending upon implementations, snprintf may return - * either -1 or the number of bytes that would be needed to store - * the total message. In both cases, we must adjust it. - */ - if (hdr_len < 0 || hdr_len > sizeof(logmsg)) - hdr_len = sizeof(logmsg); - - dataptr = logmsg + hdr_len; - } - - va_start(argp, message); - data_len = vsnprintf(dataptr, logmsg + sizeof(logmsg) - dataptr, message, argp); - if (data_len < 0 || data_len > (logmsg + sizeof(logmsg) - dataptr)) - data_len = logmsg + sizeof(logmsg) - dataptr; - va_end(argp); - dataptr[data_len - 1] = '\n'; /* force a break on ultra-long lines */ - - if (p == NULL) { - if (global.logfac1 >= 0) { - sa[nbloggers] = &global.logsrv1; - facilities[nbloggers] = global.logfac1; - loglevel[nbloggers] = global.loglev1; - nbloggers++; - } - if (global.logfac2 >= 0) { - sa[nbloggers] = &global.logsrv2; - facilities[nbloggers] = global.logfac2; - loglevel[nbloggers] = global.loglev2; - nbloggers++; - } - } else { - if (p->logfac1 >= 0) { - sa[nbloggers] = &p->logsrv1; - facilities[nbloggers] = p->logfac1; - loglevel[nbloggers] = p->loglev1; - nbloggers++; - } - if (p->logfac2 >= 0) { - sa[nbloggers] = &p->logsrv2; - facilities[nbloggers] = p->logfac2; - loglevel[nbloggers] = p->loglev2; - nbloggers++; - } - } - - while (nbloggers-- > 0) { - /* we can filter the level of the messages that are sent to each logger */ - if (level > loglevel[nbloggers]) - continue; - - /* For each target, we may have a different facility. - * We can also have a different log level for each message. - * This induces variations in the message header length. - * Since we don't want to recompute it each time, nor copy it every - * time, we only change the facility in the pre-computed header, - * and we change the pointer to the header accordingly. - */ - fac_level = (facilities[nbloggers] << 3) + level; - log_ptr = logmsg + 3; /* last digit of the log level */ - do { - *log_ptr = '0' + fac_level % 10; - fac_level /= 10; - log_ptr--; - } while (fac_level && log_ptr > logmsg); - *log_ptr = '<'; - - /* the total syslog message now starts at logptr, for dataptr+data_len-logptr */ - -#ifndef MSG_NOSIGNAL - sendto(logfd, log_ptr, dataptr + data_len - log_ptr, MSG_DONTWAIT, - (struct sockaddr *)sa[nbloggers], sizeof(**sa)); -#else - sendto(logfd, log_ptr, dataptr + data_len - log_ptr, MSG_DONTWAIT | MSG_NOSIGNAL, - (struct sockaddr *)sa[nbloggers], sizeof(**sa)); -#endif - } -} - - -/* sets to the current time */ -static inline struct timeval *tv_now(struct timeval *tv) { - if (tv) - gettimeofday(tv, NULL); - return tv; -} - -/* - * adds ms to , set the result to and returns a pointer - */ -static struct timeval *tv_delayfrom(struct timeval *tv, struct timeval *from, int ms) { - if (!tv || !from) - return NULL; - tv->tv_usec = from->tv_usec + (ms%1000)*1000; - tv->tv_sec = from->tv_sec + (ms/1000); - while (tv->tv_usec >= 1000000) { - tv->tv_usec -= 1000000; - tv->tv_sec++; - } - return tv; -} - -/* - * compares and : returns 0 if equal, -1 if tv1 < tv2, 1 if tv1 > tv2 - * Must not be used when either argument is eternity. Use tv_cmp2() for that. - */ -static inline int tv_cmp(struct timeval *tv1, struct timeval *tv2) { - if (tv1->tv_sec < tv2->tv_sec) - return -1; - else if (tv1->tv_sec > tv2->tv_sec) - return 1; - else if (tv1->tv_usec < tv2->tv_usec) - return -1; - else if (tv1->tv_usec > tv2->tv_usec) - return 1; - else - return 0; -} - -/* - * returns the absolute difference, in ms, between tv1 and tv2 - * Must not be used when either argument is eternity. - */ -unsigned long tv_delta(struct timeval *tv1, struct timeval *tv2) { - int cmp; - unsigned long ret; - - - cmp = tv_cmp(tv1, tv2); - if (!cmp) - return 0; /* same dates, null diff */ - else if (cmp < 0) { - struct timeval *tmp = tv1; - tv1 = tv2; - tv2 = tmp; - } - ret = (tv1->tv_sec - tv2->tv_sec) * 1000; - if (tv1->tv_usec > tv2->tv_usec) - ret += (tv1->tv_usec - tv2->tv_usec) / 1000; - else - ret -= (tv2->tv_usec - tv1->tv_usec) / 1000; - return (unsigned long) ret; -} - -/* - * returns the difference, in ms, between tv1 and tv2 - * Must not be used when either argument is eternity. - */ -static inline unsigned long tv_diff(struct timeval *tv1, struct timeval *tv2) { - unsigned long ret; - - ret = (tv2->tv_sec - tv1->tv_sec) * 1000; - if (tv2->tv_usec > tv1->tv_usec) - ret += (tv2->tv_usec - tv1->tv_usec) / 1000; - else - ret -= (tv1->tv_usec - tv2->tv_usec) / 1000; - return (unsigned long) ret; -} - -/* - * compares and modulo 1ms: returns 0 if equal, -1 if tv1 < tv2, 1 if tv1 > tv2 - * Must not be used when either argument is eternity. Use tv_cmp2_ms() for that. - */ -static int tv_cmp_ms(struct timeval *tv1, struct timeval *tv2) { - if (tv1->tv_sec == tv2->tv_sec) { - if (tv2->tv_usec >= tv1->tv_usec + 1000) - return -1; - else if (tv1->tv_usec >= tv2->tv_usec + 1000) - return 1; - else - return 0; - } - else if ((tv2->tv_sec > tv1->tv_sec + 1) || - ((tv2->tv_sec == tv1->tv_sec + 1) && (tv2->tv_usec + 1000000 >= tv1->tv_usec + 1000))) - return -1; - else if ((tv1->tv_sec > tv2->tv_sec + 1) || - ((tv1->tv_sec == tv2->tv_sec + 1) && (tv1->tv_usec + 1000000 >= tv2->tv_usec + 1000))) - return 1; - else - return 0; -} - -/* - * returns the remaining time between tv1=now and event=tv2 - * if tv2 is passed, 0 is returned. - * Must not be used when either argument is eternity. - */ -static inline unsigned long tv_remain(struct timeval *tv1, struct timeval *tv2) { - unsigned long ret; - - if (tv_cmp_ms(tv1, tv2) >= 0) - return 0; /* event elapsed */ - - ret = (tv2->tv_sec - tv1->tv_sec) * 1000; - if (tv2->tv_usec > tv1->tv_usec) - ret += (tv2->tv_usec - tv1->tv_usec) / 1000; - else - ret -= (tv1->tv_usec - tv2->tv_usec) / 1000; - return (unsigned long) ret; -} - - -/* - * zeroes a struct timeval - */ - -static inline struct timeval *tv_eternity(struct timeval *tv) { - tv->tv_sec = tv->tv_usec = 0; - return tv; -} - -/* - * returns 1 if tv is null, else 0 - */ -static inline int tv_iseternity(struct timeval *tv) { - if (tv->tv_sec == 0 && tv->tv_usec == 0) - return 1; - else - return 0; -} - -/* - * compares and : returns 0 if equal, -1 if tv1 < tv2, 1 if tv1 > tv2, - * considering that 0 is the eternity. - */ -static int tv_cmp2(struct timeval *tv1, struct timeval *tv2) { - if (tv_iseternity(tv1)) - if (tv_iseternity(tv2)) - return 0; /* same */ - else - return 1; /* tv1 later than tv2 */ - else if (tv_iseternity(tv2)) - return -1; /* tv2 later than tv1 */ - - if (tv1->tv_sec > tv2->tv_sec) - return 1; - else if (tv1->tv_sec < tv2->tv_sec) - return -1; - else if (tv1->tv_usec > tv2->tv_usec) - return 1; - else if (tv1->tv_usec < tv2->tv_usec) - return -1; - else - return 0; -} - -/* - * compares and modulo 1 ms: returns 0 if equal, -1 if tv1 < tv2, 1 if tv1 > tv2, - * considering that 0 is the eternity. - */ -static int tv_cmp2_ms(struct timeval *tv1, struct timeval *tv2) { - if (tv_iseternity(tv1)) - if (tv_iseternity(tv2)) - return 0; /* same */ - else - return 1; /* tv1 later than tv2 */ - else if (tv_iseternity(tv2)) - return -1; /* tv2 later than tv1 */ - - if (tv1->tv_sec == tv2->tv_sec) { - if (tv1->tv_usec >= tv2->tv_usec + 1000) - return 1; - else if (tv2->tv_usec >= tv1->tv_usec + 1000) - return -1; - else - return 0; - } - else if ((tv1->tv_sec > tv2->tv_sec + 1) || - ((tv1->tv_sec == tv2->tv_sec + 1) && (tv1->tv_usec + 1000000 >= tv2->tv_usec + 1000))) - return 1; - else if ((tv2->tv_sec > tv1->tv_sec + 1) || - ((tv2->tv_sec == tv1->tv_sec + 1) && (tv2->tv_usec + 1000000 >= tv1->tv_usec + 1000))) - return -1; - else - return 0; -} - -/* - * returns the remaining time between tv1=now and event=tv2 - * if tv2 is passed, 0 is returned. - * Returns TIME_ETERNITY if tv2 is eternity. - */ -static unsigned long tv_remain2(struct timeval *tv1, struct timeval *tv2) { - unsigned long ret; - - if (tv_iseternity(tv2)) - return TIME_ETERNITY; - - if (tv_cmp_ms(tv1, tv2) >= 0) - return 0; /* event elapsed */ - - ret = (tv2->tv_sec - tv1->tv_sec) * 1000; - if (tv2->tv_usec > tv1->tv_usec) - ret += (tv2->tv_usec - tv1->tv_usec) / 1000; - else - ret -= (tv1->tv_usec - tv2->tv_usec) / 1000; - return (unsigned long) ret; -} - -/* - * returns the first event between tv1 and tv2 into tvmin. - * a zero tv is ignored. tvmin is returned. - */ -static inline struct timeval *tv_min(struct timeval *tvmin, - struct timeval *tv1, struct timeval *tv2) { - - if (tv_cmp2(tv1, tv2) <= 0) - *tvmin = *tv1; - else - *tvmin = *tv2; - - return tvmin; -} - - - -/***********************************************************/ -/* fd management ***************************************/ -/***********************************************************/ - - - -/* Deletes an FD from the fdsets, and recomputes the maxfd limit. - * The file descriptor is also closed. - */ -static void fd_delete(int fd) { - FD_CLR(fd, StaticReadEvent); - FD_CLR(fd, StaticWriteEvent); -#if defined(ENABLE_EPOLL) - if (PrevReadEvent) { - FD_CLR(fd, PrevReadEvent); - FD_CLR(fd, PrevWriteEvent); - } -#endif - - close(fd); - fdtab[fd].state = FD_STCLOSE; - - while ((maxfd-1 >= 0) && (fdtab[maxfd-1].state == FD_STCLOSE)) - maxfd--; -} - -/* recomputes the maxfd limit from the fd */ -static inline void fd_insert(int fd) { - if (fd+1 > maxfd) - maxfd = fd+1; -} - -/*************************************************************/ -/* task management ***************************************/ -/*************************************************************/ - -/* puts the task in run queue , and returns */ -static inline struct task *task_wakeup(struct task **q, struct task *t) { - if (t->state == TASK_RUNNING) - return t; - else { - t->rqnext = *q; - t->state = TASK_RUNNING; - return *q = t; - } -} - -/* removes the task from the queue - * MUST be 's first task. - * set the run queue to point to the next one, and return it - */ -static inline struct task *task_sleep(struct task **q, struct task *t) { - if (t->state == TASK_RUNNING) { - *q = t->rqnext; - t->state = TASK_IDLE; /* tell that s has left the run queue */ - } - return *q; /* return next running task */ -} - -/* - * removes the task from its wait queue. It must have already been removed - * from the run queue. A pointer to the task itself is returned. - */ -static inline struct task *task_delete(struct task *t) { - t->prev->next = t->next; - t->next->prev = t->prev; - return t; -} - -/* - * frees a task. Its context must have been freed since it will be lost. - */ -static inline void task_free(struct task *t) { - pool_free(task, t); -} - -/* inserts into its assigned wait queue, where it may already be. In this case, it - * may be only moved or left where it was, depending on its timing requirements. - * is returned. - */ -struct task *task_queue(struct task *task) { - struct task *list = task->wq; - struct task *start_from; - - /* This is a very dirty hack to queue non-expirable tasks in another queue - * in order to avoid pulluting the tail of the standard queue. This will go - * away with the new O(log(n)) scheduler anyway. - */ - if (tv_iseternity(&task->expire)) { - /* if the task was queued in the standard wait queue, we must dequeue it */ - if (task->prev) { - if (task->wq == LIST_HEAD(wait_queue[1])) - return task; - else { - task_delete(task); - task->prev = NULL; - } - } - list = task->wq = LIST_HEAD(wait_queue[1]); - } else { - /* if the task was queued in the eternity queue, we must dequeue it */ - if (task->prev && (task->wq == LIST_HEAD(wait_queue[1]))) { - task_delete(task); - task->prev = NULL; - list = task->wq = LIST_HEAD(wait_queue[0]); - } - } - - /* next, test if the task was already in a list */ - if (task->prev == NULL) { - // start_from = list; - start_from = list->prev; -#if STATTIME > 0 - stats_tsk_new++; -#endif - /* insert the unlinked into the list, searching back from the last entry */ - while (start_from != list && tv_cmp2(&task->expire, &start_from->expire) < 0) { - start_from = start_from->prev; -#if STATTIME > 0 - stats_tsk_nsrch++; -#endif - } - - // while (start_from->next != list && tv_cmp2(&task->expire, &start_from->next->expire) > 0) { - // start_from = start_from->next; - // stats_tsk_nsrch++; - // } - } - else if (task->prev == list || - tv_cmp2(&task->expire, &task->prev->expire) >= 0) { /* walk right */ - start_from = task->next; - if (start_from == list || tv_cmp2(&task->expire, &start_from->expire) <= 0) { -#if STATTIME > 0 - stats_tsk_good++; -#endif - return task; /* it's already in the right place */ - } - -#if STATTIME > 0 - stats_tsk_right++; -#endif - - /* if the task is not at the right place, there's little chance that - * it has only shifted a bit, and it will nearly always be queued - * at the end of the list because of constant timeouts - * (observed in real case). - */ -#ifndef WE_REALLY_THINK_THAT_THIS_TASK_MAY_HAVE_SHIFTED - start_from = list->prev; /* assume we'll queue to the end of the list */ - while (start_from != list && tv_cmp2(&task->expire, &start_from->expire) < 0) { - start_from = start_from->prev; -#if STATTIME > 0 - stats_tsk_lsrch++; -#endif - } -#else /* WE_REALLY_... */ - /* insert the unlinked into the list, searching after position */ - while (start_from->next != list && tv_cmp2(&task->expire, &start_from->next->expire) > 0) { - start_from = start_from->next; -#if STATTIME > 0 - stats_tsk_rsrch++; -#endif - } -#endif /* WE_REALLY_... */ - - /* we need to unlink it now */ - task_delete(task); - } - else { /* walk left. */ -#if STATTIME > 0 - stats_tsk_left++; -#endif -#ifdef LEFT_TO_TOP /* not very good */ - start_from = list; - while (start_from->next != list && tv_cmp2(&task->expire, &start_from->next->expire) > 0) { - start_from = start_from->next; -#if STATTIME > 0 - stats_tsk_lsrch++; -#endif - } -#else - start_from = task->prev->prev; /* valid because of the previous test above */ - while (start_from != list && tv_cmp2(&task->expire, &start_from->expire) < 0) { - start_from = start_from->prev; -#if STATTIME > 0 - stats_tsk_lsrch++; -#endif - } -#endif - /* we need to unlink it now */ - task_delete(task); - } - task->prev = start_from; - task->next = start_from->next; - task->next->prev = task; - start_from->next = task; - return task; -} - - -/*********************************************************************/ -/* pending connections queues **************************************/ -/*********************************************************************/ - -/* - * Detaches pending connection

, decreases the pending count, and frees - * the pending connection. The connection might have been queued to a specific - * server as well as to the proxy. The session also gets marked unqueued. - */ -static void pendconn_free(struct pendconn *p) { - LIST_DEL(&p->list); - p->sess->pend_pos = NULL; - if (p->srv) - p->srv->nbpend--; - else - p->sess->proxy->nbpend--; - p->sess->proxy->totpend--; - pool_free(pendconn, p); -} - -/* Returns the first pending connection for server , which may be NULL if - * nothing is pending. - */ -static inline struct pendconn *pendconn_from_srv(struct server *s) { - if (!s->nbpend) - return NULL; - - return LIST_ELEM(s->pendconns.n, struct pendconn *, list); -} - -/* Returns the first pending connection for proxy , which may be NULL if - * nothing is pending. - */ -static inline struct pendconn *pendconn_from_px(struct proxy *px) { - if (!px->nbpend) - return NULL; - - return LIST_ELEM(px->pendconns.n, struct pendconn *, list); -} - -/* Detaches the next pending connection from either a server or a proxy, and - * returns its associated session. If no pending connection is found, NULL is - * returned. Note that neither nor can be NULL. - */ -static struct session *pendconn_get_next_sess(struct server *srv, struct proxy *px) { - struct pendconn *p; - struct session *sess; - - p = pendconn_from_srv(srv); - if (!p) { - p = pendconn_from_px(px); - if (!p) - return NULL; - p->sess->srv = srv; - } - sess = p->sess; - pendconn_free(p); - return sess; -} - -/* Adds the session to the pending connection list of server ->srv - * or to the one of ->proxy if srv is NULL. All counters and back pointers - * are updated accordingly. Returns NULL if no memory is available, otherwise the - * pendconn itself. - */ -static struct pendconn *pendconn_add(struct session *sess) { - struct pendconn *p; - - p = pool_alloc(pendconn); - if (!p) - return NULL; - - sess->pend_pos = p; - p->sess = sess; - p->srv = sess->srv; - if (sess->srv) { - LIST_ADDQ(&sess->srv->pendconns, &p->list); - sess->logs.srv_queue_size += sess->srv->nbpend; - sess->srv->nbpend++; - if (sess->srv->nbpend > sess->srv->nbpend_max) - sess->srv->nbpend_max = sess->srv->nbpend; - } else { - LIST_ADDQ(&sess->proxy->pendconns, &p->list); - sess->logs.prx_queue_size += sess->proxy->nbpend; - sess->proxy->nbpend++; - if (sess->proxy->nbpend > sess->proxy->nbpend_max) - sess->proxy->nbpend_max = sess->proxy->nbpend; - } - sess->proxy->totpend++; - return p; -} - -/* returns the effective dynamic maxconn for a server, considering the minconn - * and the proxy's usage relative to its saturation. - */ -static unsigned int srv_dynamic_maxconn(struct server *s) { - return s->minconn ? - ((s->maxconn * s->proxy->nbconn / s->proxy->maxconn) < s->minconn) ? s->minconn : - (s->maxconn * s->proxy->nbconn / s->proxy->maxconn) : s->maxconn; -} - -/* returns 0 if nothing has to be done for server regarding queued connections, - * and non-zero otherwise. Suited for and if/else usage. - */ -static inline int may_dequeue_tasks(struct server *s, struct proxy *p) { - return (s && (s->nbpend || p->nbpend) && - (!s->maxconn || s->cur_sess < srv_dynamic_maxconn(s)) && - s->queue_mgt); -} - - - -/*********************************************************************/ -/* more specific functions ***************************************/ -/*********************************************************************/ - -/* some prototypes */ -static int maintain_proxies(void); - -/* This either returns the sockname or the original destination address. Code - * inspired from Patrick Schaaf's example of nf_getsockname() implementation. - */ -static int get_original_dst(int fd, struct sockaddr_in *sa, socklen_t *salen) { -#if defined(TPROXY) && defined(SO_ORIGINAL_DST) - return getsockopt(fd, SOL_IP, SO_ORIGINAL_DST, (void *)sa, salen); -#else -#if defined(TPROXY) && defined(USE_GETSOCKNAME) - return getsockname(fd, (struct sockaddr *)sa, salen); -#else - return -1; -#endif -#endif -} - -/* - * frees the context associated to a session. It must have been removed first. - */ -static void session_free(struct session *s) { - if (s->pend_pos) - pendconn_free(s->pend_pos); - if (s->req) - pool_free(buffer, s->req); - if (s->rep) - pool_free(buffer, s->rep); - - if (s->rsp_cap != NULL) { - struct cap_hdr *h; - for (h = s->proxy->rsp_cap; h; h = h->next) { - if (s->rsp_cap[h->index] != NULL) - pool_free_to(h->pool, s->rsp_cap[h->index]); - } - pool_free_to(s->proxy->rsp_cap_pool, s->rsp_cap); - } - if (s->req_cap != NULL) { - struct cap_hdr *h; - for (h = s->proxy->req_cap; h; h = h->next) { - if (s->req_cap[h->index] != NULL) - pool_free_to(h->pool, s->req_cap[h->index]); - } - pool_free_to(s->proxy->req_cap_pool, s->req_cap); - } - - if (s->logs.uri) - pool_free(requri, s->logs.uri); - if (s->logs.cli_cookie) - pool_free(capture, s->logs.cli_cookie); - if (s->logs.srv_cookie) - pool_free(capture, s->logs.srv_cookie); - - pool_free(session, s); -} - - -/* - * This function recounts the number of usable active and backup servers for - * proxy

. These numbers are returned into the p->srv_act and p->srv_bck. - * This function also recomputes the total active and backup weights. - */ -static void recount_servers(struct proxy *px) { - struct server *srv; - - px->srv_act = 0; px->srv_bck = px->tot_wact = px->tot_wbck = 0; - for (srv = px->srv; srv != NULL; srv = srv->next) { - if (srv->state & SRV_RUNNING) { - if (srv->state & SRV_BACKUP) { - px->srv_bck++; - px->tot_wbck += srv->eweight + 1; - } else { - px->srv_act++; - px->tot_wact += srv->eweight + 1; - } - } - } -} - -/* This function recomputes the server map for proxy px. It - * relies on px->tot_wact and px->tot_wbck, so it must be - * called after recount_servers(). It also expects px->srv_map - * to be initialized to the largest value needed. - */ -static void recalc_server_map(struct proxy *px) { - int o, tot, flag; - struct server *cur, *best; - - if (px->srv_act) { - flag = SRV_RUNNING; - tot = px->tot_wact; - } else if (px->srv_bck) { - flag = SRV_RUNNING | SRV_BACKUP; - if (px->options & PR_O_USE_ALL_BK) - tot = px->tot_wbck; - else - tot = 1; /* the first server is enough */ - } else { - px->srv_map_sz = 0; - return; - } - - /* this algorithm gives priority to the first server, which means that - * it will respect the declaration order for equivalent weights, and - * that whatever the weights, the first server called will always be - * the first declard. This is an important asumption for the backup - * case, where we want the first server only. - */ - for (cur = px->srv; cur; cur = cur->next) - cur->wscore = 0; - - for (o = 0; o < tot; o++) { - int max = 0; - best = NULL; - for (cur = px->srv; cur; cur = cur->next) { - if ((cur->state & (SRV_RUNNING | SRV_BACKUP)) == flag) { - int v; - - /* If we are forced to return only one server, we don't want to - * go further, because we would return the wrong one due to - * divide overflow. - */ - if (tot == 1) { - best = cur; - break; - } - - cur->wscore += cur->eweight + 1; - v = (cur->wscore + tot) / tot; /* result between 0 and 3 */ - if (best == NULL || v > max) { - max = v; - best = cur; - } - } - } - px->srv_map[o] = best; - best->wscore -= tot; - } - px->srv_map_sz = tot; -} - -/* - * This function tries to find a running server with free connection slots for - * the proxy following the round-robin method. - * If any server is found, it will be returned and px->srv_rr_idx will be updated - * to point to the next server. If no valid server is found, NULL is returned. - */ -static inline struct server *get_server_rr_with_conns(struct proxy *px) { - int newidx; - struct server *srv; - - if (px->srv_map_sz == 0) - return NULL; - - if (px->srv_rr_idx < 0 || px->srv_rr_idx >= px->srv_map_sz) - px->srv_rr_idx = 0; - newidx = px->srv_rr_idx; - - do { - srv = px->srv_map[newidx++]; - if (!srv->maxconn || srv->cur_sess < srv_dynamic_maxconn(srv)) { - px->srv_rr_idx = newidx; - return srv; - } - if (newidx == px->srv_map_sz) - newidx = 0; - } while (newidx != px->srv_rr_idx); - - return NULL; -} - - -/* - * This function tries to find a running server for the proxy following - * the round-robin method. - * If any server is found, it will be returned and px->srv_rr_idx will be updated - * to point to the next server. If no valid server is found, NULL is returned. - */ -static inline struct server *get_server_rr(struct proxy *px) { - if (px->srv_map_sz == 0) - return NULL; - - if (px->srv_rr_idx < 0 || px->srv_rr_idx >= px->srv_map_sz) - px->srv_rr_idx = 0; - return px->srv_map[px->srv_rr_idx++]; -} - - -/* - * This function tries to find a running server for the proxy following - * the source hash method. Depending on the number of active/backup servers, - * it will either look for active servers, or for backup servers. - * If any server is found, it will be returned. If no valid server is found, - * NULL is returned. - */ -static inline struct server *get_server_sh(struct proxy *px, char *addr, int len) { - unsigned int h, l; - - if (px->srv_map_sz == 0) - return NULL; - - l = h = 0; - if (px->srv_act > 1 || (px->srv_act == 0 && px->srv_bck > 1)) { - while ((l + sizeof (int)) <= len) { - h ^= ntohl(*(unsigned int *)(&addr[l])); - l += sizeof (int); - } - h %= px->srv_map_sz; - } - return px->srv_map[h]; -} - - -/* - * This function marks the session as 'assigned' in direct or dispatch modes, - * or tries to assign one in balance mode, according to the algorithm. It does - * nothing if the session had already been assigned a server. - * - * It may return : - * SRV_STATUS_OK if everything is OK. s->srv will be valid. - * SRV_STATUS_NOSRV if no server is available. s->srv = NULL. - * SRV_STATUS_FULL if all servers are saturated. s->srv = NULL. - * SRV_STATUS_INTERNAL for other unrecoverable errors. - * - * Upon successful return, the session flag SN_ASSIGNED to indicate that it does - * not need to be called anymore. This usually means that s->srv can be trusted - * in balance and direct modes. This flag is not cleared, so it's to the caller - * to clear it if required (eg: redispatch). - * - */ - -int assign_server(struct session *s) { -#ifdef DEBUG_FULL - fprintf(stderr,"assign_server : s=%p\n",s); -#endif - - if (s->pend_pos) - return SRV_STATUS_INTERNAL; - - if (!(s->flags & SN_ASSIGNED)) { - if ((s->proxy->options & PR_O_BALANCE) && !(s->flags & SN_DIRECT)) { - if (!s->proxy->srv_act && !s->proxy->srv_bck) - return SRV_STATUS_NOSRV; - - if (s->proxy->options & PR_O_BALANCE_RR) { - s->srv = get_server_rr_with_conns(s->proxy); - if (!s->srv) - return SRV_STATUS_FULL; - } - else if (s->proxy->options & PR_O_BALANCE_SH) { - int len; - - if (s->cli_addr.ss_family == AF_INET) - len = 4; - else if (s->cli_addr.ss_family == AF_INET6) - len = 16; - else /* unknown IP family */ - return SRV_STATUS_INTERNAL; - - s->srv = get_server_sh(s->proxy, - (void *)&((struct sockaddr_in *)&s->cli_addr)->sin_addr, - len); - } - else /* unknown balancing algorithm */ - return SRV_STATUS_INTERNAL; - } - s->flags |= SN_ASSIGNED; - } - return SRV_STATUS_OK; -} - -/* - * This function assigns a server address to a session, and sets SN_ADDR_SET. - * The address is taken from the currently assigned server, or from the - * dispatch or transparent address. - * - * It may return : - * SRV_STATUS_OK if everything is OK. - * SRV_STATUS_INTERNAL for other unrecoverable errors. - * - * Upon successful return, the session flag SN_ADDR_SET is set. This flag is - * not cleared, so it's to the caller to clear it if required. - * - */ -int assign_server_address(struct session *s) { -#ifdef DEBUG_FULL - fprintf(stderr,"assign_server_address : s=%p\n",s); -#endif - - if (s->flags & SN_DIRECT || s->proxy->options & PR_O_BALANCE) { - /* A server is necessarily known for this session */ - if (!(s->flags & SN_ASSIGNED)) - return SRV_STATUS_INTERNAL; - - s->srv_addr = s->srv->addr; - - /* if this server remaps proxied ports, we'll use - * the port the client connected to with an offset. */ - if (s->srv->state & SRV_MAPPORTS) { - struct sockaddr_in sockname; - socklen_t namelen = sizeof(sockname); - - if (!(s->proxy->options & PR_O_TRANSP) || - get_original_dst(s->cli_fd, (struct sockaddr_in *)&sockname, &namelen) == -1) - getsockname(s->cli_fd, (struct sockaddr *)&sockname, &namelen); - s->srv_addr.sin_port = htons(ntohs(s->srv_addr.sin_port) + ntohs(sockname.sin_port)); - } - } - else if (*(int *)&s->proxy->dispatch_addr.sin_addr) { - /* connect to the defined dispatch addr */ - s->srv_addr = s->proxy->dispatch_addr; - } - else if (s->proxy->options & PR_O_TRANSP) { - /* in transparent mode, use the original dest addr if no dispatch specified */ - socklen_t salen = sizeof(s->srv_addr); - - if (get_original_dst(s->cli_fd, &s->srv_addr, &salen) == -1) { - qfprintf(stderr, "Cannot get original server address.\n"); - return SRV_STATUS_INTERNAL; - } - } - - s->flags |= SN_ADDR_SET; - return SRV_STATUS_OK; -} - -/* This function assigns a server to session if required, and can add the - * connection to either the assigned server's queue or to the proxy's queue. - * - * Returns : - * - * SRV_STATUS_OK if everything is OK. - * SRV_STATUS_NOSRV if no server is available. s->srv = NULL. - * SRV_STATUS_QUEUED if the connection has been queued. - * SRV_STATUS_FULL if the server(s) is/are saturated and the - * connection could not be queued. - * SRV_STATUS_INTERNAL for other unrecoverable errors. - * - */ -int assign_server_and_queue(struct session *s) { - struct pendconn *p; - int err; - - if (s->pend_pos) - return SRV_STATUS_INTERNAL; - - if (s->flags & SN_ASSIGNED) { - /* a server does not need to be assigned, perhaps because we're in - * direct mode, or in dispatch or transparent modes where the server - * is not needed. - */ - if (s->srv && - s->srv->maxconn && s->srv->cur_sess >= srv_dynamic_maxconn(s->srv)) { - p = pendconn_add(s); - if (p) - return SRV_STATUS_QUEUED; - else - return SRV_STATUS_FULL; - } - return SRV_STATUS_OK; - } - - /* a server needs to be assigned */ - err = assign_server(s); - switch (err) { - case SRV_STATUS_OK: - /* in balance mode, we might have servers with connection limits */ - if (s->srv && - s->srv->maxconn && s->srv->cur_sess >= srv_dynamic_maxconn(s->srv)) { - p = pendconn_add(s); - if (p) - return SRV_STATUS_QUEUED; - else - return SRV_STATUS_FULL; - } - return SRV_STATUS_OK; - - case SRV_STATUS_FULL: - /* queue this session into the proxy's queue */ - p = pendconn_add(s); - if (p) - return SRV_STATUS_QUEUED; - else - return SRV_STATUS_FULL; - - case SRV_STATUS_NOSRV: - case SRV_STATUS_INTERNAL: - return err; - default: - return SRV_STATUS_INTERNAL; - } -} - - -/* - * This function initiates a connection to the server assigned to this session - * (s->srv, s->srv_addr). It will assign a server if none is assigned yet. - * It can return one of : - * - SN_ERR_NONE if everything's OK - * - SN_ERR_SRVTO if there are no more servers - * - SN_ERR_SRVCL if the connection was refused by the server - * - SN_ERR_PRXCOND if the connection has been limited by the proxy (maxconn) - * - SN_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...) - * - SN_ERR_INTERNAL for any other purely internal errors - * Additionnally, in the case of SN_ERR_RESOURCE, an emergency log will be emitted. - */ -int connect_server(struct session *s) { - int fd, err; - - if (!(s->flags & SN_ADDR_SET)) { - err = assign_server_address(s); - if (err != SRV_STATUS_OK) - return SN_ERR_INTERNAL; - } - - if ((fd = s->srv_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { - qfprintf(stderr, "Cannot get a server socket.\n"); - - if (errno == ENFILE) - send_log(s->proxy, LOG_EMERG, - "Proxy %s reached system FD limit at %d. Please check system tunables.\n", - s->proxy->id, maxfd); - else if (errno == EMFILE) - send_log(s->proxy, LOG_EMERG, - "Proxy %s reached process FD limit at %d. Please check 'ulimit-n' and restart.\n", - s->proxy->id, maxfd); - else if (errno == ENOBUFS || errno == ENOMEM) - send_log(s->proxy, LOG_EMERG, - "Proxy %s reached system memory limit at %d sockets. Please check system tunables.\n", - s->proxy->id, maxfd); - /* this is a resource error */ - return SN_ERR_RESOURCE; - } - - if (fd >= global.maxsock) { - /* do not log anything there, it's a normal condition when this option - * is used to serialize connections to a server ! - */ - Alert("socket(): not enough free sockets. Raise -n argument. Giving up.\n"); - close(fd); - return SN_ERR_PRXCOND; /* it is a configuration limit */ - } - - if ((fcntl(fd, F_SETFL, O_NONBLOCK)==-1) || - (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *) &one, sizeof(one)) == -1)) { - qfprintf(stderr,"Cannot set client socket to non blocking mode.\n"); - close(fd); - return SN_ERR_INTERNAL; - } - - if (s->proxy->options & PR_O_TCP_SRV_KA) - setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char *) &one, sizeof(one)); - - /* allow specific binding : - * - server-specific at first - * - proxy-specific next - */ - if (s->srv != NULL && s->srv->state & SRV_BIND_SRC) { - setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(one)); - if (bind(fd, (struct sockaddr *)&s->srv->source_addr, sizeof(s->srv->source_addr)) == -1) { - Alert("Cannot bind to source address before connect() for server %s/%s. Aborting.\n", - s->proxy->id, s->srv->id); - close(fd); - send_log(s->proxy, LOG_EMERG, - "Cannot bind to source address before connect() for server %s/%s.\n", - s->proxy->id, s->srv->id); - return SN_ERR_RESOURCE; - } - } - else if (s->proxy->options & PR_O_BIND_SRC) { - setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(one)); - if (bind(fd, (struct sockaddr *)&s->proxy->source_addr, sizeof(s->proxy->source_addr)) == -1) { - Alert("Cannot bind to source address before connect() for proxy %s. Aborting.\n", s->proxy->id); - close(fd); - send_log(s->proxy, LOG_EMERG, - "Cannot bind to source address before connect() for server %s/%s.\n", - s->proxy->id, s->srv->id); - return SN_ERR_RESOURCE; - } - } - - if ((connect(fd, (struct sockaddr *)&s->srv_addr, sizeof(s->srv_addr)) == -1) && - (errno != EINPROGRESS) && (errno != EALREADY) && (errno != EISCONN)) { - - if (errno == EAGAIN || errno == EADDRINUSE) { - char *msg; - if (errno == EAGAIN) /* no free ports left, try again later */ - msg = "no free ports"; - else - msg = "local address already in use"; - - qfprintf(stderr,"Cannot connect: %s.\n",msg); - close(fd); - send_log(s->proxy, LOG_EMERG, - "Connect() failed for server %s/%s: %s.\n", - s->proxy->id, s->srv->id, msg); - return SN_ERR_RESOURCE; - } else if (errno == ETIMEDOUT) { - //qfprintf(stderr,"Connect(): ETIMEDOUT"); - close(fd); - return SN_ERR_SRVTO; - } else { - // (errno == ECONNREFUSED || errno == ENETUNREACH || errno == EACCES || errno == EPERM) - //qfprintf(stderr,"Connect(): %d", errno); - close(fd); - return SN_ERR_SRVCL; - } - } - - fdtab[fd].owner = s->task; - fdtab[fd].read = &event_srv_read; - fdtab[fd].write = &event_srv_write; - fdtab[fd].state = FD_STCONN; /* connection in progress */ - - FD_SET(fd, StaticWriteEvent); /* for connect status */ -#if defined(DEBUG_FULL) && defined(ENABLE_EPOLL) - if (PrevReadEvent) { - assert(!(FD_ISSET(fd, PrevReadEvent))); - assert(!(FD_ISSET(fd, PrevWriteEvent))); - } -#endif - - fd_insert(fd); - if (s->srv) { - s->srv->cur_sess++; - if (s->srv->cur_sess > s->srv->cur_sess_max) - s->srv->cur_sess_max = s->srv->cur_sess; - } - - if (s->proxy->contimeout) - tv_delayfrom(&s->cnexpire, &now, s->proxy->contimeout); - else - tv_eternity(&s->cnexpire); - return SN_ERR_NONE; /* connection is OK */ -} - -/* - * this function is called on a read event from a client socket. - * It returns 0. - */ -int event_cli_read(int fd) { - struct task *t = fdtab[fd].owner; - struct session *s = t->context; - struct buffer *b = s->req; - int ret, max; - -#ifdef DEBUG_FULL - fprintf(stderr,"event_cli_read : fd=%d, s=%p\n", fd, s); -#endif - - if (fdtab[fd].state != FD_STERROR) { -#ifdef FILL_BUFFERS - while (1) -#else - do -#endif - { - if (b->l == 0) { /* let's realign the buffer to optimize I/O */ - b->r = b->w = b->h = b->lr = b->data; - max = b->rlim - b->data; - } - else if (b->r > b->w) { - max = b->rlim - b->r; - } - else { - max = b->w - b->r; - /* FIXME: theorically, if w>0, we shouldn't have rlim < data+size anymore - * since it means that the rewrite protection has been removed. This - * implies that the if statement can be removed. - */ - if (max > b->rlim - b->data) - max = b->rlim - b->data; - } - - if (max == 0) { /* not anymore room to store data */ - FD_CLR(fd, StaticReadEvent); - break; - } - -#ifndef MSG_NOSIGNAL - { - int skerr; - socklen_t lskerr = sizeof(skerr); - - getsockopt(fd, SOL_SOCKET, SO_ERROR, &skerr, &lskerr); - if (skerr) - ret = -1; - else - ret = recv(fd, b->r, max, 0); - } -#else - ret = recv(fd, b->r, max, MSG_NOSIGNAL); -#endif - if (ret > 0) { - b->r += ret; - b->l += ret; - s->res_cr = RES_DATA; - - if (b->r == b->data + BUFSIZE) { - b->r = b->data; /* wrap around the buffer */ - } - - b->total += ret; - /* we hope to read more data or to get a close on next round */ - continue; - } - else if (ret == 0) { - s->res_cr = RES_NULL; - break; - } - else if (errno == EAGAIN) {/* ignore EAGAIN */ - break; - } - else { - s->res_cr = RES_ERROR; - fdtab[fd].state = FD_STERROR; - break; - } - } /* while(1) */ -#ifndef FILL_BUFFERS - while (0); -#endif - } - else { - s->res_cr = RES_ERROR; - fdtab[fd].state = FD_STERROR; - } - - if (s->res_cr != RES_SILENT) { - if (s->proxy->clitimeout && FD_ISSET(fd, StaticReadEvent)) - tv_delayfrom(&s->crexpire, &now, s->proxy->clitimeout); - else - tv_eternity(&s->crexpire); - - task_wakeup(&rq, t); - } - - return 0; -} - - -/* - * this function is called on a read event from a server socket. - * It returns 0. - */ -int event_srv_read(int fd) { - struct task *t = fdtab[fd].owner; - struct session *s = t->context; - struct buffer *b = s->rep; - int ret, max; - -#ifdef DEBUG_FULL - fprintf(stderr,"event_srv_read : fd=%d, s=%p\n", fd, s); -#endif - - if (fdtab[fd].state != FD_STERROR) { -#ifdef FILL_BUFFERS - while (1) -#else - do -#endif - { - if (b->l == 0) { /* let's realign the buffer to optimize I/O */ - b->r = b->w = b->h = b->lr = b->data; - max = b->rlim - b->data; - } - else if (b->r > b->w) { - max = b->rlim - b->r; - } - else { - max = b->w - b->r; - /* FIXME: theorically, if w>0, we shouldn't have rlim < data+size anymore - * since it means that the rewrite protection has been removed. This - * implies that the if statement can be removed. - */ - if (max > b->rlim - b->data) - max = b->rlim - b->data; - } - - if (max == 0) { /* not anymore room to store data */ - FD_CLR(fd, StaticReadEvent); - break; - } - -#ifndef MSG_NOSIGNAL - { - int skerr; - socklen_t lskerr = sizeof(skerr); - - getsockopt(fd, SOL_SOCKET, SO_ERROR, &skerr, &lskerr); - if (skerr) - ret = -1; - else - ret = recv(fd, b->r, max, 0); - } -#else - ret = recv(fd, b->r, max, MSG_NOSIGNAL); -#endif - if (ret > 0) { - b->r += ret; - b->l += ret; - s->res_sr = RES_DATA; - - if (b->r == b->data + BUFSIZE) { - b->r = b->data; /* wrap around the buffer */ - } - - b->total += ret; - /* we hope to read more data or to get a close on next round */ - continue; - } - else if (ret == 0) { - s->res_sr = RES_NULL; - break; - } - else if (errno == EAGAIN) {/* ignore EAGAIN */ - break; - } - else { - s->res_sr = RES_ERROR; - fdtab[fd].state = FD_STERROR; - break; - } - } /* while(1) */ -#ifndef FILL_BUFFERS - while (0); -#endif - } - else { - s->res_sr = RES_ERROR; - fdtab[fd].state = FD_STERROR; - } - - if (s->res_sr != RES_SILENT) { - if (s->proxy->srvtimeout && FD_ISSET(fd, StaticReadEvent)) - tv_delayfrom(&s->srexpire, &now, s->proxy->srvtimeout); - else - tv_eternity(&s->srexpire); - - task_wakeup(&rq, t); - } - - return 0; -} - -/* - * this function is called on a write event from a client socket. - * It returns 0. - */ -int event_cli_write(int fd) { - struct task *t = fdtab[fd].owner; - struct session *s = t->context; - struct buffer *b = s->rep; - int ret, max; - -#ifdef DEBUG_FULL - fprintf(stderr,"event_cli_write : fd=%d, s=%p\n", fd, s); -#endif - - if (b->l == 0) { /* let's realign the buffer to optimize I/O */ - b->r = b->w = b->h = b->lr = b->data; - // max = BUFSIZE; BUG !!!! - max = 0; - } - else if (b->r > b->w) { - max = b->r - b->w; - } - else - max = b->data + BUFSIZE - b->w; - - if (fdtab[fd].state != FD_STERROR) { - if (max == 0) { - s->res_cw = RES_NULL; - task_wakeup(&rq, t); - tv_eternity(&s->cwexpire); - FD_CLR(fd, StaticWriteEvent); - return 0; - } - -#ifndef MSG_NOSIGNAL - { - int skerr; - socklen_t lskerr = sizeof(skerr); - - getsockopt(fd, SOL_SOCKET, SO_ERROR, &skerr, &lskerr); - if (skerr) - ret = -1; - else - ret = send(fd, b->w, max, MSG_DONTWAIT); - } -#else - ret = send(fd, b->w, max, MSG_DONTWAIT | MSG_NOSIGNAL); -#endif - - if (ret > 0) { - b->l -= ret; - b->w += ret; - - s->res_cw = RES_DATA; - - if (b->w == b->data + BUFSIZE) { - b->w = b->data; /* wrap around the buffer */ - } - } - else if (ret == 0) { - /* nothing written, just make as if we were never called */ -// s->res_cw = RES_NULL; - return 0; - } - else if (errno == EAGAIN) /* ignore EAGAIN */ - return 0; - else { - s->res_cw = RES_ERROR; - fdtab[fd].state = FD_STERROR; - } - } - else { - s->res_cw = RES_ERROR; - fdtab[fd].state = FD_STERROR; - } - - if (s->proxy->clitimeout) { - tv_delayfrom(&s->cwexpire, &now, s->proxy->clitimeout); - /* FIXME: to prevent the client from expiring read timeouts during writes, - * we refresh it. A solution would be to merge read+write timeouts into a - * unique one, although that needs some study particularly on full-duplex - * TCP connections. */ - s->crexpire = s->cwexpire; - } - else - tv_eternity(&s->cwexpire); - - task_wakeup(&rq, t); - return 0; -} - - -/* - * this function is called on a write event from a server socket. - * It returns 0. - */ -int event_srv_write(int fd) { - struct task *t = fdtab[fd].owner; - struct session *s = t->context; - struct buffer *b = s->req; - int ret, max; - -#ifdef DEBUG_FULL - fprintf(stderr,"event_srv_write : fd=%d, s=%p\n", fd, s); -#endif - - if (b->l == 0) { /* let's realign the buffer to optimize I/O */ - b->r = b->w = b->h = b->lr = b->data; - // max = BUFSIZE; BUG !!!! - max = 0; - } - else if (b->r > b->w) { - max = b->r - b->w; - } - else - max = b->data + BUFSIZE - b->w; - - if (fdtab[fd].state != FD_STERROR) { - if (max == 0) { - /* may be we have received a connection acknowledgement in TCP mode without data */ - if (s->srv_state == SV_STCONN) { - int skerr; - socklen_t lskerr = sizeof(skerr); - getsockopt(fd, SOL_SOCKET, SO_ERROR, &skerr, &lskerr); - if (skerr) { - s->res_sw = RES_ERROR; - fdtab[fd].state = FD_STERROR; - task_wakeup(&rq, t); - tv_eternity(&s->swexpire); - FD_CLR(fd, StaticWriteEvent); - return 0; - } - } - - s->res_sw = RES_NULL; - task_wakeup(&rq, t); - fdtab[fd].state = FD_STREADY; - tv_eternity(&s->swexpire); - FD_CLR(fd, StaticWriteEvent); - return 0; - } - -#ifndef MSG_NOSIGNAL - { - int skerr; - socklen_t lskerr = sizeof(skerr); - getsockopt(fd, SOL_SOCKET, SO_ERROR, &skerr, &lskerr); - if (skerr) - ret = -1; - else - ret = send(fd, b->w, max, MSG_DONTWAIT); - } -#else - ret = send(fd, b->w, max, MSG_DONTWAIT | MSG_NOSIGNAL); -#endif - fdtab[fd].state = FD_STREADY; - if (ret > 0) { - b->l -= ret; - b->w += ret; - - s->res_sw = RES_DATA; - - if (b->w == b->data + BUFSIZE) { - b->w = b->data; /* wrap around the buffer */ - } - } - else if (ret == 0) { - /* nothing written, just make as if we were never called */ - // s->res_sw = RES_NULL; - return 0; - } - else if (errno == EAGAIN) /* ignore EAGAIN */ - return 0; - else { - s->res_sw = RES_ERROR; - fdtab[fd].state = FD_STERROR; - } - } - else { - s->res_sw = RES_ERROR; - fdtab[fd].state = FD_STERROR; - } - - /* We don't want to re-arm read/write timeouts if we're trying to connect, - * otherwise it could loop indefinitely ! - */ - if (s->srv_state != SV_STCONN) { - if (s->proxy->srvtimeout) { - tv_delayfrom(&s->swexpire, &now, s->proxy->srvtimeout); - /* FIXME: to prevent the server from expiring read timeouts during writes, - * we refresh it. A solution would be to merge read+write+connect timeouts - * into a unique one since we don't mind expiring on read or write, and none - * of them is enabled while waiting for connect(), although that needs some - * study particularly on full-duplex TCP connections. */ - s->srexpire = s->swexpire; - } - else - tv_eternity(&s->swexpire); - } - - task_wakeup(&rq, t); - return 0; -} - - -/* returns 1 if the buffer is empty, 0 otherwise */ -static inline int buffer_isempty(struct buffer *buf) { - return buf->l == 0; -} - - -/* returns 1 if the buffer is full, 0 otherwise */ -static inline int buffer_isfull(struct buffer *buf) { - return buf->l == BUFSIZE; -} - - -/* flushes any content from buffer */ -void buffer_flush(struct buffer *buf) { - buf->r = buf->h = buf->lr = buf->w = buf->data; - buf->l = 0; -} - - -/* returns the maximum number of bytes writable at once in this buffer */ -int buffer_max(struct buffer *buf) { - if (buf->l == BUFSIZE) - return 0; - else if (buf->r >= buf->w) - return buf->data + BUFSIZE - buf->r; - else - return buf->w - buf->r; -} - - -/* - * Tries to realign the given buffer, and returns how many bytes can be written - * there at once without overwriting anything. - */ -int buffer_realign(struct buffer *buf) { - if (buf->l == 0) { - /* let's realign the buffer to optimize I/O */ - buf->r = buf->w = buf->h = buf->lr = buf->data; - } - return buffer_max(buf); -} - - -/* writes bytes from message to buffer . Returns 0 in case of - * success, or the number of bytes available otherwise. - * FIXME-20060521: handle unaligned data. - */ -int buffer_write(struct buffer *buf, const char *msg, int len) { - int max; - - max = buffer_realign(buf); - - if (len > max) - return max; - - memcpy(buf->r, msg, len); - buf->l += len; - buf->r += len; - if (buf->r == buf->data + BUFSIZE) - buf->r = buf->data; - return 0; -} - - -/* - * returns a message to the client ; the connection is shut down for read, - * and the request is cleared so that no server connection can be initiated. - * The client must be in a valid state for this (HEADER, DATA ...). - * Nothing is performed on the server side. - * The reply buffer doesn't need to be empty before this. - */ -void client_retnclose(struct session *s, int len, const char *msg) { - FD_CLR(s->cli_fd, StaticReadEvent); - FD_SET(s->cli_fd, StaticWriteEvent); - tv_eternity(&s->crexpire); - if (s->proxy->clitimeout) - tv_delayfrom(&s->cwexpire, &now, s->proxy->clitimeout); - else - tv_eternity(&s->cwexpire); - shutdown(s->cli_fd, SHUT_RD); - s->cli_state = CL_STSHUTR; - buffer_flush(s->rep); - buffer_write(s->rep, msg, len); - s->req->l = 0; -} - - -/* - * returns a message into the rep buffer, and flushes the req buffer. - * The reply buffer doesn't need to be empty before this. - */ -void client_return(struct session *s, int len, const char *msg) { - buffer_flush(s->rep); - buffer_write(s->rep, msg, len); - s->req->l = 0; -} - -/* - * Produces data for the session depending on its source. Expects to be - * called with s->cli_state == CL_STSHUTR. Right now, only statistics can be - * produced. It stops by itself by unsetting the SN_SELF_GEN flag from the - * session, which it uses to keep on being called when there is free space in - * the buffer, of simply by letting an empty buffer upon return. It returns 1 - * if it changes the session state from CL_STSHUTR, otherwise 0. - */ -int produce_content(struct session *s) { - struct buffer *rep = s->rep; - struct proxy *px; - struct server *sv; - int msglen; - - if (s->data_source == DATA_SRC_NONE) { - s->flags &= ~SN_SELF_GEN; - return 1; - } - else if (s->data_source == DATA_SRC_STATS) { - msglen = 0; - - if (s->data_state == DATA_ST_INIT) { /* the function had not been called yet */ - unsigned int up; - - s->flags |= SN_SELF_GEN; // more data will follow - - /* send the start of the HTTP response */ - msglen += snprintf(trash + msglen, sizeof(trash) - msglen, - "HTTP/1.0 200 OK\r\n" - "Cache-Control: no-cache\r\n" - "Connection: close\r\n" - "\r\n\r\n"); - - s->logs.status = 200; - client_retnclose(s, msglen, trash); // send the start of the response. - msglen = 0; - - if (!(s->flags & SN_ERR_MASK)) // this is not really an error but it is - s->flags |= SN_ERR_PRXCOND; // to mark that it comes from the proxy - if (!(s->flags & SN_FINST_MASK)) - s->flags |= SN_FINST_R; - - /* WARNING! This must fit in the first buffer !!! */ - msglen += snprintf(trash + msglen, sizeof(trash) - msglen, - "Statistics Report for " PRODUCT_NAME "\n" - "\n" - ""); - - if (buffer_write(rep, trash, msglen) != 0) - return 0; - msglen = 0; - - up = (now.tv_sec - start_date.tv_sec); - - /* WARNING! this has to fit the first packet too */ - msglen += snprintf(trash + msglen, sizeof(trash) - msglen, - "

" PRODUCT_NAME "

\n" - "

Statistics Report for pid %d

\n" - "
\n" - "

> General process information

\n" - "
\n" - "

pid = %d (nbproc = %d)
\n" - "uptime = %dd %dh%02dm%02ds
\n" - "system limits : memmax = %s%s ; ulimit-n = %d
\n" - "maxsock = %d
\n" - "maxconn = %d (current conns = %d)
\n" - "

\n" - "\n" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "
 active UP  backup UP
active UP, going down backup UP, going down
active DOWN, going up backup DOWN, going up
active or backup DOWN  not checked
\n" - "
\n" - "", - pid, pid, global.nbproc, - up / 86400, (up % 86400) / 3600, - (up % 3600) / 60, (up % 60), - global.rlimit_memmax ? ultoa(global.rlimit_memmax) : "unlimited", - global.rlimit_memmax ? " MB" : "", - global.rlimit_nofile, - global.maxsock, - global.maxconn, - actconn - ); - - if (buffer_write(rep, trash, msglen) != 0) - return 0; - msglen = 0; - - s->data_state = DATA_ST_DATA; - memset(&s->data_ctx, 0, sizeof(s->data_ctx)); - - px = s->data_ctx.stats.px = proxy; - s->data_ctx.stats.px_st = DATA_ST_INIT; - } - - while (s->data_ctx.stats.px) { - int dispatch_sess, dispatch_cum; - int failed_checks, down_trans; - int failed_secu, failed_conns, failed_resp; - - if (s->data_ctx.stats.px_st == DATA_ST_INIT) { - /* we are on a new proxy */ - px = s->data_ctx.stats.px; - - /* skip the disabled proxies */ - if (px->state == PR_STSTOPPED) - goto next_proxy; - - if (s->proxy->uri_auth && s->proxy->uri_auth->scope) { - /* we have a limited scope, we have to check the proxy name */ - struct stat_scope *scope; - int len; - - len = strlen(px->id); - scope = s->proxy->uri_auth->scope; - - while (scope) { - /* match exact proxy name */ - if (scope->px_len == len && !memcmp(px->id, scope->px_id, len)) - break; - - /* match '.' which means 'self' proxy */ - if (!strcmp(scope->px_id, ".") && px == s->proxy) - break; - scope = scope->next; - } - - /* proxy name not found */ - if (scope == NULL) - goto next_proxy; - } - - msglen += snprintf(trash + msglen, sizeof(trash) - msglen, - "

> Proxy instance %s : " - "%d conns (maxconn=%d), %d queued (%d unassigned), %d total conns

\n" - "", - px->id, - px->nbconn, px->maxconn, px->totpend, px->nbpend, px->cum_conn); - - msglen += snprintf(trash + msglen, sizeof(trash) - msglen, - "\n" - "" - "" - "" - "" - "\n" - "" - "" - "" - "" - "\n"); - - if (buffer_write(rep, trash, msglen) != 0) - return 0; - msglen = 0; - - s->data_ctx.stats.sv = px->srv; - s->data_ctx.stats.px_st = DATA_ST_DATA; - } - - px = s->data_ctx.stats.px; - - /* stats.sv has been initialized above */ - while (s->data_ctx.stats.sv != NULL) { - static char *act_tab_bg[5] = { /*down*/"#FF9090", /*rising*/"#FFD020", /*failing*/"#FFFFA0", /*up*/"#C0FFC0", /*unchecked*/"#E0E0E0" }; - static char *bck_tab_bg[5] = { /*down*/"#FF9090", /*rising*/"#FF80ff", /*failing*/"#C060FF", /*up*/"#B0D0FF", /*unchecked*/"#E0E0E0" }; - static char *srv_hlt_st[5] = { "DOWN", "DN %d/%d ↑", "UP %d/%d ↓", "UP", "no check" }; - int sv_state; /* 0=DOWN, 1=going up, 2=going down, 3=UP */ - - sv = s->data_ctx.stats.sv; - - /* FIXME: produce some small strings for "UP/DOWN x/y &#xxxx;" */ - if (!(sv->state & SRV_CHECKED)) - sv_state = 4; - else if (sv->state & SRV_RUNNING) - if (sv->health == sv->rise + sv->fall - 1) - sv_state = 3; /* UP */ - else - sv_state = 2; /* going down */ - else - if (sv->health) - sv_state = 1; /* going up */ - else - sv_state = 0; /* DOWN */ - - /* name, weight */ - msglen += snprintf(trash, sizeof(trash), - "", - (sv->state & SRV_BACKUP) ? "-" : "Y", - (sv->state & SRV_BACKUP) ? "Y" : "-"); - - /* queue : current, max */ - msglen += snprintf(trash + msglen, sizeof(trash) - msglen, - "", - sv->nbpend, sv->nbpend_max); - - /* sessions : current, max, limit, cumul */ - msglen += snprintf(trash + msglen, sizeof(trash) - msglen, - "", - sv->cur_sess, sv->cur_sess_max, sv->maxconn ? ultoa(sv->maxconn) : "-", sv->cum_sess); - - /* errors : connect, response, security */ - msglen += snprintf(trash + msglen, sizeof(trash) - msglen, - "\n", - sv->failed_conns, sv->failed_resp, sv->failed_secu); - - /* check failures : unique, fatal */ - if (sv->state & SRV_CHECKED) - msglen += snprintf(trash + msglen, sizeof(trash) - msglen, - "\n", - sv->failed_checks, sv->down_trans); - else - msglen += snprintf(trash + msglen, sizeof(trash) - msglen, - "\n"); - - if (buffer_write(rep, trash, msglen) != 0) - return 0; - msglen = 0; - - s->data_ctx.stats.sv = sv->next; - } /* while sv */ - - /* now we are past the last server, we'll dump information about the dispatcher */ - - /* We have to count down from the proxy to the servers to tell how - * many sessions are on the dispatcher, and how many checks have - * failed. We cannot count this during the servers dump because it - * might be interrupted multiple times. - */ - dispatch_sess = px->nbconn; - dispatch_cum = px->cum_conn; - failed_secu = px->failed_secu; - failed_conns = px->failed_conns; - failed_resp = px->failed_resp; - failed_checks = down_trans = 0; - - sv = px->srv; - while (sv) { - dispatch_sess -= sv->cur_sess; - dispatch_cum -= sv->cum_sess; - failed_conns -= sv->failed_conns; - failed_resp -= sv->failed_resp; - failed_secu -= sv->failed_secu; - if (sv->state & SRV_CHECKED) { - failed_checks += sv->failed_checks; - down_trans += sv->down_trans; - } - sv = sv->next; - } - - /* name, weight, status, act, bck */ - msglen += snprintf(trash + msglen, sizeof(trash), - "" - "" - "", - px->state == PR_STRUN ? "UP" : "DOWN"); - - /* queue : current, max */ - msglen += snprintf(trash + msglen, sizeof(trash) - msglen, - "", - px->nbpend, px->nbpend_max); - - /* sessions : current, max, limit, cumul. */ - msglen += snprintf(trash + msglen, sizeof(trash) - msglen, - "", - dispatch_sess, px->nbconn_max, px->maxconn, dispatch_cum); - - /* errors : connect, response, security */ - msglen += snprintf(trash + msglen, sizeof(trash) - msglen, - "\n", - failed_conns, failed_resp, failed_secu); - - /* check failures : unique, fatal */ - msglen += snprintf(trash + msglen, sizeof(trash) - msglen, - "\n"); - - - /* now the summary for the whole proxy */ - /* name, weight, status, act, bck */ - msglen += snprintf(trash + msglen, sizeof(trash), - "" - "" - "", - (px->state == PR_STRUN && ((px->srv == NULL) || px->srv_act || px->srv_bck)) ? "UP" : "DOWN", - px->srv_act, px->srv_bck); - - /* queue : current, max */ - msglen += snprintf(trash + msglen, sizeof(trash) - msglen, - "", - px->totpend, px->nbpend_max); - - /* sessions : current, max, limit, cumul */ - msglen += snprintf(trash + msglen, sizeof(trash) - msglen, - "", - px->nbconn, px->nbconn_max, px->maxconn, px->cum_conn); - - /* errors : connect, response, security */ - msglen += snprintf(trash + msglen, sizeof(trash) - msglen, - "\n", - px->failed_conns, px->failed_resp, px->failed_secu); - - /* check failures : unique, fatal */ - msglen += snprintf(trash + msglen, sizeof(trash) - msglen, - "\n", - failed_checks, down_trans); - - msglen += snprintf(trash + msglen, sizeof(trash) - msglen, "
ServerQueueSessionsErrors
NameWeightStatusAct.Bck.Curr.Max.Curr.Max.LimitCumul.Conn.Resp.Sec.CheckDown
%s%d", - (sv->state & SRV_BACKUP) ? bck_tab_bg[sv_state] : act_tab_bg[sv_state], - sv->id, sv->uweight+1); - /* status */ - msglen += snprintf(trash + msglen, sizeof(trash) - msglen, srv_hlt_st[sv_state], - (sv->state & SRV_RUNNING) ? (sv->health - sv->rise + 1) : (sv->health), - (sv->state & SRV_RUNNING) ? (sv->fall) : (sv->rise)); - - /* act, bck */ - msglen += snprintf(trash + msglen, sizeof(trash) - msglen, - "%s%s%d%d%d%d%s%d%d%d%d%d%d
--
Dispatcher-%s--%d%d%d%d%d%d%d%d%d--
Total-%s%d%d%d%d%d%d%d%d%d%d%d%d%d

\n"); - - if (buffer_write(rep, trash, msglen) != 0) - return 0; - msglen = 0; - - s->data_ctx.stats.px_st = DATA_ST_INIT; - next_proxy: - s->data_ctx.stats.px = px->next; - } /* proxy loop */ - /* here, we just have reached the sv == NULL and px == NULL */ - s->flags &= ~SN_SELF_GEN; - return 1; - } - else { - /* unknown data source */ - s->logs.status = 500; - client_retnclose(s, s->proxy->errmsg.len500, s->proxy->errmsg.msg500); - if (!(s->flags & SN_ERR_MASK)) - s->flags |= SN_ERR_PRXCOND; - if (!(s->flags & SN_FINST_MASK)) - s->flags |= SN_FINST_R; - s->flags &= SN_SELF_GEN; - return 1; - } -} - - -/* - * send a log for the session when we have enough info about it - */ -void sess_log(struct session *s) { - char pn[INET6_ADDRSTRLEN + strlen(":65535")]; - struct proxy *p = s->proxy; - int log; - char *uri; - char *pxid; - char *srv; - struct tm *tm; - - /* This is a first attempt at a better logging system. - * For now, we rely on send_log() to provide the date, although it obviously - * is the date of the log and not of the request, and most fields are not - * computed. - */ - - log = p->to_log & ~s->logs.logwait; - - if (s->cli_addr.ss_family == AF_INET) - inet_ntop(AF_INET, - (const void *)&((struct sockaddr_in *)&s->cli_addr)->sin_addr, - pn, sizeof(pn)); - else - inet_ntop(AF_INET6, - (const void *)&((struct sockaddr_in6 *)(&s->cli_addr))->sin6_addr, - pn, sizeof(pn)); - - uri = (log & LW_REQ) ? s->logs.uri ? s->logs.uri : "" : ""; - pxid = p->id; - srv = (p->to_log & LW_SVID) ? - (s->data_source != DATA_SRC_STATS) ? - (s->srv != NULL) ? s->srv->id : "" : "" : "-"; - - tm = localtime(&s->logs.tv_accept.tv_sec); - if (p->to_log & LW_REQ) { - char tmpline[MAX_SYSLOG_LEN], *h; - int hdr; - - h = tmpline; - if (p->to_log & LW_REQHDR && (h < tmpline + sizeof(tmpline) - 10)) { - *(h++) = ' '; - *(h++) = '{'; - for (hdr = 0; hdr < p->nb_req_cap; hdr++) { - if (hdr) - *(h++) = '|'; - if (s->req_cap[hdr] != NULL) - h = encode_string(h, tmpline + sizeof(tmpline) - 7, '#', hdr_encode_map, s->req_cap[hdr]); - } - *(h++) = '}'; - } - - if (p->to_log & LW_RSPHDR && (h < tmpline + sizeof(tmpline) - 7)) { - *(h++) = ' '; - *(h++) = '{'; - for (hdr = 0; hdr < p->nb_rsp_cap; hdr++) { - if (hdr) - *(h++) = '|'; - if (s->rsp_cap[hdr] != NULL) - h = encode_string(h, tmpline + sizeof(tmpline) - 4, '#', hdr_encode_map, s->rsp_cap[hdr]); - } - *(h++) = '}'; - } - - if (h < tmpline + sizeof(tmpline) - 4) { - *(h++) = ' '; - *(h++) = '"'; - h = encode_string(h, tmpline + sizeof(tmpline) - 1, '#', url_encode_map, uri); - *(h++) = '"'; - } - *h = '\0'; - - send_log(p, LOG_INFO, "%s:%d [%02d/%s/%04d:%02d:%02d:%02d] %s %s %d/%d/%d/%d/%s%d %d %s%lld %s %s %c%c%c%c %d/%d/%d %d/%d%s\n", - pn, - (s->cli_addr.ss_family == AF_INET) ? - ntohs(((struct sockaddr_in *)&s->cli_addr)->sin_port) : - ntohs(((struct sockaddr_in6 *)&s->cli_addr)->sin6_port), - tm->tm_mday, monthname[tm->tm_mon], tm->tm_year+1900, - tm->tm_hour, tm->tm_min, tm->tm_sec, - pxid, srv, - s->logs.t_request, - (s->logs.t_queue >= 0) ? s->logs.t_queue - s->logs.t_request : -1, - (s->logs.t_connect >= 0) ? s->logs.t_connect - s->logs.t_queue : -1, - (s->logs.t_data >= 0) ? s->logs.t_data - s->logs.t_connect : -1, - (p->to_log & LW_BYTES) ? "" : "+", s->logs.t_close, - s->logs.status, - (p->to_log & LW_BYTES) ? "" : "+", s->logs.bytes, - s->logs.cli_cookie ? s->logs.cli_cookie : "-", - s->logs.srv_cookie ? s->logs.srv_cookie : "-", - sess_term_cond[(s->flags & SN_ERR_MASK) >> SN_ERR_SHIFT], - sess_fin_state[(s->flags & SN_FINST_MASK) >> SN_FINST_SHIFT], - (p->options & PR_O_COOK_ANY) ? sess_cookie[(s->flags & SN_CK_MASK) >> SN_CK_SHIFT] : '-', - (p->options & PR_O_COOK_ANY) ? sess_set_cookie[(s->flags & SN_SCK_MASK) >> SN_SCK_SHIFT] : '-', - s->srv ? s->srv->cur_sess : 0, p->nbconn, actconn, - s->logs.srv_queue_size, s->logs.prx_queue_size, tmpline); - } - else { - send_log(p, LOG_INFO, "%s:%d [%02d/%s/%04d:%02d:%02d:%02d] %s %s %d/%d/%s%d %s%lld %c%c %d/%d/%d %d/%d\n", - pn, - (s->cli_addr.ss_family == AF_INET) ? - ntohs(((struct sockaddr_in *)&s->cli_addr)->sin_port) : - ntohs(((struct sockaddr_in6 *)&s->cli_addr)->sin6_port), - tm->tm_mday, monthname[tm->tm_mon], tm->tm_year+1900, - tm->tm_hour, tm->tm_min, tm->tm_sec, - pxid, srv, - (s->logs.t_queue >= 0) ? s->logs.t_queue : -1, - (s->logs.t_connect >= 0) ? s->logs.t_connect - s->logs.t_queue : -1, - (p->to_log & LW_BYTES) ? "" : "+", s->logs.t_close, - (p->to_log & LW_BYTES) ? "" : "+", s->logs.bytes, - sess_term_cond[(s->flags & SN_ERR_MASK) >> SN_ERR_SHIFT], - sess_fin_state[(s->flags & SN_FINST_MASK) >> SN_FINST_SHIFT], - s->srv ? s->srv->cur_sess : 0, p->nbconn, actconn, - s->logs.srv_queue_size, s->logs.prx_queue_size); - } - - s->logs.logwait = 0; -} - - -/* - * this function is called on a read event from a listen socket, corresponding - * to an accept. It tries to accept as many connections as possible. - * It returns 0. - */ -int event_accept(int fd) { - struct proxy *p = (struct proxy *)fdtab[fd].owner; - struct session *s; - struct task *t; - int cfd; - int max_accept; - - if (global.nbproc > 1) - max_accept = 8; /* let other processes catch some connections too */ - else - max_accept = -1; - - while (p->nbconn < p->maxconn && max_accept--) { - struct sockaddr_storage addr; - socklen_t laddr = sizeof(addr); - - if ((cfd = accept(fd, (struct sockaddr *)&addr, &laddr)) == -1) { - switch (errno) { - case EAGAIN: - case EINTR: - case ECONNABORTED: - return 0; /* nothing more to accept */ - case ENFILE: - send_log(p, LOG_EMERG, - "Proxy %s reached system FD limit at %d. Please check system tunables.\n", - p->id, maxfd); - return 0; - case EMFILE: - send_log(p, LOG_EMERG, - "Proxy %s reached process FD limit at %d. Please check 'ulimit-n' and restart.\n", - p->id, maxfd); - return 0; - case ENOBUFS: - case ENOMEM: - send_log(p, LOG_EMERG, - "Proxy %s reached system memory limit at %d sockets. Please check system tunables.\n", - p->id, maxfd); - return 0; - default: - return 0; - } - } - - if ((s = pool_alloc(session)) == NULL) { /* disable this proxy for a while */ - Alert("out of memory in event_accept().\n"); - FD_CLR(fd, StaticReadEvent); - p->state = PR_STIDLE; - close(cfd); - return 0; - } - - /* if this session comes from a known monitoring system, we want to ignore - * it as soon as possible, which means closing it immediately for TCP. - */ - s->flags = 0; - if (addr.ss_family == AF_INET && - p->mon_mask.s_addr && - (((struct sockaddr_in *)&addr)->sin_addr.s_addr & p->mon_mask.s_addr) == p->mon_net.s_addr) { - if (p->mode == PR_MODE_TCP) { - close(cfd); - pool_free(session, s); - continue; - } - s->flags |= SN_MONITOR; - } - - if ((t = pool_alloc(task)) == NULL) { /* disable this proxy for a while */ - Alert("out of memory in event_accept().\n"); - FD_CLR(fd, StaticReadEvent); - p->state = PR_STIDLE; - close(cfd); - pool_free(session, s); - return 0; - } - - s->cli_addr = addr; - if (cfd >= global.maxsock) { - Alert("accept(): not enough free sockets. Raise -n argument. Giving up.\n"); - close(cfd); - pool_free(task, t); - pool_free(session, s); - return 0; - } - - if ((fcntl(cfd, F_SETFL, O_NONBLOCK) == -1) || - (setsockopt(cfd, IPPROTO_TCP, TCP_NODELAY, - (char *) &one, sizeof(one)) == -1)) { - Alert("accept(): cannot set the socket in non blocking mode. Giving up\n"); - close(cfd); - pool_free(task, t); - pool_free(session, s); - return 0; - } - - if (p->options & PR_O_TCP_CLI_KA) - setsockopt(cfd, SOL_SOCKET, SO_KEEPALIVE, (char *) &one, sizeof(one)); - - t->next = t->prev = t->rqnext = NULL; /* task not in run queue yet */ - t->wq = LIST_HEAD(wait_queue[0]); /* but already has a wait queue assigned */ - t->state = TASK_IDLE; - t->process = process_session; - t->context = s; - - s->task = t; - s->proxy = p; - s->cli_state = (p->mode == PR_MODE_HTTP) ? CL_STHEADERS : CL_STDATA; /* no HTTP headers for non-HTTP proxies */ - s->srv_state = SV_STIDLE; - s->req = s->rep = NULL; /* will be allocated later */ - - s->res_cr = s->res_cw = s->res_sr = s->res_sw = RES_SILENT; - s->cli_fd = cfd; - s->srv_fd = -1; - s->req_line.len = -1; - s->auth_hdr.len = -1; - s->srv = NULL; - s->pend_pos = NULL; - s->conn_retries = p->conn_retries; - - if (s->flags & SN_MONITOR) - s->logs.logwait = 0; - else - s->logs.logwait = p->to_log; - - s->logs.tv_accept = now; - s->logs.t_request = -1; - s->logs.t_queue = -1; - s->logs.t_connect = -1; - s->logs.t_data = -1; - s->logs.t_close = 0; - s->logs.uri = NULL; - s->logs.cli_cookie = NULL; - s->logs.srv_cookie = NULL; - s->logs.status = -1; - s->logs.bytes = 0; - s->logs.prx_queue_size = 0; /* we get the number of pending conns before us */ - s->logs.srv_queue_size = 0; /* we will get this number soon */ - - s->data_source = DATA_SRC_NONE; - - s->uniq_id = totalconn; - p->cum_conn++; - - if (p->nb_req_cap > 0) { - if ((s->req_cap = - pool_alloc_from(p->req_cap_pool, p->nb_req_cap*sizeof(char *))) - == NULL) { /* no memory */ - close(cfd); /* nothing can be done for this fd without memory */ - pool_free(task, t); - pool_free(session, s); - return 0; - } - memset(s->req_cap, 0, p->nb_req_cap*sizeof(char *)); - } - else - s->req_cap = NULL; - - if (p->nb_rsp_cap > 0) { - if ((s->rsp_cap = - pool_alloc_from(p->rsp_cap_pool, p->nb_rsp_cap*sizeof(char *))) - == NULL) { /* no memory */ - if (s->req_cap != NULL) - pool_free_to(p->req_cap_pool, s->req_cap); - close(cfd); /* nothing can be done for this fd without memory */ - pool_free(task, t); - pool_free(session, s); - return 0; - } - memset(s->rsp_cap, 0, p->nb_rsp_cap*sizeof(char *)); - } - else - s->rsp_cap = NULL; - - if ((p->mode == PR_MODE_TCP || p->mode == PR_MODE_HTTP) - && (p->logfac1 >= 0 || p->logfac2 >= 0)) { - struct sockaddr_storage sockname; - socklen_t namelen = sizeof(sockname); - - if (addr.ss_family != AF_INET || - !(s->proxy->options & PR_O_TRANSP) || - get_original_dst(cfd, (struct sockaddr_in *)&sockname, &namelen) == -1) - getsockname(cfd, (struct sockaddr *)&sockname, &namelen); - - if (p->to_log) { - /* we have the client ip */ - if (s->logs.logwait & LW_CLIP) - if (!(s->logs.logwait &= ~LW_CLIP)) - sess_log(s); - } - else if (s->cli_addr.ss_family == AF_INET) { - char pn[INET_ADDRSTRLEN], sn[INET_ADDRSTRLEN]; - if (inet_ntop(AF_INET, (const void *)&((struct sockaddr_in *)&sockname)->sin_addr, - sn, sizeof(sn)) && - inet_ntop(AF_INET, (const void *)&((struct sockaddr_in *)&s->cli_addr)->sin_addr, - pn, sizeof(pn))) { - send_log(p, LOG_INFO, "Connect from %s:%d to %s:%d (%s/%s)\n", - pn, ntohs(((struct sockaddr_in *)&s->cli_addr)->sin_port), - sn, ntohs(((struct sockaddr_in *)&sockname)->sin_port), - p->id, (p->mode == PR_MODE_HTTP) ? "HTTP" : "TCP"); - } - } - else { - char pn[INET6_ADDRSTRLEN], sn[INET6_ADDRSTRLEN]; - if (inet_ntop(AF_INET6, (const void *)&((struct sockaddr_in6 *)&sockname)->sin6_addr, - sn, sizeof(sn)) && - inet_ntop(AF_INET6, (const void *)&((struct sockaddr_in6 *)&s->cli_addr)->sin6_addr, - pn, sizeof(pn))) { - send_log(p, LOG_INFO, "Connect from %s:%d to %s:%d (%s/%s)\n", - pn, ntohs(((struct sockaddr_in6 *)&s->cli_addr)->sin6_port), - sn, ntohs(((struct sockaddr_in6 *)&sockname)->sin6_port), - p->id, (p->mode == PR_MODE_HTTP) ? "HTTP" : "TCP"); - } - } - } - - if ((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) { - struct sockaddr_in sockname; - socklen_t namelen = sizeof(sockname); - int len; - if (addr.ss_family != AF_INET || - !(s->proxy->options & PR_O_TRANSP) || - get_original_dst(cfd, (struct sockaddr_in *)&sockname, &namelen) == -1) - getsockname(cfd, (struct sockaddr *)&sockname, &namelen); - - if (s->cli_addr.ss_family == AF_INET) { - char pn[INET_ADDRSTRLEN]; - inet_ntop(AF_INET, - (const void *)&((struct sockaddr_in *)&s->cli_addr)->sin_addr, - pn, sizeof(pn)); - - len = sprintf(trash, "%08x:%s.accept(%04x)=%04x from [%s:%d]\n", - s->uniq_id, p->id, (unsigned short)fd, (unsigned short)cfd, - pn, ntohs(((struct sockaddr_in *)&s->cli_addr)->sin_port)); - } - else { - char pn[INET6_ADDRSTRLEN]; - inet_ntop(AF_INET6, - (const void *)&((struct sockaddr_in6 *)(&s->cli_addr))->sin6_addr, - pn, sizeof(pn)); - - len = sprintf(trash, "%08x:%s.accept(%04x)=%04x from [%s:%d]\n", - s->uniq_id, p->id, (unsigned short)fd, (unsigned short)cfd, - pn, ntohs(((struct sockaddr_in6 *)(&s->cli_addr))->sin6_port)); - } - - write(1, trash, len); - } - - if ((s->req = pool_alloc(buffer)) == NULL) { /* no memory */ - if (s->rsp_cap != NULL) - pool_free_to(p->rsp_cap_pool, s->rsp_cap); - if (s->req_cap != NULL) - pool_free_to(p->req_cap_pool, s->req_cap); - close(cfd); /* nothing can be done for this fd without memory */ - pool_free(task, t); - pool_free(session, s); - return 0; - } - - s->req->l = 0; - s->req->total = 0; - s->req->h = s->req->r = s->req->lr = s->req->w = s->req->data; /* r and w will be reset further */ - s->req->rlim = s->req->data + BUFSIZE; - if (s->cli_state == CL_STHEADERS) /* reserve some space for header rewriting */ - s->req->rlim -= MAXREWRITE; - - if ((s->rep = pool_alloc(buffer)) == NULL) { /* no memory */ - pool_free(buffer, s->req); - if (s->rsp_cap != NULL) - pool_free_to(p->rsp_cap_pool, s->rsp_cap); - if (s->req_cap != NULL) - pool_free_to(p->req_cap_pool, s->req_cap); - close(cfd); /* nothing can be done for this fd without memory */ - pool_free(task, t); - pool_free(session, s); - return 0; - } - s->rep->l = 0; - s->rep->total = 0; - s->rep->h = s->rep->r = s->rep->lr = s->rep->w = s->rep->rlim = s->rep->data; - - fdtab[cfd].read = &event_cli_read; - fdtab[cfd].write = &event_cli_write; - fdtab[cfd].owner = t; - fdtab[cfd].state = FD_STREADY; - - if ((p->mode == PR_MODE_HTTP && (s->flags & SN_MONITOR)) || - (p->mode == PR_MODE_HEALTH && (p->options & PR_O_HTTP_CHK))) - /* Either we got a request from a monitoring system on an HTTP instance, - * or we're in health check mode with the 'httpchk' option enabled. In - * both cases, we return a fake "HTTP/1.0 200 OK" response and we exit. - */ - client_retnclose(s, 19, "HTTP/1.0 200 OK\r\n\r\n"); /* forge a 200 response */ - else if (p->mode == PR_MODE_HEALTH) { /* health check mode, no client reading */ - client_retnclose(s, 3, "OK\n"); /* forge an "OK" response */ - } - else { - FD_SET(cfd, StaticReadEvent); - } - -#if defined(DEBUG_FULL) && defined(ENABLE_EPOLL) - if (PrevReadEvent) { - assert(!(FD_ISSET(cfd, PrevReadEvent))); - assert(!(FD_ISSET(cfd, PrevWriteEvent))); - } -#endif - fd_insert(cfd); - - tv_eternity(&s->cnexpire); - tv_eternity(&s->srexpire); - tv_eternity(&s->swexpire); - tv_eternity(&s->crexpire); - tv_eternity(&s->cwexpire); - - if (s->proxy->clitimeout) { - if (FD_ISSET(cfd, StaticReadEvent)) - tv_delayfrom(&s->crexpire, &now, s->proxy->clitimeout); - if (FD_ISSET(cfd, StaticWriteEvent)) - tv_delayfrom(&s->cwexpire, &now, s->proxy->clitimeout); - } - - tv_min(&t->expire, &s->crexpire, &s->cwexpire); - - task_queue(t); - - if (p->mode != PR_MODE_HEALTH) - task_wakeup(&rq, t); - - p->nbconn++; - if (p->nbconn > p->nbconn_max) - p->nbconn_max = p->nbconn; - actconn++; - totalconn++; - - // fprintf(stderr, "accepting from %p => %d conn, %d total, task=%p\n", p, actconn, totalconn, t); - } /* end of while (p->nbconn < p->maxconn) */ - return 0; -} - - -/* - * This function is used only for server health-checks. It handles - * the connection acknowledgement. If the proxy requires HTTP health-checks, - * it sends the request. In other cases, it returns 1 if the socket is OK, - * or -1 if an error occured. - */ -int event_srv_chk_w(int fd) { - struct task *t = fdtab[fd].owner; - struct server *s = t->context; - int skerr; - socklen_t lskerr = sizeof(skerr); - - skerr = 1; - if ((getsockopt(fd, SOL_SOCKET, SO_ERROR, &skerr, &lskerr) == -1) - || (skerr != 0)) { - /* in case of TCP only, this tells us if the connection failed */ - s->result = -1; - fdtab[fd].state = FD_STERROR; - FD_CLR(fd, StaticWriteEvent); - } - else if (s->result != -1) { - /* we don't want to mark 'UP' a server on which we detected an error earlier */ - if (s->proxy->options & PR_O_HTTP_CHK) { - int ret; - /* we want to check if this host replies to "OPTIONS / HTTP/1.0" - * so we'll send the request, and won't wake the checker up now. - */ -#ifndef MSG_NOSIGNAL - ret = send(fd, s->proxy->check_req, s->proxy->check_len, MSG_DONTWAIT); -#else - ret = send(fd, s->proxy->check_req, s->proxy->check_len, MSG_DONTWAIT | MSG_NOSIGNAL); -#endif - if (ret == s->proxy->check_len) { - FD_SET(fd, StaticReadEvent); /* prepare for reading reply */ - FD_CLR(fd, StaticWriteEvent); /* nothing more to write */ - return 0; - } - else { - s->result = -1; - FD_CLR(fd, StaticWriteEvent); - } - } - else { - /* good TCP connection is enough */ - s->result = 1; - } - } - - task_wakeup(&rq, t); - return 0; -} - - -/* - * This function is used only for server health-checks. It handles - * the server's reply to an HTTP request. It returns 1 if the server replies - * 2xx or 3xx (valid responses), or -1 in other cases. - */ -int event_srv_chk_r(int fd) { - char reply[64]; - int len, result; - struct task *t = fdtab[fd].owner; - struct server *s = t->context; - int skerr; - socklen_t lskerr = sizeof(skerr); - - result = len = -1; - - getsockopt(fd, SOL_SOCKET, SO_ERROR, &skerr, &lskerr); - if (!skerr) { -#ifndef MSG_NOSIGNAL - len = recv(fd, reply, sizeof(reply), 0); -#else - /* Warning! Linux returns EAGAIN on SO_ERROR if data are still available - * but the connection was closed on the remote end. Fortunately, recv still - * works correctly and we don't need to do the getsockopt() on linux. - */ - len = recv(fd, reply, sizeof(reply), MSG_NOSIGNAL); -#endif - - if ((len >= sizeof("HTTP/1.0 000")) && - !memcmp(reply, "HTTP/1.", 7) && - (reply[9] == '2' || reply[9] == '3')) /* 2xx or 3xx */ - result = 1; - } - - if (result == -1) - fdtab[fd].state = FD_STERROR; - - if (s->result != -1) - s->result = result; - - FD_CLR(fd, StaticReadEvent); - task_wakeup(&rq, t); - return 0; -} - - -/* - * this function writes the string at position which must be in buffer , - * and moves just after the end of . - * 's parameters (l, r, w, h, lr) are recomputed to be valid after the shift. - * the shift value (positive or negative) is returned. - * If there's no space left, the move is not done. - * - */ -int buffer_replace(struct buffer *b, char *pos, char *end, char *str) { - int delta; - int len; - - len = strlen(str); - delta = len - (end - pos); - - if (delta + b->r >= b->data + BUFSIZE) - return 0; /* no space left */ - - /* first, protect the end of the buffer */ - memmove(end + delta, end, b->data + b->l - end); - - /* now, copy str over pos */ - memcpy(pos, str,len); - - /* we only move data after the displaced zone */ - if (b->r > pos) b->r += delta; - if (b->w > pos) b->w += delta; - if (b->h > pos) b->h += delta; - if (b->lr > pos) b->lr += delta; - b->l += delta; - - return delta; -} - -/* same except that the string length is given, which allows str to be NULL if - * len is 0. - */ -int buffer_replace2(struct buffer *b, char *pos, char *end, char *str, int len) { - int delta; - - delta = len - (end - pos); - - if (delta + b->r >= b->data + BUFSIZE) - return 0; /* no space left */ - - if (b->data + b->l < end) - /* The data has been stolen, we could have crashed. Maybe we should abort() ? */ - return 0; - - /* first, protect the end of the buffer */ - memmove(end + delta, end, b->data + b->l - end); - - /* now, copy str over pos */ - if (len) - memcpy(pos, str, len); - - /* we only move data after the displaced zone */ - if (b->r > pos) b->r += delta; - if (b->w > pos) b->w += delta; - if (b->h > pos) b->h += delta; - if (b->lr > pos) b->lr += delta; - b->l += delta; - - return delta; -} - - -int exp_replace(char *dst, char *src, char *str, regmatch_t *matches) { - char *old_dst = dst; - - while (*str) { - if (*str == '\\') { - str++; - if (isdigit((int)*str)) { - int len, num; - - num = *str - '0'; - str++; - - if (matches[num].rm_eo > -1 && matches[num].rm_so > -1) { - len = matches[num].rm_eo - matches[num].rm_so; - memcpy(dst, src + matches[num].rm_so, len); - dst += len; - } - - } - else if (*str == 'x') { - unsigned char hex1, hex2; - str++; - - hex1 = toupper(*str++) - '0'; - hex2 = toupper(*str++) - '0'; - - if (hex1 > 9) hex1 -= 'A' - '9' - 1; - if (hex2 > 9) hex2 -= 'A' - '9' - 1; - *dst++ = (hex1<<4) + hex2; - } - else - *dst++ = *str++; - } - else - *dst++ = *str++; - } - *dst = 0; - return dst - old_dst; -} - -static int ishex(char s) -{ - return (s >= '0' && s <= '9') || (s >= 'A' && s <= 'F') || (s >= 'a' && s <= 'f'); -} - -/* returns NULL if the replacement string is valid, or the pointer to the first error */ -char *check_replace_string(char *str) -{ - char *err = NULL; - while (*str) { - if (*str == '\\') { - err = str; /* in case of a backslash, we return the pointer to it */ - str++; - if (!*str) - return err; - else if (isdigit((int)*str)) - err = NULL; - else if (*str == 'x') { - str++; - if (!ishex(*str)) - return err; - str++; - if (!ishex(*str)) - return err; - err = NULL; - } - else { - Warning("'\\%c' : deprecated use of a backslash before something not '\\','x' or a digit.\n", *str); - err = NULL; - } - } - str++; - } - return err; -} - -/* - * manages the client FSM and its socket. BTW, it also tries to handle the - * cookie. It returns 1 if a state has changed (and a resync may be needed), - * 0 else. - */ -int process_cli(struct session *t) { - int s = t->srv_state; - int c = t->cli_state; - struct buffer *req = t->req; - struct buffer *rep = t->rep; - int method_checked = 0; - appsess *asession_temp = NULL; - appsess local_asession; - -#ifdef DEBUG_FULL - fprintf(stderr,"process_cli: c=%s s=%s set(r,w)=%d,%d exp(r,w)=%d.%d,%d.%d\n", - cli_stnames[c], srv_stnames[s], - FD_ISSET(t->cli_fd, StaticReadEvent), FD_ISSET(t->cli_fd, StaticWriteEvent), - t->crexpire.tv_sec, t->crexpire.tv_usec, - t->cwexpire.tv_sec, t->cwexpire.tv_usec); -#endif - //fprintf(stderr,"process_cli: c=%d, s=%d, cr=%d, cw=%d, sr=%d, sw=%d\n", c, s, - //FD_ISSET(t->cli_fd, StaticReadEvent), FD_ISSET(t->cli_fd, StaticWriteEvent), - //FD_ISSET(t->srv_fd, StaticReadEvent), FD_ISSET(t->srv_fd, StaticWriteEvent) - //); - if (c == CL_STHEADERS) { - /* now parse the partial (or complete) headers */ - while (req->lr < req->r) { /* this loop only sees one header at each iteration */ - char *ptr; - int delete_header; - char *request_line = NULL; - - ptr = req->lr; - - /* look for the end of the current header */ - while (ptr < req->r && *ptr != '\n' && *ptr != '\r') - ptr++; - - if (ptr == req->h) { /* empty line, end of headers */ - int line, len; - - /* - * first, let's check that it's not a leading empty line, in - * which case we'll ignore and remove it (according to RFC2616). - */ - if (req->h == req->data) { - /* to get a complete header line, we need the ending \r\n, \n\r, \r or \n too */ - if (ptr > req->r - 2) { - /* this is a partial header, let's wait for more to come */ - req->lr = ptr; - break; - } - - /* now we know that *ptr is either \r or \n, - * and that there are at least 1 char after it. - */ - if ((ptr[0] == ptr[1]) || (ptr[1] != '\r' && ptr[1] != '\n')) - req->lr = ptr + 1; /* \r\r, \n\n, \r[^\n], \n[^\r] */ - else - req->lr = ptr + 2; /* \r\n or \n\r */ - /* ignore empty leading lines */ - buffer_replace2(req, req->h, req->lr, NULL, 0); - req->h = req->lr; - continue; - } - - /* we can only get here after an end of headers */ - /* we'll have something else to do here : add new headers ... */ - - if (t->flags & SN_CLDENY) { - /* no need to go further */ - t->logs.status = 403; - t->logs.t_request = tv_diff(&t->logs.tv_accept, &now); /* let's log the request time */ - client_retnclose(t, t->proxy->errmsg.len403, t->proxy->errmsg.msg403); - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_PRXCOND; - if (!(t->flags & SN_FINST_MASK)) - t->flags |= SN_FINST_R; - return 1; - } - - /* Right now, we know that we have processed the entire headers - * and that unwanted requests have been filtered out. We can do - * whatever we want. - */ - - if (t->proxy->uri_auth != NULL - && t->req_line.len >= t->proxy->uri_auth->uri_len + 4) { /* +4 for "GET /" */ - if (!memcmp(t->req_line.str + 4, - t->proxy->uri_auth->uri_prefix, t->proxy->uri_auth->uri_len) - && !memcmp(t->req_line.str, "GET ", 4)) { - struct user_auth *user; - int authenticated; - - /* we are in front of a interceptable URI. Let's check - * if there's an authentication and if it's valid. - */ - user = t->proxy->uri_auth->users; - if (!user) { - /* no user auth required, it's OK */ - authenticated = 1; - } else { - authenticated = 0; - - /* a user list is defined, we have to check. - * skip 21 chars for "Authorization: Basic ". - */ - if (t->auth_hdr.len < 21 || memcmp(t->auth_hdr.str + 14, " Basic ", 7)) - user = NULL; - - while (user) { - if ((t->auth_hdr.len == user->user_len + 21) - && !memcmp(t->auth_hdr.str+21, user->user_pwd, user->user_len)) { - authenticated = 1; - break; - } - user = user->next; - } - } - - if (!authenticated) { - int msglen; - - /* no need to go further */ - - msglen = sprintf(trash, HTTP_401_fmt, t->proxy->uri_auth->auth_realm); - t->logs.status = 401; - client_retnclose(t, msglen, trash); - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_PRXCOND; - if (!(t->flags & SN_FINST_MASK)) - t->flags |= SN_FINST_R; - return 1; - } - - t->cli_state = CL_STSHUTR; - req->rlim = req->data + BUFSIZE; /* no more rewrite needed */ - t->logs.t_request = tv_diff(&t->logs.tv_accept, &now); - t->data_source = DATA_SRC_STATS; - t->data_state = DATA_ST_INIT; - produce_content(t); - return 1; - } - } - - - for (line = 0; line < t->proxy->nb_reqadd; line++) { - len = sprintf(trash, "%s\r\n", t->proxy->req_add[line]); - buffer_replace2(req, req->h, req->h, trash, len); - } - - if (t->proxy->options & PR_O_FWDFOR) { - if (t->cli_addr.ss_family == AF_INET) { - unsigned char *pn; - pn = (unsigned char *)&((struct sockaddr_in *)&t->cli_addr)->sin_addr; - len = sprintf(trash, "X-Forwarded-For: %d.%d.%d.%d\r\n", - pn[0], pn[1], pn[2], pn[3]); - buffer_replace2(req, req->h, req->h, trash, len); - } - else if (t->cli_addr.ss_family == AF_INET6) { - char pn[INET6_ADDRSTRLEN]; - inet_ntop(AF_INET6, - (const void *)&((struct sockaddr_in6 *)(&t->cli_addr))->sin6_addr, - pn, sizeof(pn)); - len = sprintf(trash, "X-Forwarded-For: %s\r\n", pn); - buffer_replace2(req, req->h, req->h, trash, len); - } - } - - /* add a "connection: close" line if needed */ - if (t->proxy->options & PR_O_HTTP_CLOSE) - buffer_replace2(req, req->h, req->h, "Connection: close\r\n", 19); - - if (!memcmp(req->data, "POST ", 5)) { - /* this is a POST request, which is not cacheable by default */ - t->flags |= SN_POST; - } - - t->cli_state = CL_STDATA; - req->rlim = req->data + BUFSIZE; /* no more rewrite needed */ - - t->logs.t_request = tv_diff(&t->logs.tv_accept, &now); - /* FIXME: we'll set the client in a wait state while we try to - * connect to the server. Is this really needed ? wouldn't it be - * better to release the maximum of system buffers instead ? - * The solution is to enable the FD but set its time-out to - * eternity as long as the server-side does not enable data xfer. - * CL_STDATA also has to take care of this, which is done below. - */ - //FD_CLR(t->cli_fd, StaticReadEvent); - //tv_eternity(&t->crexpire); - - /* FIXME: if we break here (as up to 1.1.23), having the client - * shutdown its connection can lead to an abort further. - * it's better to either return 1 or even jump directly to the - * data state which will save one schedule. - */ - //break; - - if (!t->proxy->clitimeout || - (t->srv_state < SV_STDATA && t->proxy->srvtimeout)) - /* If the client has no timeout, or if the server is not ready yet, - * and we know for sure that it can expire, then it's cleaner to - * disable the timeout on the client side so that too low values - * cannot make the sessions abort too early. - * - * FIXME-20050705: the server needs a way to re-enable this time-out - * when it switches its state, otherwise a client can stay connected - * indefinitely. This now seems to be OK. - */ - tv_eternity(&t->crexpire); - - goto process_data; - } - - /* to get a complete header line, we need the ending \r\n, \n\r, \r or \n too */ - if (ptr > req->r - 2) { - /* this is a partial header, let's wait for more to come */ - req->lr = ptr; - break; - } - - /* now we know that *ptr is either \r or \n, - * and that there are at least 1 char after it. - */ - if ((ptr[0] == ptr[1]) || (ptr[1] != '\r' && ptr[1] != '\n')) - req->lr = ptr + 1; /* \r\r, \n\n, \r[^\n], \n[^\r] */ - else - req->lr = ptr + 2; /* \r\n or \n\r */ - - /* - * now we know that we have a full header ; we can do whatever - * we want with these pointers : - * req->h = beginning of header - * ptr = end of header (first \r or \n) - * req->lr = beginning of next line (next rep->h) - * req->r = end of data (not used at this stage) - */ - - if (!method_checked && (t->proxy->appsession_name != NULL) && - ((memcmp(req->h, "GET ", 4) == 0) || (memcmp(req->h, "POST ", 4) == 0)) && - ((request_line = memchr(req->h, ';', req->lr - req->h)) != NULL)) { - - /* skip ; */ - request_line++; - - /* look if we have a jsessionid */ - - if (strncasecmp(request_line, t->proxy->appsession_name, t->proxy->appsession_name_len) == 0) { - - /* skip jsessionid= */ - request_line += t->proxy->appsession_name_len + 1; - - /* First try if we allready have an appsession */ - asession_temp = &local_asession; - - if ((asession_temp->sessid = pool_alloc_from(apools.sessid, apools.ses_msize)) == NULL) { - Alert("Not enough memory process_cli():asession_temp->sessid:calloc().\n"); - send_log(t->proxy, LOG_ALERT, "Not enough Memory process_cli():asession_temp->sessid:calloc().\n"); - return 0; - } - - /* Copy the sessionid */ - memcpy(asession_temp->sessid, request_line, t->proxy->appsession_len); - asession_temp->sessid[t->proxy->appsession_len] = 0; - asession_temp->serverid = NULL; - - /* only do insert, if lookup fails */ - if (chtbl_lookup(&(t->proxy->htbl_proxy), (void *)&asession_temp)) { - if ((asession_temp = pool_alloc(appsess)) == NULL) { - Alert("Not enough memory process_cli():asession:calloc().\n"); - send_log(t->proxy, LOG_ALERT, "Not enough memory process_cli():asession:calloc().\n"); - return 0; - } - asession_temp->sessid = local_asession.sessid; - asession_temp->serverid = local_asession.serverid; - chtbl_insert(&(t->proxy->htbl_proxy), (void *) asession_temp); - } /* end if (chtbl_lookup()) */ - else { - /*free wasted memory;*/ - pool_free_to(apools.sessid, local_asession.sessid); - } - - tv_delayfrom(&asession_temp->expire, &now, t->proxy->appsession_timeout); - asession_temp->request_count++; - -#if defined(DEBUG_HASH) - print_table(&(t->proxy->htbl_proxy)); -#endif - - if (asession_temp->serverid == NULL) { - Alert("Found Application Session without matching server.\n"); - } else { - struct server *srv = t->proxy->srv; - while (srv) { - if (strcmp(srv->id, asession_temp->serverid) == 0) { - if (srv->state & SRV_RUNNING || t->proxy->options & PR_O_PERSIST) { - /* we found the server and it's usable */ - t->flags &= ~SN_CK_MASK; - t->flags |= SN_CK_VALID | SN_DIRECT | SN_ASSIGNED; - t->srv = srv; - break; - } else { - t->flags &= ~SN_CK_MASK; - t->flags |= SN_CK_DOWN; - } - } /* end if (strcmp()) */ - srv = srv->next; - }/* end while(srv) */ - }/* end else of if (asession_temp->serverid == NULL) */ - }/* end if (strncasecmp(request_line,t->proxy->appsession_name,apssesion_name_len) == 0) */ - else { - //fprintf(stderr,">>>>>>>>>>>>>>>>>>>>>>NO SESSION\n"); - } - method_checked = 1; - } /* end if (!method_checked ...) */ - else{ - //printf("No Methode-Header with Session-String\n"); - } - - if (t->logs.logwait & LW_REQ) { - /* we have a complete HTTP request that we must log */ - int urilen; - - if ((t->logs.uri = pool_alloc(requri)) == NULL) { - Alert("HTTP logging : out of memory.\n"); - t->logs.status = 500; - client_retnclose(t, t->proxy->errmsg.len500, t->proxy->errmsg.msg500); - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_PRXCOND; - if (!(t->flags & SN_FINST_MASK)) - t->flags |= SN_FINST_R; - return 1; - } - - urilen = ptr - req->h; - if (urilen >= REQURI_LEN) - urilen = REQURI_LEN - 1; - memcpy(t->logs.uri, req->h, urilen); - t->logs.uri[urilen] = 0; - - if (!(t->logs.logwait &= ~LW_REQ)) - sess_log(t); - } - else if (t->logs.logwait & LW_REQHDR) { - struct cap_hdr *h; - int len; - for (h = t->proxy->req_cap; h; h = h->next) { - if ((h->namelen + 2 <= ptr - req->h) && - (req->h[h->namelen] == ':') && - (strncasecmp(req->h, h->name, h->namelen) == 0)) { - - if (t->req_cap[h->index] == NULL) - t->req_cap[h->index] = pool_alloc_from(h->pool, h->len + 1); - - len = ptr - (req->h + h->namelen + 2); - if (len > h->len) - len = h->len; - - memcpy(t->req_cap[h->index], req->h + h->namelen + 2, len); - t->req_cap[h->index][len]=0; - } - } - - } - - delete_header = 0; - - if ((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) { - int len, max; - len = sprintf(trash, "%08x:%s.clihdr[%04x:%04x]: ", t->uniq_id, t->proxy->id, (unsigned short)t->cli_fd, (unsigned short)t->srv_fd); - max = ptr - req->h; - UBOUND(max, sizeof(trash) - len - 1); - len += strlcpy2(trash + len, req->h, max + 1); - trash[len++] = '\n'; - write(1, trash, len); - } - - - /* remove "connection: " if needed */ - if (!delete_header && (t->proxy->options & PR_O_HTTP_CLOSE) - && (strncasecmp(req->h, "Connection: ", 12) == 0)) { - delete_header = 1; - } - - /* try headers regexps */ - if (!delete_header && t->proxy->req_exp != NULL - && !(t->flags & SN_CLDENY)) { - struct hdr_exp *exp; - char term; - - term = *ptr; - *ptr = '\0'; - exp = t->proxy->req_exp; - do { - if (regexec(exp->preg, req->h, MAX_MATCH, pmatch, 0) == 0) { - switch (exp->action) { - case ACT_ALLOW: - if (!(t->flags & SN_CLDENY)) - t->flags |= SN_CLALLOW; - break; - case ACT_REPLACE: - if (!(t->flags & SN_CLDENY)) { - int len = exp_replace(trash, req->h, exp->replace, pmatch); - ptr += buffer_replace2(req, req->h, ptr, trash, len); - } - break; - case ACT_REMOVE: - if (!(t->flags & SN_CLDENY)) - delete_header = 1; - break; - case ACT_DENY: - if (!(t->flags & SN_CLALLOW)) - t->flags |= SN_CLDENY; - break; - case ACT_PASS: /* we simply don't deny this one */ - break; - } - break; - } - } while ((exp = exp->next) != NULL); - *ptr = term; /* restore the string terminator */ - } - - /* Now look for cookies. Conforming to RFC2109, we have to support - * attributes whose name begin with a '$', and associate them with - * the right cookie, if we want to delete this cookie. - * So there are 3 cases for each cookie read : - * 1) it's a special attribute, beginning with a '$' : ignore it. - * 2) it's a server id cookie that we *MAY* want to delete : save - * some pointers on it (last semi-colon, beginning of cookie...) - * 3) it's an application cookie : we *MAY* have to delete a previous - * "special" cookie. - * At the end of loop, if a "special" cookie remains, we may have to - * remove it. If no application cookie persists in the header, we - * *MUST* delete it - */ - if (!delete_header && - (t->proxy->cookie_name != NULL || t->proxy->capture_name != NULL || t->proxy->appsession_name !=NULL) - && !(t->flags & SN_CLDENY) && (ptr >= req->h + 8) - && (strncasecmp(req->h, "Cookie: ", 8) == 0)) { - char *p1, *p2, *p3, *p4; - char *del_colon, *del_cookie, *colon; - int app_cookies; - - p1 = req->h + 8; /* first char after 'Cookie: ' */ - colon = p1; - /* del_cookie == NULL => nothing to be deleted */ - del_colon = del_cookie = NULL; - app_cookies = 0; - - while (p1 < ptr) { - /* skip spaces and colons, but keep an eye on these ones */ - while (p1 < ptr) { - if (*p1 == ';' || *p1 == ',') - colon = p1; - else if (!isspace((int)*p1)) - break; - p1++; - } - - if (p1 == ptr) - break; - - /* p1 is at the beginning of the cookie name */ - p2 = p1; - while (p2 < ptr && *p2 != '=') - p2++; - - if (p2 == ptr) - break; - - p3 = p2 + 1; /* skips the '=' sign */ - if (p3 == ptr) - break; - - p4 = p3; - while (p4 < ptr && !isspace((int)*p4) && *p4 != ';' && *p4 != ',') - p4++; - - /* here, we have the cookie name between p1 and p2, - * and its value between p3 and p4. - * we can process it : - * - * Cookie: NAME=VALUE; - * | || || | - * | || || +--> p4 - * | || |+-------> p3 - * | || +--------> p2 - * | |+------------> p1 - * | +-------------> colon - * +--------------------> req->h - */ - - if (*p1 == '$') { - /* skip this one */ - } - else { - /* first, let's see if we want to capture it */ - if (t->proxy->capture_name != NULL && - t->logs.cli_cookie == NULL && - (p4 - p1 >= t->proxy->capture_namelen) && - memcmp(p1, t->proxy->capture_name, t->proxy->capture_namelen) == 0) { - int log_len = p4 - p1; - - if ((t->logs.cli_cookie = pool_alloc(capture)) == NULL) { - Alert("HTTP logging : out of memory.\n"); - } else { - if (log_len > t->proxy->capture_len) - log_len = t->proxy->capture_len; - memcpy(t->logs.cli_cookie, p1, log_len); - t->logs.cli_cookie[log_len] = 0; - } - } - - if ((p2 - p1 == t->proxy->cookie_len) && (t->proxy->cookie_name != NULL) && - (memcmp(p1, t->proxy->cookie_name, p2 - p1) == 0)) { - /* Cool... it's the right one */ - struct server *srv = t->proxy->srv; - char *delim; - - /* if we're in cookie prefix mode, we'll search the delimitor so that we - * have the server ID betweek p3 and delim, and the original cookie between - * delim+1 and p4. Otherwise, delim==p4 : - * - * Cookie: NAME=SRV~VALUE; - * | || || | | - * | || || | +--> p4 - * | || || +--------> delim - * | || |+-----------> p3 - * | || +------------> p2 - * | |+----------------> p1 - * | +-----------------> colon - * +------------------------> req->h - */ - - if (t->proxy->options & PR_O_COOK_PFX) { - for (delim = p3; delim < p4; delim++) - if (*delim == COOKIE_DELIM) - break; - } - else - delim = p4; - - - /* Here, we'll look for the first running server which supports the cookie. - * This allows to share a same cookie between several servers, for example - * to dedicate backup servers to specific servers only. - * However, to prevent clients from sticking to cookie-less backup server - * when they have incidentely learned an empty cookie, we simply ignore - * empty cookies and mark them as invalid. - */ - if (delim == p3) - srv = NULL; - - while (srv) { - if ((srv->cklen == delim - p3) && !memcmp(p3, srv->cookie, delim - p3)) { - if (srv->state & SRV_RUNNING || t->proxy->options & PR_O_PERSIST) { - /* we found the server and it's usable */ - t->flags &= ~SN_CK_MASK; - t->flags |= SN_CK_VALID | SN_DIRECT | SN_ASSIGNED; - t->srv = srv; - break; - } else { - /* we found a server, but it's down */ - t->flags &= ~SN_CK_MASK; - t->flags |= SN_CK_DOWN; - } - } - srv = srv->next; - } - - if (!srv && !(t->flags & SN_CK_DOWN)) { - /* no server matched this cookie */ - t->flags &= ~SN_CK_MASK; - t->flags |= SN_CK_INVALID; - } - - /* depending on the cookie mode, we may have to either : - * - delete the complete cookie if we're in insert+indirect mode, so that - * the server never sees it ; - * - remove the server id from the cookie value, and tag the cookie as an - * application cookie so that it does not get accidentely removed later, - * if we're in cookie prefix mode - */ - if ((t->proxy->options & PR_O_COOK_PFX) && (delim != p4)) { - buffer_replace2(req, p3, delim + 1, NULL, 0); - p4 -= (delim + 1 - p3); - ptr -= (delim + 1 - p3); - del_cookie = del_colon = NULL; - app_cookies++; /* protect the header from deletion */ - } - else if (del_cookie == NULL && - (t->proxy->options & (PR_O_COOK_INS | PR_O_COOK_IND)) == (PR_O_COOK_INS | PR_O_COOK_IND)) { - del_cookie = p1; - del_colon = colon; - } - } else { - /* now we know that we must keep this cookie since it's - * not ours. But if we wanted to delete our cookie - * earlier, we cannot remove the complete header, but we - * can remove the previous block itself. - */ - app_cookies++; - - if (del_cookie != NULL) { - buffer_replace2(req, del_cookie, p1, NULL, 0); - p4 -= (p1 - del_cookie); - ptr -= (p1 - del_cookie); - del_cookie = del_colon = NULL; - } - } - - if ((t->proxy->appsession_name != NULL) && - (memcmp(p1, t->proxy->appsession_name, p2 - p1) == 0)) { - /* first, let's see if the cookie is our appcookie*/ - - /* Cool... it's the right one */ - - asession_temp = &local_asession; - - if ((asession_temp->sessid = pool_alloc_from(apools.sessid, apools.ses_msize)) == NULL) { - Alert("Not enough memory process_cli():asession->sessid:malloc().\n"); - send_log(t->proxy, LOG_ALERT, "Not enough memory process_cli():asession->sessid:malloc().\n"); - return 0; - } - - memcpy(asession_temp->sessid, p3, t->proxy->appsession_len); - asession_temp->sessid[t->proxy->appsession_len] = 0; - asession_temp->serverid = NULL; - - /* only do insert, if lookup fails */ - if (chtbl_lookup(&(t->proxy->htbl_proxy), (void *) &asession_temp) != 0) { - if ((asession_temp = pool_alloc(appsess)) == NULL) { - Alert("Not enough memory process_cli():asession:calloc().\n"); - send_log(t->proxy, LOG_ALERT, "Not enough memory process_cli():asession:calloc().\n"); - return 0; - } - - asession_temp->sessid = local_asession.sessid; - asession_temp->serverid = local_asession.serverid; - chtbl_insert(&(t->proxy->htbl_proxy), (void *) asession_temp); - } - else{ - /* free wasted memory */ - pool_free_to(apools.sessid, local_asession.sessid); - } - - if (asession_temp->serverid == NULL) { - Alert("Found Application Session without matching server.\n"); - } else { - struct server *srv = t->proxy->srv; - while (srv) { - if (strcmp(srv->id, asession_temp->serverid) == 0) { - if (srv->state & SRV_RUNNING || t->proxy->options & PR_O_PERSIST) { - /* we found the server and it's usable */ - t->flags &= ~SN_CK_MASK; - t->flags |= SN_CK_VALID | SN_DIRECT | SN_ASSIGNED; - t->srv = srv; - break; - } else { - t->flags &= ~SN_CK_MASK; - t->flags |= SN_CK_DOWN; - } - } - srv = srv->next; - }/* end while(srv) */ - }/* end else if server == NULL */ - - tv_delayfrom(&asession_temp->expire, &now, t->proxy->appsession_timeout); - }/* end if ((t->proxy->appsession_name != NULL) ... */ - } - - /* we'll have to look for another cookie ... */ - p1 = p4; - } /* while (p1 < ptr) */ - - /* There's no more cookie on this line. - * We may have marked the last one(s) for deletion. - * We must do this now in two ways : - * - if there is no app cookie, we simply delete the header ; - * - if there are app cookies, we must delete the end of the - * string properly, including the colon/semi-colon before - * the cookie name. - */ - if (del_cookie != NULL) { - if (app_cookies) { - buffer_replace2(req, del_colon, ptr, NULL, 0); - /* WARNING! becomes invalid for now. If some code - * below needs to rely on it before the end of the global - * header loop, we need to correct it with this code : - */ - ptr = del_colon; - } - else - delete_header = 1; - } - } /* end of cookie processing on this header */ - - /* let's look if we have to delete this header */ - if (delete_header && !(t->flags & SN_CLDENY)) { - buffer_replace2(req, req->h, req->lr, NULL, 0); - /* WARNING: ptr is not valid anymore, since the header may have - * been deleted or truncated ! */ - } else { - /* try to catch the first line as the request */ - if (t->req_line.len < 0) { - t->req_line.str = req->h; - t->req_line.len = ptr - req->h; - } - - /* We might also need the 'Authorization: ' header */ - if (t->auth_hdr.len < 0 && - t->proxy->uri_auth != NULL && - ptr > req->h + 15 && - !strncasecmp("Authorization: ", req->h, 15)) { - t->auth_hdr.str = req->h; - t->auth_hdr.len = ptr - req->h; - } - } - - req->h = req->lr; - } /* while (req->lr < req->r) */ - - /* end of header processing (even if incomplete) */ - - if ((req->l < req->rlim - req->data) && ! FD_ISSET(t->cli_fd, StaticReadEvent)) { - /* fd in StaticReadEvent was disabled, perhaps because of a previous buffer - * full. We cannot loop here since event_cli_read will disable it only if - * req->l == rlim-data - */ - FD_SET(t->cli_fd, StaticReadEvent); - if (t->proxy->clitimeout) - tv_delayfrom(&t->crexpire, &now, t->proxy->clitimeout); - else - tv_eternity(&t->crexpire); - } - - /* Since we are in header mode, if there's no space left for headers, we - * won't be able to free more later, so the session will never terminate. - */ - if (req->l >= req->rlim - req->data) { - t->logs.status = 400; - client_retnclose(t, t->proxy->errmsg.len400, t->proxy->errmsg.msg400); - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_PRXCOND; - if (!(t->flags & SN_FINST_MASK)) - t->flags |= SN_FINST_R; - return 1; - } - else if (t->res_cr == RES_ERROR || t->res_cr == RES_NULL) { - /* read error, or last read : give up. */ - tv_eternity(&t->crexpire); - fd_delete(t->cli_fd); - t->cli_state = CL_STCLOSE; - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_CLICL; - if (!(t->flags & SN_FINST_MASK)) - t->flags |= SN_FINST_R; - return 1; - } - else if (tv_cmp2_ms(&t->crexpire, &now) <= 0) { - - /* read timeout : give up with an error message. - */ - t->logs.status = 408; - client_retnclose(t, t->proxy->errmsg.len408, t->proxy->errmsg.msg408); - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_CLITO; - if (!(t->flags & SN_FINST_MASK)) - t->flags |= SN_FINST_R; - return 1; - } - - return t->cli_state != CL_STHEADERS; - } - else if (c == CL_STDATA) { - process_data: - /* FIXME: this error handling is partly buggy because we always report - * a 'DATA' phase while we don't know if the server was in IDLE, CONN - * or HEADER phase. BTW, it's not logical to expire the client while - * we're waiting for the server to connect. - */ - /* read or write error */ - if (t->res_cw == RES_ERROR || t->res_cr == RES_ERROR) { - tv_eternity(&t->crexpire); - tv_eternity(&t->cwexpire); - fd_delete(t->cli_fd); - t->cli_state = CL_STCLOSE; - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_CLICL; - if (!(t->flags & SN_FINST_MASK)) { - if (t->pend_pos) - t->flags |= SN_FINST_Q; - else if (s == SV_STCONN) - t->flags |= SN_FINST_C; - else - t->flags |= SN_FINST_D; - } - return 1; - } - /* last read, or end of server write */ - else if (t->res_cr == RES_NULL || s == SV_STSHUTW || s == SV_STCLOSE) { - FD_CLR(t->cli_fd, StaticReadEvent); - tv_eternity(&t->crexpire); - shutdown(t->cli_fd, SHUT_RD); - t->cli_state = CL_STSHUTR; - return 1; - } - /* last server read and buffer empty */ - else if ((s == SV_STSHUTR || s == SV_STCLOSE) && (rep->l == 0)) { - FD_CLR(t->cli_fd, StaticWriteEvent); - tv_eternity(&t->cwexpire); - shutdown(t->cli_fd, SHUT_WR); - /* We must ensure that the read part is still alive when switching - * to shutw */ - FD_SET(t->cli_fd, StaticReadEvent); - if (t->proxy->clitimeout) - tv_delayfrom(&t->crexpire, &now, t->proxy->clitimeout); - t->cli_state = CL_STSHUTW; - //fprintf(stderr,"%p:%s(%d), c=%d, s=%d\n", t, __FUNCTION__, __LINE__, t->cli_state, t->cli_state); - return 1; - } - /* read timeout */ - else if (tv_cmp2_ms(&t->crexpire, &now) <= 0) { - FD_CLR(t->cli_fd, StaticReadEvent); - tv_eternity(&t->crexpire); - shutdown(t->cli_fd, SHUT_RD); - t->cli_state = CL_STSHUTR; - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_CLITO; - if (!(t->flags & SN_FINST_MASK)) { - if (t->pend_pos) - t->flags |= SN_FINST_Q; - else if (s == SV_STCONN) - t->flags |= SN_FINST_C; - else - t->flags |= SN_FINST_D; - } - return 1; - } - /* write timeout */ - else if (tv_cmp2_ms(&t->cwexpire, &now) <= 0) { - FD_CLR(t->cli_fd, StaticWriteEvent); - tv_eternity(&t->cwexpire); - shutdown(t->cli_fd, SHUT_WR); - /* We must ensure that the read part is still alive when switching - * to shutw */ - FD_SET(t->cli_fd, StaticReadEvent); - if (t->proxy->clitimeout) - tv_delayfrom(&t->crexpire, &now, t->proxy->clitimeout); - - t->cli_state = CL_STSHUTW; - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_CLITO; - if (!(t->flags & SN_FINST_MASK)) { - if (t->pend_pos) - t->flags |= SN_FINST_Q; - else if (s == SV_STCONN) - t->flags |= SN_FINST_C; - else - t->flags |= SN_FINST_D; - } - return 1; - } - - if (req->l >= req->rlim - req->data) { - /* no room to read more data */ - if (FD_ISSET(t->cli_fd, StaticReadEvent)) { - /* stop reading until we get some space */ - FD_CLR(t->cli_fd, StaticReadEvent); - tv_eternity(&t->crexpire); - } - } - else { - /* there's still some space in the buffer */ - if (! FD_ISSET(t->cli_fd, StaticReadEvent)) { - FD_SET(t->cli_fd, StaticReadEvent); - if (!t->proxy->clitimeout || - (t->srv_state < SV_STDATA && t->proxy->srvtimeout)) - /* If the client has no timeout, or if the server not ready yet, and we - * know for sure that it can expire, then it's cleaner to disable the - * timeout on the client side so that too low values cannot make the - * sessions abort too early. - */ - tv_eternity(&t->crexpire); - else - tv_delayfrom(&t->crexpire, &now, t->proxy->clitimeout); - } - } - - if ((rep->l == 0) || - ((s < SV_STDATA) /* FIXME: this may be optimized && (rep->w == rep->h)*/)) { - if (FD_ISSET(t->cli_fd, StaticWriteEvent)) { - FD_CLR(t->cli_fd, StaticWriteEvent); /* stop writing */ - tv_eternity(&t->cwexpire); - } - } - else { /* buffer not empty */ - if (! FD_ISSET(t->cli_fd, StaticWriteEvent)) { - FD_SET(t->cli_fd, StaticWriteEvent); /* restart writing */ - if (t->proxy->clitimeout) { - tv_delayfrom(&t->cwexpire, &now, t->proxy->clitimeout); - /* FIXME: to prevent the client from expiring read timeouts during writes, - * we refresh it. */ - t->crexpire = t->cwexpire; - } - else - tv_eternity(&t->cwexpire); - } - } - return 0; /* other cases change nothing */ - } - else if (c == CL_STSHUTR) { - if (t->res_cw == RES_ERROR) { - tv_eternity(&t->cwexpire); - fd_delete(t->cli_fd); - t->cli_state = CL_STCLOSE; - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_CLICL; - if (!(t->flags & SN_FINST_MASK)) { - if (t->pend_pos) - t->flags |= SN_FINST_Q; - else if (s == SV_STCONN) - t->flags |= SN_FINST_C; - else - t->flags |= SN_FINST_D; - } - return 1; - } - else if ((s == SV_STSHUTR || s == SV_STCLOSE) && (rep->l == 0) - && !(t->flags & SN_SELF_GEN)) { - tv_eternity(&t->cwexpire); - fd_delete(t->cli_fd); - t->cli_state = CL_STCLOSE; - return 1; - } - else if (tv_cmp2_ms(&t->cwexpire, &now) <= 0) { - tv_eternity(&t->cwexpire); - fd_delete(t->cli_fd); - t->cli_state = CL_STCLOSE; - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_CLITO; - if (!(t->flags & SN_FINST_MASK)) { - if (t->pend_pos) - t->flags |= SN_FINST_Q; - else if (s == SV_STCONN) - t->flags |= SN_FINST_C; - else - t->flags |= SN_FINST_D; - } - return 1; - } - - if (t->flags & SN_SELF_GEN) { - produce_content(t); - if (rep->l == 0) { - tv_eternity(&t->cwexpire); - fd_delete(t->cli_fd); - t->cli_state = CL_STCLOSE; - return 1; - } - } - - if ((rep->l == 0) - || ((s == SV_STHEADERS) /* FIXME: this may be optimized && (rep->w == rep->h)*/)) { - if (FD_ISSET(t->cli_fd, StaticWriteEvent)) { - FD_CLR(t->cli_fd, StaticWriteEvent); /* stop writing */ - tv_eternity(&t->cwexpire); - } - } - else { /* buffer not empty */ - if (! FD_ISSET(t->cli_fd, StaticWriteEvent)) { - FD_SET(t->cli_fd, StaticWriteEvent); /* restart writing */ - if (t->proxy->clitimeout) { - tv_delayfrom(&t->cwexpire, &now, t->proxy->clitimeout); - /* FIXME: to prevent the client from expiring read timeouts during writes, - * we refresh it. */ - t->crexpire = t->cwexpire; - } - else - tv_eternity(&t->cwexpire); - } - } - return 0; - } - else if (c == CL_STSHUTW) { - if (t->res_cr == RES_ERROR) { - tv_eternity(&t->crexpire); - fd_delete(t->cli_fd); - t->cli_state = CL_STCLOSE; - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_CLICL; - if (!(t->flags & SN_FINST_MASK)) { - if (t->pend_pos) - t->flags |= SN_FINST_Q; - else if (s == SV_STCONN) - t->flags |= SN_FINST_C; - else - t->flags |= SN_FINST_D; - } - return 1; - } - else if (t->res_cr == RES_NULL || s == SV_STSHUTW || s == SV_STCLOSE) { - tv_eternity(&t->crexpire); - fd_delete(t->cli_fd); - t->cli_state = CL_STCLOSE; - return 1; - } - else if (tv_cmp2_ms(&t->crexpire, &now) <= 0) { - tv_eternity(&t->crexpire); - fd_delete(t->cli_fd); - t->cli_state = CL_STCLOSE; - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_CLITO; - if (!(t->flags & SN_FINST_MASK)) { - if (t->pend_pos) - t->flags |= SN_FINST_Q; - else if (s == SV_STCONN) - t->flags |= SN_FINST_C; - else - t->flags |= SN_FINST_D; - } - return 1; - } - else if (req->l >= req->rlim - req->data) { - /* no room to read more data */ - - /* FIXME-20050705: is it possible for a client to maintain a session - * after the timeout by sending more data after it receives a close ? - */ - - if (FD_ISSET(t->cli_fd, StaticReadEvent)) { - /* stop reading until we get some space */ - FD_CLR(t->cli_fd, StaticReadEvent); - tv_eternity(&t->crexpire); - //fprintf(stderr,"%p:%s(%d), c=%d, s=%d\n", t, __FUNCTION__, __LINE__, t->cli_state, t->cli_state); - } - } - else { - /* there's still some space in the buffer */ - if (! FD_ISSET(t->cli_fd, StaticReadEvent)) { - FD_SET(t->cli_fd, StaticReadEvent); - if (t->proxy->clitimeout) - tv_delayfrom(&t->crexpire, &now, t->proxy->clitimeout); - else - tv_eternity(&t->crexpire); - //fprintf(stderr,"%p:%s(%d), c=%d, s=%d\n", t, __FUNCTION__, __LINE__, t->cli_state, t->cli_state); - } - } - return 0; - } - else { /* CL_STCLOSE: nothing to do */ - if ((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) { - int len; - len = sprintf(trash, "%08x:%s.clicls[%04x:%04x]\n", t->uniq_id, t->proxy->id, (unsigned short)t->cli_fd, (unsigned short)t->srv_fd); - write(1, trash, len); - } - return 0; - } - return 0; -} - -/* This function turns the server state into the SV_STCLOSE, and sets - * indicators accordingly. Note that if is 0, no message is - * returned. - */ -void srv_close_with_err(struct session *t, int err, int finst, int status, int msglen, char *msg) { - t->srv_state = SV_STCLOSE; - if (status > 0) { - t->logs.status = status; - if (t->proxy->mode == PR_MODE_HTTP) - client_return(t, msglen, msg); - } - if (!(t->flags & SN_ERR_MASK)) - t->flags |= err; - if (!(t->flags & SN_FINST_MASK)) - t->flags |= finst; -} - -/* - * This function checks the retry count during the connect() job. - * It updates the session's srv_state and retries, so that the caller knows - * what it has to do. It uses the last connection error to set the log when - * it expires. It returns 1 when it has expired, and 0 otherwise. - */ -int srv_count_retry_down(struct session *t, int conn_err) { - /* we are in front of a retryable error */ - t->conn_retries--; - if (t->conn_retries < 0) { - /* if not retryable anymore, let's abort */ - tv_eternity(&t->cnexpire); - srv_close_with_err(t, conn_err, SN_FINST_C, - 503, t->proxy->errmsg.len503, t->proxy->errmsg.msg503); - if (t->srv) - t->srv->failed_conns++; - t->proxy->failed_conns++; - - /* We used to have a free connection slot. Since we'll never use it, - * we have to inform the server that it may be used by another session. - */ - if (may_dequeue_tasks(t->srv, t->proxy)) - task_wakeup(&rq, t->srv->queue_mgt); - return 1; - } - return 0; -} - -/* - * This function performs the retryable part of the connect() job. - * It updates the session's srv_state and retries, so that the caller knows - * what it has to do. It returns 1 when it breaks out of the loop, or 0 if - * it needs to redispatch. - */ -int srv_retryable_connect(struct session *t) { - int conn_err; - - /* This loop ensures that we stop before the last retry in case of a - * redispatchable server. - */ - do { - /* initiate a connection to the server */ - conn_err = connect_server(t); - switch (conn_err) { - - case SN_ERR_NONE: - //fprintf(stderr,"0: c=%d, s=%d\n", c, s); - t->srv_state = SV_STCONN; - return 1; - - case SN_ERR_INTERNAL: - tv_eternity(&t->cnexpire); - srv_close_with_err(t, SN_ERR_INTERNAL, SN_FINST_C, - 500, t->proxy->errmsg.len500, t->proxy->errmsg.msg500); - if (t->srv) - t->srv->failed_conns++; - t->proxy->failed_conns++; - /* release other sessions waiting for this server */ - if (may_dequeue_tasks(t->srv, t->proxy)) - task_wakeup(&rq, t->srv->queue_mgt); - return 1; - } - /* ensure that we have enough retries left */ - if (srv_count_retry_down(t, conn_err)) { - /* let's try to offer this slot to anybody */ - if (may_dequeue_tasks(t->srv, t->proxy)) - task_wakeup(&rq, t->srv->queue_mgt); - return 1; - } - } while (t->srv == NULL || t->conn_retries > 0 || !(t->proxy->options & PR_O_REDISP)); - - /* We're on our last chance, and the REDISP option was specified. - * We will ignore cookie and force to balance or use the dispatcher. - */ - /* let's try to offer this slot to anybody */ - if (may_dequeue_tasks(t->srv, t->proxy)) - task_wakeup(&rq, t->srv->queue_mgt); - - if (t->srv) - t->srv->failed_conns++; - t->proxy->failed_conns++; - - t->flags &= ~(SN_DIRECT | SN_ASSIGNED | SN_ADDR_SET); - t->srv = NULL; /* it's left to the dispatcher to choose a server */ - if ((t->flags & SN_CK_MASK) == SN_CK_VALID) { - t->flags &= ~SN_CK_MASK; - t->flags |= SN_CK_DOWN; - } - return 0; -} - -/* This function performs the "redispatch" part of a connection attempt. It - * will assign a server if required, queue the connection if required, and - * handle errors that might arise at this level. It can change the server - * state. It will return 1 if it encounters an error, switches the server - * state, or has to queue a connection. Otherwise, it will return 0 indicating - * that the connection is ready to use. - */ - -int srv_redispatch_connect(struct session *t) { - int conn_err; - - /* We know that we don't have any connection pending, so we will - * try to get a new one, and wait in this state if it's queued - */ - conn_err = assign_server_and_queue(t); - switch (conn_err) { - case SRV_STATUS_OK: - break; - - case SRV_STATUS_NOSRV: - /* note: it is guaranteed that t->srv == NULL here */ - tv_eternity(&t->cnexpire); - srv_close_with_err(t, SN_ERR_SRVTO, SN_FINST_C, - 503, t->proxy->errmsg.len503, t->proxy->errmsg.msg503); - if (t->srv) - t->srv->failed_conns++; - t->proxy->failed_conns++; - - return 1; - - case SRV_STATUS_QUEUED: - /* FIXME-20060503 : we should use the queue timeout instead */ - if (t->proxy->contimeout) - tv_delayfrom(&t->cnexpire, &now, t->proxy->contimeout); - else - tv_eternity(&t->cnexpire); - t->srv_state = SV_STIDLE; - /* do nothing else and do not wake any other session up */ - return 1; - - case SRV_STATUS_FULL: - case SRV_STATUS_INTERNAL: - default: - tv_eternity(&t->cnexpire); - srv_close_with_err(t, SN_ERR_INTERNAL, SN_FINST_C, - 500, t->proxy->errmsg.len500, t->proxy->errmsg.msg500); - if (t->srv) - t->srv->failed_conns++; - t->proxy->failed_conns++; - - /* release other sessions waiting for this server */ - if (may_dequeue_tasks(t->srv, t->proxy)) - task_wakeup(&rq, t->srv->queue_mgt); - return 1; - } - /* if we get here, it's because we got SRV_STATUS_OK, which also - * means that the connection has not been queued. - */ - return 0; -} - - -/* - * manages the server FSM and its socket. It returns 1 if a state has changed - * (and a resync may be needed), 0 else. - */ -int process_srv(struct session *t) { - int s = t->srv_state; - int c = t->cli_state; - struct buffer *req = t->req; - struct buffer *rep = t->rep; - appsess *asession_temp = NULL; - appsess local_asession; - int conn_err; - -#ifdef DEBUG_FULL - fprintf(stderr,"process_srv: c=%s, s=%s\n", cli_stnames[c], srv_stnames[s]); -#endif - //fprintf(stderr,"process_srv: c=%d, s=%d, cr=%d, cw=%d, sr=%d, sw=%d\n", c, s, - //FD_ISSET(t->cli_fd, StaticReadEvent), FD_ISSET(t->cli_fd, StaticWriteEvent), - //FD_ISSET(t->srv_fd, StaticReadEvent), FD_ISSET(t->srv_fd, StaticWriteEvent) - //); - if (s == SV_STIDLE) { - if (c == CL_STHEADERS) - return 0; /* stay in idle, waiting for data to reach the client side */ - else if (c == CL_STCLOSE || c == CL_STSHUTW || - (c == CL_STSHUTR && - (t->req->l == 0 || t->proxy->options & PR_O_ABRT_CLOSE))) { /* give up */ - tv_eternity(&t->cnexpire); - if (t->pend_pos) - t->logs.t_queue = tv_diff(&t->logs.tv_accept, &now); - /* note that this must not return any error because it would be able to - * overwrite the client_retnclose() output. - */ - srv_close_with_err(t, SN_ERR_CLICL, t->pend_pos ? SN_FINST_Q : SN_FINST_C, 0, 0, NULL); - - return 1; - } - else { - /* Right now, we will need to create a connection to the server. - * We might already have tried, and got a connection pending, in - * which case we will not do anything till it's pending. It's up - * to any other session to release it and wake us up again. - */ - if (t->pend_pos) { - if (tv_cmp2_ms(&t->cnexpire, &now) > 0) - return 0; - else { - /* we've been waiting too long here */ - tv_eternity(&t->cnexpire); - t->logs.t_queue = tv_diff(&t->logs.tv_accept, &now); - srv_close_with_err(t, SN_ERR_SRVTO, SN_FINST_Q, - 503, t->proxy->errmsg.len503, t->proxy->errmsg.msg503); - if (t->srv) - t->srv->failed_conns++; - t->proxy->failed_conns++; - return 1; - } - } - - do { - /* first, get a connection */ - if (srv_redispatch_connect(t)) - return t->srv_state != SV_STIDLE; - - /* try to (re-)connect to the server, and fail if we expire the - * number of retries. - */ - if (srv_retryable_connect(t)) { - t->logs.t_queue = tv_diff(&t->logs.tv_accept, &now); - return t->srv_state != SV_STIDLE; - } - - } while (1); - } - } - else if (s == SV_STCONN) { /* connection in progress */ - if (c == CL_STCLOSE || c == CL_STSHUTW || - (c == CL_STSHUTR && - (t->req->l == 0 || t->proxy->options & PR_O_ABRT_CLOSE))) { /* give up */ - tv_eternity(&t->cnexpire); - fd_delete(t->srv_fd); - if (t->srv) - t->srv->cur_sess--; - - /* note that this must not return any error because it would be able to - * overwrite the client_retnclose() output. - */ - srv_close_with_err(t, SN_ERR_CLICL, SN_FINST_C, 0, 0, NULL); - return 1; - } - if (t->res_sw == RES_SILENT && tv_cmp2_ms(&t->cnexpire, &now) > 0) { - //fprintf(stderr,"1: c=%d, s=%d, now=%d.%06d, exp=%d.%06d\n", c, s, now.tv_sec, now.tv_usec, t->cnexpire.tv_sec, t->cnexpire.tv_usec); - return 0; /* nothing changed */ - } - else if (t->res_sw == RES_SILENT || t->res_sw == RES_ERROR) { - /* timeout, asynchronous connect error or first write error */ - //fprintf(stderr,"2: c=%d, s=%d\n", c, s); - - fd_delete(t->srv_fd); - if (t->srv) - t->srv->cur_sess--; - - if (t->res_sw == RES_SILENT) - conn_err = SN_ERR_SRVTO; // it was a connect timeout. - else - conn_err = SN_ERR_SRVCL; // it was an asynchronous connect error. - - /* ensure that we have enough retries left */ - if (srv_count_retry_down(t, conn_err)) - return 1; - - do { - /* Now we will try to either reconnect to the same server or - * connect to another server. If the connection gets queued - * because all servers are saturated, then we will go back to - * the SV_STIDLE state. - */ - if (srv_retryable_connect(t)) { - t->logs.t_queue = tv_diff(&t->logs.tv_accept, &now); - return t->srv_state != SV_STCONN; - } - - /* we need to redispatch the connection to another server */ - if (srv_redispatch_connect(t)) - return t->srv_state != SV_STCONN; - } while (1); - } - else { /* no error or write 0 */ - t->logs.t_connect = tv_diff(&t->logs.tv_accept, &now); - - //fprintf(stderr,"3: c=%d, s=%d\n", c, s); - if (req->l == 0) /* nothing to write */ { - FD_CLR(t->srv_fd, StaticWriteEvent); - tv_eternity(&t->swexpire); - } else /* need the right to write */ { - FD_SET(t->srv_fd, StaticWriteEvent); - if (t->proxy->srvtimeout) { - tv_delayfrom(&t->swexpire, &now, t->proxy->srvtimeout); - /* FIXME: to prevent the server from expiring read timeouts during writes, - * we refresh it. */ - t->srexpire = t->swexpire; - } - else - tv_eternity(&t->swexpire); - } - - if (t->proxy->mode == PR_MODE_TCP) { /* let's allow immediate data connection in this case */ - FD_SET(t->srv_fd, StaticReadEvent); - if (t->proxy->srvtimeout) - tv_delayfrom(&t->srexpire, &now, t->proxy->srvtimeout); - else - tv_eternity(&t->srexpire); - - t->srv_state = SV_STDATA; - if (t->srv) - t->srv->cum_sess++; - rep->rlim = rep->data + BUFSIZE; /* no rewrite needed */ - - /* if the user wants to log as soon as possible, without counting - bytes from the server, then this is the right moment. */ - if (t->proxy->to_log && !(t->logs.logwait & LW_BYTES)) { - t->logs.t_close = t->logs.t_connect; /* to get a valid end date */ - sess_log(t); - } - } - else { - t->srv_state = SV_STHEADERS; - if (t->srv) - t->srv->cum_sess++; - rep->rlim = rep->data + BUFSIZE - MAXREWRITE; /* rewrite needed */ - } - tv_eternity(&t->cnexpire); - return 1; - } - } - else if (s == SV_STHEADERS) { /* receiving server headers */ - /* now parse the partial (or complete) headers */ - while (rep->lr < rep->r) { /* this loop only sees one header at each iteration */ - char *ptr; - int delete_header; - - ptr = rep->lr; - - /* look for the end of the current header */ - while (ptr < rep->r && *ptr != '\n' && *ptr != '\r') - ptr++; - - if (ptr == rep->h) { - int line, len; - - /* we can only get here after an end of headers */ - - /* first, we'll block if security checks have caught nasty things */ - if (t->flags & SN_CACHEABLE) { - if ((t->flags & SN_CACHE_COOK) && - (t->flags & SN_SCK_ANY) && - (t->proxy->options & PR_O_CHK_CACHE)) { - - /* we're in presence of a cacheable response containing - * a set-cookie header. We'll block it as requested by - * the 'checkcache' option, and send an alert. - */ - tv_eternity(&t->srexpire); - tv_eternity(&t->swexpire); - fd_delete(t->srv_fd); - if (t->srv) { - t->srv->cur_sess--; - t->srv->failed_secu++; - } - t->proxy->failed_secu++; - t->srv_state = SV_STCLOSE; - t->logs.status = 502; - client_return(t, t->proxy->errmsg.len502, t->proxy->errmsg.msg502); - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_PRXCOND; - if (!(t->flags & SN_FINST_MASK)) - t->flags |= SN_FINST_H; - - Alert("Blocking cacheable cookie in response from instance %s, server %s.\n", t->proxy->id, t->srv->id); - send_log(t->proxy, LOG_ALERT, "Blocking cacheable cookie in response from instance %s, server %s.\n", t->proxy->id, t->srv->id); - - /* We used to have a free connection slot. Since we'll never use it, - * we have to inform the server that it may be used by another session. - */ - if (may_dequeue_tasks(t->srv, t->proxy)) - task_wakeup(&rq, t->srv->queue_mgt); - - return 1; - } - } - - /* next, we'll block if an 'rspideny' or 'rspdeny' filter matched */ - if (t->flags & SN_SVDENY) { - tv_eternity(&t->srexpire); - tv_eternity(&t->swexpire); - fd_delete(t->srv_fd); - if (t->srv) { - t->srv->cur_sess--; - t->srv->failed_secu++; - } - t->proxy->failed_secu++; - t->srv_state = SV_STCLOSE; - t->logs.status = 502; - client_return(t, t->proxy->errmsg.len502, t->proxy->errmsg.msg502); - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_PRXCOND; - if (!(t->flags & SN_FINST_MASK)) - t->flags |= SN_FINST_H; - /* We used to have a free connection slot. Since we'll never use it, - * we have to inform the server that it may be used by another session. - */ - if (may_dequeue_tasks(t->srv, t->proxy)) - task_wakeup(&rq, t->srv->queue_mgt); - - return 1; - } - - /* we'll have something else to do here : add new headers ... */ - - if ((t->srv) && !(t->flags & SN_DIRECT) && (t->proxy->options & PR_O_COOK_INS) && - (!(t->proxy->options & PR_O_COOK_POST) || (t->flags & SN_POST))) { - /* the server is known, it's not the one the client requested, we have to - * insert a set-cookie here, except if we want to insert only on POST - * requests and this one isn't. Note that servers which don't have cookies - * (eg: some backup servers) will return a full cookie removal request. - */ - len = sprintf(trash, "Set-Cookie: %s=%s; path=/\r\n", - t->proxy->cookie_name, - t->srv->cookie ? t->srv->cookie : "; Expires=Thu, 01-Jan-1970 00:00:01 GMT"); - - t->flags |= SN_SCK_INSERTED; - - /* Here, we will tell an eventual cache on the client side that we don't - * want it to cache this reply because HTTP/1.0 caches also cache cookies ! - * Some caches understand the correct form: 'no-cache="set-cookie"', but - * others don't (eg: apache <= 1.3.26). So we use 'private' instead. - */ - if (t->proxy->options & PR_O_COOK_NOC) - //len += sprintf(newhdr + len, "Cache-control: no-cache=\"set-cookie\"\r\n"); - len += sprintf(trash + len, "Cache-control: private\r\n"); - - if (rep->data + rep->l < rep->h) - /* The data has been stolen, we will crash cleanly instead of corrupting memory */ - *(int *)0 = 0; - buffer_replace2(rep, rep->h, rep->h, trash, len); - } - - /* headers to be added */ - for (line = 0; line < t->proxy->nb_rspadd; line++) { - len = sprintf(trash, "%s\r\n", t->proxy->rsp_add[line]); - buffer_replace2(rep, rep->h, rep->h, trash, len); - } - - /* add a "connection: close" line if needed */ - if (t->proxy->options & PR_O_HTTP_CLOSE) - buffer_replace2(rep, rep->h, rep->h, "Connection: close\r\n", 19); - - t->srv_state = SV_STDATA; - rep->rlim = rep->data + BUFSIZE; /* no more rewrite needed */ - t->logs.t_data = tv_diff(&t->logs.tv_accept, &now); - - /* client connection already closed or option 'httpclose' required : - * we close the server's outgoing connection right now. - */ - if ((req->l == 0) && - (c == CL_STSHUTR || c == CL_STCLOSE || t->proxy->options & PR_O_FORCE_CLO)) { - FD_CLR(t->srv_fd, StaticWriteEvent); - tv_eternity(&t->swexpire); - - /* We must ensure that the read part is still alive when switching - * to shutw */ - FD_SET(t->srv_fd, StaticReadEvent); - if (t->proxy->srvtimeout) - tv_delayfrom(&t->srexpire, &now, t->proxy->srvtimeout); - - shutdown(t->srv_fd, SHUT_WR); - t->srv_state = SV_STSHUTW; - } - - /* if the user wants to log as soon as possible, without counting - bytes from the server, then this is the right moment. */ - if (t->proxy->to_log && !(t->logs.logwait & LW_BYTES)) { - t->logs.t_close = t->logs.t_data; /* to get a valid end date */ - t->logs.bytes = rep->h - rep->data; - sess_log(t); - } - break; - } - - /* to get a complete header line, we need the ending \r\n, \n\r, \r or \n too */ - if (ptr > rep->r - 2) { - /* this is a partial header, let's wait for more to come */ - rep->lr = ptr; - break; - } - - // fprintf(stderr,"h=%p, ptr=%p, lr=%p, r=%p, *h=", rep->h, ptr, rep->lr, rep->r); - // write(2, rep->h, ptr - rep->h); fprintf(stderr,"\n"); - - /* now we know that *ptr is either \r or \n, - * and that there are at least 1 char after it. - */ - if ((ptr[0] == ptr[1]) || (ptr[1] != '\r' && ptr[1] != '\n')) - rep->lr = ptr + 1; /* \r\r, \n\n, \r[^\n], \n[^\r] */ - else - rep->lr = ptr + 2; /* \r\n or \n\r */ - - /* - * now we know that we have a full header ; we can do whatever - * we want with these pointers : - * rep->h = beginning of header - * ptr = end of header (first \r or \n) - * rep->lr = beginning of next line (next rep->h) - * rep->r = end of data (not used at this stage) - */ - - - if (t->logs.status == -1) { - t->logs.logwait &= ~LW_RESP; - t->logs.status = atoi(rep->h + 9); - switch (t->logs.status) { - case 200: - case 203: - case 206: - case 300: - case 301: - case 410: - /* RFC2616 @13.4: - * "A response received with a status code of - * 200, 203, 206, 300, 301 or 410 MAY be stored - * by a cache (...) unless a cache-control - * directive prohibits caching." - * - * RFC2616 @9.5: POST method : - * "Responses to this method are not cacheable, - * unless the response includes appropriate - * Cache-Control or Expires header fields." - */ - if (!(t->flags & SN_POST) && (t->proxy->options & PR_O_CHK_CACHE)) - t->flags |= SN_CACHEABLE | SN_CACHE_COOK; - break; - default: - break; - } - } - else if (t->logs.logwait & LW_RSPHDR) { - struct cap_hdr *h; - int len; - for (h = t->proxy->rsp_cap; h; h = h->next) { - if ((h->namelen + 2 <= ptr - rep->h) && - (rep->h[h->namelen] == ':') && - (strncasecmp(rep->h, h->name, h->namelen) == 0)) { - - if (t->rsp_cap[h->index] == NULL) - t->rsp_cap[h->index] = pool_alloc_from(h->pool, h->len + 1); - - len = ptr - (rep->h + h->namelen + 2); - if (len > h->len) - len = h->len; - - memcpy(t->rsp_cap[h->index], rep->h + h->namelen + 2, len); - t->rsp_cap[h->index][len]=0; - } - } - - } - - delete_header = 0; - - if ((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) { - int len, max; - len = sprintf(trash, "%08x:%s.srvhdr[%04x:%04x]: ", t->uniq_id, t->proxy->id, (unsigned short)t->cli_fd, (unsigned short)t->srv_fd); - max = ptr - rep->h; - UBOUND(max, sizeof(trash) - len - 1); - len += strlcpy2(trash + len, rep->h, max + 1); - trash[len++] = '\n'; - write(1, trash, len); - } - - /* remove "connection: " if needed */ - if (!delete_header && (t->proxy->options & PR_O_HTTP_CLOSE) - && (strncasecmp(rep->h, "Connection: ", 12) == 0)) { - delete_header = 1; - } - - /* try headers regexps */ - if (!delete_header && t->proxy->rsp_exp != NULL - && !(t->flags & SN_SVDENY)) { - struct hdr_exp *exp; - char term; - - term = *ptr; - *ptr = '\0'; - exp = t->proxy->rsp_exp; - do { - if (regexec(exp->preg, rep->h, MAX_MATCH, pmatch, 0) == 0) { - switch (exp->action) { - case ACT_ALLOW: - if (!(t->flags & SN_SVDENY)) - t->flags |= SN_SVALLOW; - break; - case ACT_REPLACE: - if (!(t->flags & SN_SVDENY)) { - int len = exp_replace(trash, rep->h, exp->replace, pmatch); - ptr += buffer_replace2(rep, rep->h, ptr, trash, len); - } - break; - case ACT_REMOVE: - if (!(t->flags & SN_SVDENY)) - delete_header = 1; - break; - case ACT_DENY: - if (!(t->flags & SN_SVALLOW)) - t->flags |= SN_SVDENY; - break; - case ACT_PASS: /* we simply don't deny this one */ - break; - } - break; - } - } while ((exp = exp->next) != NULL); - *ptr = term; /* restore the string terminator */ - } - - /* check for cache-control: or pragma: headers */ - if (!delete_header && (t->flags & SN_CACHEABLE)) { - if (strncasecmp(rep->h, "Pragma: no-cache", 16) == 0) - t->flags &= ~SN_CACHEABLE & ~SN_CACHE_COOK; - else if (strncasecmp(rep->h, "Cache-control: ", 15) == 0) { - if (strncasecmp(rep->h + 15, "no-cache", 8) == 0) { - if (rep->h + 23 == ptr || rep->h[23] == ',') - t->flags &= ~SN_CACHEABLE & ~SN_CACHE_COOK; - else { - if (strncasecmp(rep->h + 23, "=\"set-cookie", 12) == 0 - && (rep->h[35] == '"' || rep->h[35] == ',')) - t->flags &= ~SN_CACHE_COOK; - } - } else if ((strncasecmp(rep->h + 15, "private", 7) == 0 && - (rep->h + 22 == ptr || rep->h[22] == ',')) - || (strncasecmp(rep->h + 15, "no-store", 8) == 0 && - (rep->h + 23 == ptr || rep->h[23] == ','))) { - t->flags &= ~SN_CACHEABLE & ~SN_CACHE_COOK; - } else if (strncasecmp(rep->h + 15, "max-age=0", 9) == 0 && - (rep->h + 24 == ptr || rep->h[24] == ',')) { - t->flags &= ~SN_CACHEABLE & ~SN_CACHE_COOK; - } else if (strncasecmp(rep->h + 15, "s-maxage=0", 10) == 0 && - (rep->h + 25 == ptr || rep->h[25] == ',')) { - t->flags &= ~SN_CACHEABLE & ~SN_CACHE_COOK; - } else if (strncasecmp(rep->h + 15, "public", 6) == 0 && - (rep->h + 21 == ptr || rep->h[21] == ',')) { - t->flags |= SN_CACHEABLE | SN_CACHE_COOK; - } - } - } - - /* check for server cookies */ - if (!delete_header /*&& (t->proxy->options & PR_O_COOK_ANY)*/ - && (t->proxy->cookie_name != NULL || t->proxy->capture_name != NULL || t->proxy->appsession_name !=NULL) - && (strncasecmp(rep->h, "Set-Cookie: ", 12) == 0)) { - char *p1, *p2, *p3, *p4; - - t->flags |= SN_SCK_ANY; - - p1 = rep->h + 12; /* first char after 'Set-Cookie: ' */ - - while (p1 < ptr) { /* in fact, we'll break after the first cookie */ - while (p1 < ptr && (isspace((int)*p1))) - p1++; - - if (p1 == ptr || *p1 == ';') /* end of cookie */ - break; - - /* p1 is at the beginning of the cookie name */ - p2 = p1; - - while (p2 < ptr && *p2 != '=' && *p2 != ';') - p2++; - - if (p2 == ptr || *p2 == ';') /* next cookie */ - break; - - p3 = p2 + 1; /* skips the '=' sign */ - if (p3 == ptr) - break; - - p4 = p3; - while (p4 < ptr && !isspace((int)*p4) && *p4 != ';') - p4++; - - /* here, we have the cookie name between p1 and p2, - * and its value between p3 and p4. - * we can process it. - */ - - /* first, let's see if we want to capture it */ - if (t->proxy->capture_name != NULL && - t->logs.srv_cookie == NULL && - (p4 - p1 >= t->proxy->capture_namelen) && - memcmp(p1, t->proxy->capture_name, t->proxy->capture_namelen) == 0) { - int log_len = p4 - p1; - - if ((t->logs.srv_cookie = pool_alloc(capture)) == NULL) { - Alert("HTTP logging : out of memory.\n"); - } - - if (log_len > t->proxy->capture_len) - log_len = t->proxy->capture_len; - memcpy(t->logs.srv_cookie, p1, log_len); - t->logs.srv_cookie[log_len] = 0; - } - - if ((p2 - p1 == t->proxy->cookie_len) && (t->proxy->cookie_name != NULL) && - (memcmp(p1, t->proxy->cookie_name, p2 - p1) == 0)) { - /* Cool... it's the right one */ - t->flags |= SN_SCK_SEEN; - - /* If the cookie is in insert mode on a known server, we'll delete - * this occurrence because we'll insert another one later. - * We'll delete it too if the "indirect" option is set and we're in - * a direct access. */ - if (((t->srv) && (t->proxy->options & PR_O_COOK_INS)) || - ((t->flags & SN_DIRECT) && (t->proxy->options & PR_O_COOK_IND))) { - /* this header must be deleted */ - delete_header = 1; - t->flags |= SN_SCK_DELETED; - } - else if ((t->srv) && (t->proxy->options & PR_O_COOK_RW)) { - /* replace bytes p3->p4 with the cookie name associated - * with this server since we know it. - */ - buffer_replace2(rep, p3, p4, t->srv->cookie, t->srv->cklen); - t->flags |= SN_SCK_INSERTED | SN_SCK_DELETED; - } - else if ((t->srv) && (t->proxy->options & PR_O_COOK_PFX)) { - /* insert the cookie name associated with this server - * before existing cookie, and insert a delimitor between them.. - */ - buffer_replace2(rep, p3, p3, t->srv->cookie, t->srv->cklen + 1); - p3[t->srv->cklen] = COOKIE_DELIM; - t->flags |= SN_SCK_INSERTED | SN_SCK_DELETED; - } - break; - } - - /* first, let's see if the cookie is our appcookie*/ - if ((t->proxy->appsession_name != NULL) && - (memcmp(p1, t->proxy->appsession_name, p2 - p1) == 0)) { - - /* Cool... it's the right one */ - - size_t server_id_len = strlen(t->srv->id) + 1; - asession_temp = &local_asession; - - if ((asession_temp->sessid = pool_alloc_from(apools.sessid, apools.ses_msize)) == NULL) { - Alert("Not enought Memory process_srv():asession->sessid:malloc().\n"); - send_log(t->proxy, LOG_ALERT, "Not enought Memory process_srv():asession->sessid:malloc().\n"); - } - memcpy(asession_temp->sessid, p3, t->proxy->appsession_len); - asession_temp->sessid[t->proxy->appsession_len] = 0; - asession_temp->serverid = NULL; - - /* only do insert, if lookup fails */ - if (chtbl_lookup(&(t->proxy->htbl_proxy), (void *) &asession_temp) != 0) { - if ((asession_temp = pool_alloc(appsess)) == NULL) { - Alert("Not enought Memory process_srv():asession:calloc().\n"); - send_log(t->proxy, LOG_ALERT, "Not enought Memory process_srv():asession:calloc().\n"); - return 0; - } - asession_temp->sessid = local_asession.sessid; - asession_temp->serverid = local_asession.serverid; - chtbl_insert(&(t->proxy->htbl_proxy), (void *) asession_temp); - }/* end if (chtbl_lookup()) */ - else { - /* free wasted memory */ - pool_free_to(apools.sessid, local_asession.sessid); - } /* end else from if (chtbl_lookup()) */ - - if (asession_temp->serverid == NULL) { - if ((asession_temp->serverid = pool_alloc_from(apools.serverid, apools.ser_msize)) == NULL) { - Alert("Not enought Memory process_srv():asession->sessid:malloc().\n"); - send_log(t->proxy, LOG_ALERT, "Not enought Memory process_srv():asession->sessid:malloc().\n"); - } - asession_temp->serverid[0] = '\0'; - } - - if (asession_temp->serverid[0] == '\0') - memcpy(asession_temp->serverid,t->srv->id,server_id_len); - - tv_delayfrom(&asession_temp->expire, &now, t->proxy->appsession_timeout); - -#if defined(DEBUG_HASH) - print_table(&(t->proxy->htbl_proxy)); -#endif - break; - }/* end if ((t->proxy->appsession_name != NULL) ... */ - else { - // fprintf(stderr,"Ignoring unknown cookie : "); - // write(2, p1, p2-p1); - // fprintf(stderr," = "); - // write(2, p3, p4-p3); - // fprintf(stderr,"\n"); - } - break; /* we don't want to loop again since there cannot be another cookie on the same line */ - } /* we're now at the end of the cookie value */ - } /* end of cookie processing */ - - /* check for any set-cookie in case we check for cacheability */ - if (!delete_header && !(t->flags & SN_SCK_ANY) && - (t->proxy->options & PR_O_CHK_CACHE) && - (strncasecmp(rep->h, "Set-Cookie: ", 12) == 0)) { - t->flags |= SN_SCK_ANY; - } - - /* let's look if we have to delete this header */ - if (delete_header && !(t->flags & SN_SVDENY)) - buffer_replace2(rep, rep->h, rep->lr, "", 0); - - rep->h = rep->lr; - } /* while (rep->lr < rep->r) */ - - /* end of header processing (even if incomplete) */ - - if ((rep->l < rep->rlim - rep->data) && ! FD_ISSET(t->srv_fd, StaticReadEvent)) { - /* fd in StaticReadEvent was disabled, perhaps because of a previous buffer - * full. We cannot loop here since event_srv_read will disable it only if - * rep->l == rlim-data - */ - FD_SET(t->srv_fd, StaticReadEvent); - if (t->proxy->srvtimeout) - tv_delayfrom(&t->srexpire, &now, t->proxy->srvtimeout); - else - tv_eternity(&t->srexpire); - } - - /* read error, write error */ - if (t->res_sw == RES_ERROR || t->res_sr == RES_ERROR) { - tv_eternity(&t->srexpire); - tv_eternity(&t->swexpire); - fd_delete(t->srv_fd); - if (t->srv) { - t->srv->cur_sess--; - t->srv->failed_resp++; - } - t->proxy->failed_resp++; - - t->srv_state = SV_STCLOSE; - t->logs.status = 502; - client_return(t, t->proxy->errmsg.len502, t->proxy->errmsg.msg502); - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_SRVCL; - if (!(t->flags & SN_FINST_MASK)) - t->flags |= SN_FINST_H; - /* We used to have a free connection slot. Since we'll never use it, - * we have to inform the server that it may be used by another session. - */ - if (may_dequeue_tasks(t->srv, t->proxy)) - task_wakeup(&rq, t->srv->queue_mgt); - - return 1; - } - /* end of client write or end of server read. - * since we are in header mode, if there's no space left for headers, we - * won't be able to free more later, so the session will never terminate. - */ - else if (t->res_sr == RES_NULL || c == CL_STSHUTW || c == CL_STCLOSE || rep->l >= rep->rlim - rep->data) { - FD_CLR(t->srv_fd, StaticReadEvent); - tv_eternity(&t->srexpire); - shutdown(t->srv_fd, SHUT_RD); - t->srv_state = SV_STSHUTR; - //fprintf(stderr,"%p:%s(%d), c=%d, s=%d\n", t, __FUNCTION__, __LINE__, t->cli_state, t->cli_state); - return 1; - } - /* read timeout : return a 504 to the client. - */ - else if (FD_ISSET(t->srv_fd, StaticReadEvent) && tv_cmp2_ms(&t->srexpire, &now) <= 0) { - tv_eternity(&t->srexpire); - tv_eternity(&t->swexpire); - fd_delete(t->srv_fd); - if (t->srv) { - t->srv->cur_sess--; - t->srv->failed_resp++; - } - t->proxy->failed_resp++; - t->srv_state = SV_STCLOSE; - t->logs.status = 504; - client_return(t, t->proxy->errmsg.len504, t->proxy->errmsg.msg504); - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_SRVTO; - if (!(t->flags & SN_FINST_MASK)) - t->flags |= SN_FINST_H; - /* We used to have a free connection slot. Since we'll never use it, - * we have to inform the server that it may be used by another session. - */ - if (may_dequeue_tasks(t->srv, t->proxy)) - task_wakeup(&rq, t->srv->queue_mgt); - - return 1; - } - /* last client read and buffer empty */ - /* FIXME!!! here, we don't want to switch to SHUTW if the - * client shuts read too early, because we may still have - * some work to do on the headers. - * The side-effect is that if the client completely closes its - * connection during SV_STHEADER, the connection to the server - * is kept until a response comes back or the timeout is reached. - */ - else if ((/*c == CL_STSHUTR ||*/ c == CL_STCLOSE) && (req->l == 0)) { - FD_CLR(t->srv_fd, StaticWriteEvent); - tv_eternity(&t->swexpire); - - /* We must ensure that the read part is still alive when switching - * to shutw */ - FD_SET(t->srv_fd, StaticReadEvent); - if (t->proxy->srvtimeout) - tv_delayfrom(&t->srexpire, &now, t->proxy->srvtimeout); - - shutdown(t->srv_fd, SHUT_WR); - t->srv_state = SV_STSHUTW; - return 1; - } - /* write timeout */ - /* FIXME!!! here, we don't want to switch to SHUTW if the - * client shuts read too early, because we may still have - * some work to do on the headers. - */ - else if (FD_ISSET(t->srv_fd, StaticWriteEvent) && tv_cmp2_ms(&t->swexpire, &now) <= 0) { - FD_CLR(t->srv_fd, StaticWriteEvent); - tv_eternity(&t->swexpire); - shutdown(t->srv_fd, SHUT_WR); - /* We must ensure that the read part is still alive when switching - * to shutw */ - FD_SET(t->srv_fd, StaticReadEvent); - if (t->proxy->srvtimeout) - tv_delayfrom(&t->srexpire, &now, t->proxy->srvtimeout); - - /* We must ensure that the read part is still alive when switching - * to shutw */ - FD_SET(t->srv_fd, StaticReadEvent); - if (t->proxy->srvtimeout) - tv_delayfrom(&t->srexpire, &now, t->proxy->srvtimeout); - - t->srv_state = SV_STSHUTW; - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_SRVTO; - if (!(t->flags & SN_FINST_MASK)) - t->flags |= SN_FINST_H; - return 1; - } - - if (req->l == 0) { - if (FD_ISSET(t->srv_fd, StaticWriteEvent)) { - FD_CLR(t->srv_fd, StaticWriteEvent); /* stop writing */ - tv_eternity(&t->swexpire); - } - } - else { /* client buffer not empty */ - if (! FD_ISSET(t->srv_fd, StaticWriteEvent)) { - FD_SET(t->srv_fd, StaticWriteEvent); /* restart writing */ - if (t->proxy->srvtimeout) { - tv_delayfrom(&t->swexpire, &now, t->proxy->srvtimeout); - /* FIXME: to prevent the server from expiring read timeouts during writes, - * we refresh it. */ - t->srexpire = t->swexpire; - } - else - tv_eternity(&t->swexpire); - } - } - - /* be nice with the client side which would like to send a complete header - * FIXME: COMPLETELY BUGGY !!! not all headers may be processed because the client - * would read all remaining data at once ! The client should not write past rep->lr - * when the server is in header state. - */ - //return header_processed; - return t->srv_state != SV_STHEADERS; - } - else if (s == SV_STDATA) { - /* read or write error */ - if (t->res_sw == RES_ERROR || t->res_sr == RES_ERROR) { - tv_eternity(&t->srexpire); - tv_eternity(&t->swexpire); - fd_delete(t->srv_fd); - if (t->srv) { - t->srv->cur_sess--; - t->srv->failed_resp++; - } - t->proxy->failed_resp++; - t->srv_state = SV_STCLOSE; - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_SRVCL; - if (!(t->flags & SN_FINST_MASK)) - t->flags |= SN_FINST_D; - /* We used to have a free connection slot. Since we'll never use it, - * we have to inform the server that it may be used by another session. - */ - if (may_dequeue_tasks(t->srv, t->proxy)) - task_wakeup(&rq, t->srv->queue_mgt); - - return 1; - } - /* last read, or end of client write */ - else if (t->res_sr == RES_NULL || c == CL_STSHUTW || c == CL_STCLOSE) { - FD_CLR(t->srv_fd, StaticReadEvent); - tv_eternity(&t->srexpire); - shutdown(t->srv_fd, SHUT_RD); - t->srv_state = SV_STSHUTR; - //fprintf(stderr,"%p:%s(%d), c=%d, s=%d\n", t, __FUNCTION__, __LINE__, t->cli_state, t->cli_state); - return 1; - } - /* end of client read and no more data to send */ - else if ((c == CL_STSHUTR || c == CL_STCLOSE) && (req->l == 0)) { - FD_CLR(t->srv_fd, StaticWriteEvent); - tv_eternity(&t->swexpire); - shutdown(t->srv_fd, SHUT_WR); - /* We must ensure that the read part is still alive when switching - * to shutw */ - FD_SET(t->srv_fd, StaticReadEvent); - if (t->proxy->srvtimeout) - tv_delayfrom(&t->srexpire, &now, t->proxy->srvtimeout); - - t->srv_state = SV_STSHUTW; - return 1; - } - /* read timeout */ - else if (tv_cmp2_ms(&t->srexpire, &now) <= 0) { - FD_CLR(t->srv_fd, StaticReadEvent); - tv_eternity(&t->srexpire); - shutdown(t->srv_fd, SHUT_RD); - t->srv_state = SV_STSHUTR; - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_SRVTO; - if (!(t->flags & SN_FINST_MASK)) - t->flags |= SN_FINST_D; - return 1; - } - /* write timeout */ - else if (tv_cmp2_ms(&t->swexpire, &now) <= 0) { - FD_CLR(t->srv_fd, StaticWriteEvent); - tv_eternity(&t->swexpire); - shutdown(t->srv_fd, SHUT_WR); - /* We must ensure that the read part is still alive when switching - * to shutw */ - FD_SET(t->srv_fd, StaticReadEvent); - if (t->proxy->srvtimeout) - tv_delayfrom(&t->srexpire, &now, t->proxy->srvtimeout); - t->srv_state = SV_STSHUTW; - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_SRVTO; - if (!(t->flags & SN_FINST_MASK)) - t->flags |= SN_FINST_D; - return 1; - } - - /* recompute request time-outs */ - if (req->l == 0) { - if (FD_ISSET(t->srv_fd, StaticWriteEvent)) { - FD_CLR(t->srv_fd, StaticWriteEvent); /* stop writing */ - tv_eternity(&t->swexpire); - } - } - else { /* buffer not empty, there are still data to be transferred */ - if (! FD_ISSET(t->srv_fd, StaticWriteEvent)) { - FD_SET(t->srv_fd, StaticWriteEvent); /* restart writing */ - if (t->proxy->srvtimeout) { - tv_delayfrom(&t->swexpire, &now, t->proxy->srvtimeout); - /* FIXME: to prevent the server from expiring read timeouts during writes, - * we refresh it. */ - t->srexpire = t->swexpire; - } - else - tv_eternity(&t->swexpire); - } - } - - /* recompute response time-outs */ - if (rep->l == BUFSIZE) { /* no room to read more data */ - if (FD_ISSET(t->srv_fd, StaticReadEvent)) { - FD_CLR(t->srv_fd, StaticReadEvent); - tv_eternity(&t->srexpire); - } - } - else { - if (! FD_ISSET(t->srv_fd, StaticReadEvent)) { - FD_SET(t->srv_fd, StaticReadEvent); - if (t->proxy->srvtimeout) - tv_delayfrom(&t->srexpire, &now, t->proxy->srvtimeout); - else - tv_eternity(&t->srexpire); - } - } - - return 0; /* other cases change nothing */ - } - else if (s == SV_STSHUTR) { - if (t->res_sw == RES_ERROR) { - //FD_CLR(t->srv_fd, StaticWriteEvent); - tv_eternity(&t->swexpire); - fd_delete(t->srv_fd); - if (t->srv) { - t->srv->cur_sess--; - t->srv->failed_resp++; - } - t->proxy->failed_resp++; - //close(t->srv_fd); - t->srv_state = SV_STCLOSE; - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_SRVCL; - if (!(t->flags & SN_FINST_MASK)) - t->flags |= SN_FINST_D; - /* We used to have a free connection slot. Since we'll never use it, - * we have to inform the server that it may be used by another session. - */ - if (may_dequeue_tasks(t->srv, t->proxy)) - task_wakeup(&rq, t->srv->queue_mgt); - - return 1; - } - else if ((c == CL_STSHUTR || c == CL_STCLOSE) && (req->l == 0)) { - //FD_CLR(t->srv_fd, StaticWriteEvent); - tv_eternity(&t->swexpire); - fd_delete(t->srv_fd); - if (t->srv) - t->srv->cur_sess--; - //close(t->srv_fd); - t->srv_state = SV_STCLOSE; - /* We used to have a free connection slot. Since we'll never use it, - * we have to inform the server that it may be used by another session. - */ - if (may_dequeue_tasks(t->srv, t->proxy)) - task_wakeup(&rq, t->srv->queue_mgt); - - return 1; - } - else if (tv_cmp2_ms(&t->swexpire, &now) <= 0) { - //FD_CLR(t->srv_fd, StaticWriteEvent); - tv_eternity(&t->swexpire); - fd_delete(t->srv_fd); - if (t->srv) - t->srv->cur_sess--; - //close(t->srv_fd); - t->srv_state = SV_STCLOSE; - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_SRVTO; - if (!(t->flags & SN_FINST_MASK)) - t->flags |= SN_FINST_D; - /* We used to have a free connection slot. Since we'll never use it, - * we have to inform the server that it may be used by another session. - */ - if (may_dequeue_tasks(t->srv, t->proxy)) - task_wakeup(&rq, t->srv->queue_mgt); - - return 1; - } - else if (req->l == 0) { - if (FD_ISSET(t->srv_fd, StaticWriteEvent)) { - FD_CLR(t->srv_fd, StaticWriteEvent); /* stop writing */ - tv_eternity(&t->swexpire); - } - } - else { /* buffer not empty */ - if (! FD_ISSET(t->srv_fd, StaticWriteEvent)) { - FD_SET(t->srv_fd, StaticWriteEvent); /* restart writing */ - if (t->proxy->srvtimeout) { - tv_delayfrom(&t->swexpire, &now, t->proxy->srvtimeout); - /* FIXME: to prevent the server from expiring read timeouts during writes, - * we refresh it. */ - t->srexpire = t->swexpire; - } - else - tv_eternity(&t->swexpire); - } - } - return 0; - } - else if (s == SV_STSHUTW) { - if (t->res_sr == RES_ERROR) { - //FD_CLR(t->srv_fd, StaticReadEvent); - tv_eternity(&t->srexpire); - fd_delete(t->srv_fd); - if (t->srv) { - t->srv->cur_sess--; - t->srv->failed_resp++; - } - t->proxy->failed_resp++; - //close(t->srv_fd); - t->srv_state = SV_STCLOSE; - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_SRVCL; - if (!(t->flags & SN_FINST_MASK)) - t->flags |= SN_FINST_D; - /* We used to have a free connection slot. Since we'll never use it, - * we have to inform the server that it may be used by another session. - */ - if (may_dequeue_tasks(t->srv, t->proxy)) - task_wakeup(&rq, t->srv->queue_mgt); - - return 1; - } - else if (t->res_sr == RES_NULL || c == CL_STSHUTW || c == CL_STCLOSE) { - //FD_CLR(t->srv_fd, StaticReadEvent); - tv_eternity(&t->srexpire); - fd_delete(t->srv_fd); - if (t->srv) - t->srv->cur_sess--; - //close(t->srv_fd); - t->srv_state = SV_STCLOSE; - /* We used to have a free connection slot. Since we'll never use it, - * we have to inform the server that it may be used by another session. - */ - if (may_dequeue_tasks(t->srv, t->proxy)) - task_wakeup(&rq, t->srv->queue_mgt); - - return 1; - } - else if (tv_cmp2_ms(&t->srexpire, &now) <= 0) { - //FD_CLR(t->srv_fd, StaticReadEvent); - tv_eternity(&t->srexpire); - fd_delete(t->srv_fd); - if (t->srv) - t->srv->cur_sess--; - //close(t->srv_fd); - t->srv_state = SV_STCLOSE; - if (!(t->flags & SN_ERR_MASK)) - t->flags |= SN_ERR_SRVTO; - if (!(t->flags & SN_FINST_MASK)) - t->flags |= SN_FINST_D; - /* We used to have a free connection slot. Since we'll never use it, - * we have to inform the server that it may be used by another session. - */ - if (may_dequeue_tasks(t->srv, t->proxy)) - task_wakeup(&rq, t->srv->queue_mgt); - - return 1; - } - else if (rep->l == BUFSIZE) { /* no room to read more data */ - if (FD_ISSET(t->srv_fd, StaticReadEvent)) { - FD_CLR(t->srv_fd, StaticReadEvent); - tv_eternity(&t->srexpire); - } - } - else { - if (! FD_ISSET(t->srv_fd, StaticReadEvent)) { - FD_SET(t->srv_fd, StaticReadEvent); - if (t->proxy->srvtimeout) - tv_delayfrom(&t->srexpire, &now, t->proxy->srvtimeout); - else - tv_eternity(&t->srexpire); - } - } - return 0; - } - else { /* SV_STCLOSE : nothing to do */ - if ((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) { - int len; - len = sprintf(trash, "%08x:%s.srvcls[%04x:%04x]\n", t->uniq_id, t->proxy->id, (unsigned short)t->cli_fd, (unsigned short)t->srv_fd); - write(1, trash, len); - } - return 0; - } - return 0; -} - - -/* Processes the client and server jobs of a session task, then - * puts it back to the wait queue in a clean state, or - * cleans up its resources if it must be deleted. Returns - * the time the task accepts to wait, or TIME_ETERNITY for - * infinity. - */ -int process_session(struct task *t) { - struct session *s = t->context; - int fsm_resync = 0; - - do { - fsm_resync = 0; - //fprintf(stderr,"before_cli:cli=%d, srv=%d\n", s->cli_state, s->srv_state); - fsm_resync |= process_cli(s); - //fprintf(stderr,"cli/srv:cli=%d, srv=%d\n", s->cli_state, s->srv_state); - fsm_resync |= process_srv(s); - //fprintf(stderr,"after_srv:cli=%d, srv=%d\n", s->cli_state, s->srv_state); - } while (fsm_resync); - - if (s->cli_state != CL_STCLOSE || s->srv_state != SV_STCLOSE) { - struct timeval min1, min2; - s->res_cw = s->res_cr = s->res_sw = s->res_sr = RES_SILENT; - - tv_min(&min1, &s->crexpire, &s->cwexpire); - tv_min(&min2, &s->srexpire, &s->swexpire); - tv_min(&min1, &min1, &s->cnexpire); - tv_min(&t->expire, &min1, &min2); - - /* restore t to its place in the task list */ - task_queue(t); - -#ifdef DEBUG_FULL - /* DEBUG code : this should never ever happen, otherwise it indicates - * that a task still has something to do and will provoke a quick loop. - */ - if (tv_remain2(&now, &t->expire) <= 0) - exit(100); -#endif - - return tv_remain2(&now, &t->expire); /* nothing more to do */ - } - - s->proxy->nbconn--; - actconn--; - - if ((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) { - int len; - len = sprintf(trash, "%08x:%s.closed[%04x:%04x]\n", s->uniq_id, s->proxy->id, (unsigned short)s->cli_fd, (unsigned short)s->srv_fd); - write(1, trash, len); - } - - s->logs.t_close = tv_diff(&s->logs.tv_accept, &now); - if (s->rep != NULL) - s->logs.bytes = s->rep->total; - - /* let's do a final log if we need it */ - if (s->logs.logwait && (!(s->proxy->options & PR_O_NULLNOLOG) || s->req->total)) - sess_log(s); - - /* the task MUST not be in the run queue anymore */ - task_delete(t); - session_free(s); - task_free(t); - return TIME_ETERNITY; /* rest in peace for eternity */ -} - - -/* Sets server down, notifies by all available means, recounts the - * remaining servers on the proxy and transfers queued sessions whenever - * possible to other servers. - */ -void set_server_down(struct server *s) { - struct pendconn *pc, *pc_bck, *pc_end; - struct session *sess; - int xferred; - - s->state &= ~SRV_RUNNING; - - if (s->health == s->rise) { - recount_servers(s->proxy); - recalc_server_map(s->proxy); - - /* we might have sessions queued on this server and waiting for - * a connection. Those which are redispatchable will be queued - * to another server or to the proxy itself. - */ - xferred = 0; - FOREACH_ITEM_SAFE(pc, pc_bck, &s->pendconns, pc_end, struct pendconn *, list) { - sess = pc->sess; - if ((sess->proxy->options & PR_O_REDISP)) { - /* The REDISP option was specified. We will ignore - * cookie and force to balance or use the dispatcher. - */ - sess->flags &= ~(SN_DIRECT | SN_ASSIGNED | SN_ADDR_SET); - sess->srv = NULL; /* it's left to the dispatcher to choose a server */ - if ((sess->flags & SN_CK_MASK) == SN_CK_VALID) { - sess->flags &= ~SN_CK_MASK; - sess->flags |= SN_CK_DOWN; - } - pendconn_free(pc); - task_wakeup(&rq, sess->task); - xferred++; - } - } - - sprintf(trash, "%sServer %s/%s is DOWN. %d active and %d backup servers left.%s" - " %d sessions active, %d requeued, %d remaining in queue.\n", - s->state & SRV_BACKUP ? "Backup " : "", - s->proxy->id, s->id, s->proxy->srv_act, s->proxy->srv_bck, - (s->proxy->srv_bck && !s->proxy->srv_act) ? " Running on backup." : "", - s->cur_sess, xferred, s->nbpend); - - Warning("%s", trash); - send_log(s->proxy, LOG_ALERT, "%s", trash); - - if (s->proxy->srv_bck == 0 && s->proxy->srv_act == 0) { - Alert("Proxy %s has no server available !\n", s->proxy->id); - send_log(s->proxy, LOG_EMERG, "Proxy %s has no server available !\n", s->proxy->id); - } - s->down_trans++; - } - s->health = 0; /* failure */ -} - - - -/* - * manages a server health-check. Returns - * the time the task accepts to wait, or TIME_ETERNITY for infinity. - */ -int process_chk(struct task *t) { - struct server *s = t->context; - struct sockaddr_in sa; - int fd; - - //fprintf(stderr, "process_chk: task=%p\n", t); - - new_chk: - fd = s->curfd; - if (fd < 0) { /* no check currently running */ - //fprintf(stderr, "process_chk: 2\n"); - if (tv_cmp2_ms(&t->expire, &now) > 0) { /* not good time yet */ - task_queue(t); /* restore t to its place in the task list */ - return tv_remain2(&now, &t->expire); - } - - /* we don't send any health-checks when the proxy is stopped or when - * the server should not be checked. - */ - if (!(s->state & SRV_CHECKED) || s->proxy->state == PR_STSTOPPED) { - while (tv_cmp2_ms(&t->expire, &now) <= 0) - tv_delayfrom(&t->expire, &t->expire, s->inter); - task_queue(t); /* restore t to its place in the task list */ - return tv_remain2(&now, &t->expire); - } - - /* we'll initiate a new check */ - s->result = 0; /* no result yet */ - if ((fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) != -1) { - if ((fd < global.maxsock) && - (fcntl(fd, F_SETFL, O_NONBLOCK) != -1) && - (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *) &one, sizeof(one)) != -1)) { - //fprintf(stderr, "process_chk: 3\n"); - - /* we'll connect to the check port on the server */ - sa = s->addr; - sa.sin_port = htons(s->check_port); - - /* allow specific binding : - * - server-specific at first - * - proxy-specific next - */ - if (s->state & SRV_BIND_SRC) { - setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(one)); - if (bind(fd, (struct sockaddr *)&s->source_addr, sizeof(s->source_addr)) == -1) { - Alert("Cannot bind to source address before connect() for server %s/%s. Aborting.\n", - s->proxy->id, s->id); - s->result = -1; - } - } - else if (s->proxy->options & PR_O_BIND_SRC) { - setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(one)); - if (bind(fd, (struct sockaddr *)&s->proxy->source_addr, sizeof(s->proxy->source_addr)) == -1) { - Alert("Cannot bind to source address before connect() for proxy %s. Aborting.\n", - s->proxy->id); - s->result = -1; - } - } - - if (!s->result) { - if ((connect(fd, (struct sockaddr *)&sa, sizeof(sa)) != -1) || (errno == EINPROGRESS)) { - /* OK, connection in progress or established */ - - //fprintf(stderr, "process_chk: 4\n"); - - s->curfd = fd; /* that's how we know a test is in progress ;-) */ - fdtab[fd].owner = t; - fdtab[fd].read = &event_srv_chk_r; - fdtab[fd].write = &event_srv_chk_w; - fdtab[fd].state = FD_STCONN; /* connection in progress */ - FD_SET(fd, StaticWriteEvent); /* for connect status */ -#ifdef DEBUG_FULL - assert (!FD_ISSET(fd, StaticReadEvent)); -#endif - fd_insert(fd); - /* FIXME: we allow up to for a connection to establish, but we should use another parameter */ - tv_delayfrom(&t->expire, &now, s->inter); - task_queue(t); /* restore t to its place in the task list */ - return tv_remain(&now, &t->expire); - } - else if (errno != EALREADY && errno != EISCONN && errno != EAGAIN) { - s->result = -1; /* a real error */ - } - } - } - close(fd); /* socket creation error */ - } - - if (!s->result) { /* nothing done */ - //fprintf(stderr, "process_chk: 6\n"); - while (tv_cmp2_ms(&t->expire, &now) <= 0) - tv_delayfrom(&t->expire, &t->expire, s->inter); - goto new_chk; /* may be we should initialize a new check */ - } - - /* here, we have seen a failure */ - if (s->health > s->rise) { - s->health--; /* still good */ - s->failed_checks++; - } - else - set_server_down(s); - - //fprintf(stderr, "process_chk: 7\n"); - /* FIXME: we allow up to for a connection to establish, but we should use another parameter */ - while (tv_cmp2_ms(&t->expire, &now) <= 0) - tv_delayfrom(&t->expire, &t->expire, s->inter); - goto new_chk; - } - else { - //fprintf(stderr, "process_chk: 8\n"); - /* there was a test running */ - if (s->result > 0) { /* good server detected */ - //fprintf(stderr, "process_chk: 9\n"); - s->health++; /* was bad, stays for a while */ - if (s->health >= s->rise) { - s->state |= SRV_RUNNING; - - if (s->health == s->rise) { - int xferred; - - recount_servers(s->proxy); - recalc_server_map(s->proxy); - - /* check if we can handle some connections queued at the proxy. We - * will take as many as we can handle. - */ - for (xferred = 0; !s->maxconn || xferred < srv_dynamic_maxconn(s); xferred++) { - struct session *sess; - struct pendconn *p; - - p = pendconn_from_px(s->proxy); - if (!p) - break; - p->sess->srv = s; - sess = p->sess; - pendconn_free(p); - task_wakeup(&rq, sess->task); - } - - sprintf(trash, - "%sServer %s/%s is UP. %d active and %d backup servers online.%s" - " %d sessions requeued, %d total in queue.\n", - s->state & SRV_BACKUP ? "Backup " : "", - s->proxy->id, s->id, s->proxy->srv_act, s->proxy->srv_bck, - (s->proxy->srv_bck && !s->proxy->srv_act) ? " Running on backup." : "", - xferred, s->nbpend); - - Warning("%s", trash); - send_log(s->proxy, LOG_NOTICE, "%s", trash); - } - - s->health = s->rise + s->fall - 1; /* OK now */ - } - s->curfd = -1; /* no check running anymore */ - //FD_CLR(fd, StaticWriteEvent); - fd_delete(fd); - while (tv_cmp2_ms(&t->expire, &now) <= 0) - tv_delayfrom(&t->expire, &t->expire, s->inter); - goto new_chk; - } - else if (s->result < 0 || tv_cmp2_ms(&t->expire, &now) <= 0) { - //fprintf(stderr, "process_chk: 10\n"); - /* failure or timeout detected */ - if (s->health > s->rise) { - s->health--; /* still good */ - s->failed_checks++; - } - else - set_server_down(s); - s->curfd = -1; - //FD_CLR(fd, StaticWriteEvent); - fd_delete(fd); - while (tv_cmp2_ms(&t->expire, &now) <= 0) - tv_delayfrom(&t->expire, &t->expire, s->inter); - goto new_chk; - } - /* if result is 0 and there's no timeout, we have to wait again */ - } - //fprintf(stderr, "process_chk: 11\n"); - s->result = 0; - task_queue(t); /* restore t to its place in the task list */ - return tv_remain2(&now, &t->expire); -} - - - -/* - * Manages a server's connection queue. If woken up, will try to dequeue as - * many pending sessions as possible, and wake them up. The task has nothing - * else to do, so it always returns TIME_ETERNITY. - */ -int process_srv_queue(struct task *t) { - struct server *s = (struct server*)t->context; - struct proxy *p = s->proxy; - int xferred; - - /* First, check if we can handle some connections queued at the proxy. We - * will take as many as we can handle. - */ - for (xferred = 0; s->cur_sess + xferred < srv_dynamic_maxconn(s); xferred++) { - struct session *sess; - - sess = pendconn_get_next_sess(s, p); - if (sess == NULL) - break; - task_wakeup(&rq, sess->task); - } - - return TIME_ETERNITY; -} - -#if STATTIME > 0 -int stats(void); -#endif - -/* - * This does 4 things : - * - wake up all expired tasks - * - call all runnable tasks - * - call maintain_proxies() to enable/disable the listeners - * - return the delay till next event in ms, -1 = wait indefinitely - * Note: this part should be rewritten with the O(ln(n)) scheduler. - * - */ - -int process_runnable_tasks() { - int next_time; - int time2; - struct task *t, *tnext; - - next_time = TIME_ETERNITY; /* set the timer to wait eternally first */ - - /* look for expired tasks and add them to the run queue. - */ - tnext = ((struct task *)LIST_HEAD(wait_queue[0]))->next; - while ((t = tnext) != LIST_HEAD(wait_queue[0])) { /* we haven't looped ? */ - tnext = t->next; - if (t->state & TASK_RUNNING) - continue; - - if (tv_iseternity(&t->expire)) - continue; - - /* wakeup expired entries. It doesn't matter if they are - * already running because of a previous event - */ - if (tv_cmp_ms(&t->expire, &now) <= 0) { - task_wakeup(&rq, t); - } - else { - /* first non-runnable task. Use its expiration date as an upper bound */ - int temp_time = tv_remain(&now, &t->expire); - if (temp_time) - next_time = temp_time; - break; - } - } - - /* process each task in the run queue now. Each task may be deleted - * since we only use the run queue's head. Note that any task can be - * woken up by any other task and it will be processed immediately - * after as it will be queued on the run queue's head. - */ - while ((t = rq) != NULL) { - int temp_time; - - task_sleep(&rq, t); - temp_time = t->process(t); - next_time = MINTIME(temp_time, next_time); - } - - /* maintain all proxies in a consistent state. This should quickly become a task */ - time2 = maintain_proxies(); - return MINTIME(time2, next_time); -} - - -#if defined(ENABLE_EPOLL) - -/* - * Main epoll() loop. - */ - -/* does 3 actions : - * 0 (POLL_LOOP_ACTION_INIT) : initializes necessary private structures - * 1 (POLL_LOOP_ACTION_RUN) : runs the loop - * 2 (POLL_LOOP_ACTION_CLEAN) : cleans up - * - * returns 0 if initialization failed, !0 otherwise. - */ - -int epoll_loop(int action) { - int next_time; - int status; - int fd; - - int fds, count; - int pr, pw, sr, sw; - unsigned rn, ro, wn, wo; /* read new, read old, write new, write old */ - struct epoll_event ev; - - /* private data */ - static struct epoll_event *epoll_events = NULL; - static int epoll_fd; - - if (action == POLL_LOOP_ACTION_INIT) { - epoll_fd = epoll_create(global.maxsock + 1); - if (epoll_fd < 0) - return 0; - else { - epoll_events = (struct epoll_event*) - calloc(1, sizeof(struct epoll_event) * global.maxsock); - PrevReadEvent = (fd_set *) - calloc(1, sizeof(fd_set) * (global.maxsock + FD_SETSIZE - 1) / FD_SETSIZE); - PrevWriteEvent = (fd_set *) - calloc(1, sizeof(fd_set) * (global.maxsock + FD_SETSIZE - 1) / FD_SETSIZE); - } - return 1; - } - else if (action == POLL_LOOP_ACTION_CLEAN) { - if (PrevWriteEvent) free(PrevWriteEvent); - if (PrevReadEvent) free(PrevReadEvent); - if (epoll_events) free(epoll_events); - close(epoll_fd); - epoll_fd = 0; - return 1; - } - - /* OK, it's POLL_LOOP_ACTION_RUN */ - - tv_now(&now); - - while (1) { - next_time = process_runnable_tasks(); - - /* stop when there's no connection left and we don't allow them anymore */ - if (!actconn && listeners == 0) - break; - -#if STATTIME > 0 - { - int time2; - time2 = stats(); - next_time = MINTIME(time2, next_time); - } -#endif - - for (fds = 0; (fds << INTBITS) < maxfd; fds++) { - - rn = ((int*)StaticReadEvent)[fds]; ro = ((int*)PrevReadEvent)[fds]; - wn = ((int*)StaticWriteEvent)[fds]; wo = ((int*)PrevWriteEvent)[fds]; - - if ((ro^rn) | (wo^wn)) { - for (count = 0, fd = fds << INTBITS; count < (1<> count) & 1; - pw = (wo >> count) & 1; - sr = (rn >> count) & 1; - sw = (wn >> count) & 1; -#else - pr = FD_ISSET(fd&((1< 0 - { - int time2; - time2 = stats(); - next_time = MINTIME(time2, next_time); - } -#endif - - - nbfd = 0; - for (fds = 0; (fds << INTBITS) < maxfd; fds++) { - - rn = ((int*)StaticReadEvent)[fds]; - wn = ((int*)StaticWriteEvent)[fds]; - - if ((rn|wn)) { - for (count = 0, fd = fds << INTBITS; count < (1<> count) & 1; - sw = (wn >> count) & 1; -#else - sr = FD_ISSET(fd&((1< 0 && count < nbfd; count++) { - fd = poll_events[count].fd; - - if (!(poll_events[count].revents & ( POLLOUT | POLLIN | POLLERR | POLLHUP ))) - continue; - - /* ok, we found one active fd */ - status--; - - if (FD_ISSET(fd, StaticReadEvent)) { - if (fdtab[fd].state == FD_STCLOSE) - continue; - if (poll_events[count].revents & ( POLLIN | POLLERR | POLLHUP )) - fdtab[fd].read(fd); - } - - if (FD_ISSET(fd, StaticWriteEvent)) { - if (fdtab[fd].state == FD_STCLOSE) - continue; - if (poll_events[count].revents & ( POLLOUT | POLLERR | POLLHUP )) - fdtab[fd].write(fd); - } - } - } - return 1; -} -#endif - - - -/* - * Main select() loop. - */ - -/* does 3 actions : - * 0 (POLL_LOOP_ACTION_INIT) : initializes necessary private structures - * 1 (POLL_LOOP_ACTION_RUN) : runs the loop - * 2 (POLL_LOOP_ACTION_CLEAN) : cleans up - * - * returns 0 if initialization failed, !0 otherwise. - */ - - -int select_loop(int action) { - int next_time; - int status; - int fd,i; - struct timeval delta; - int readnotnull, writenotnull; - static fd_set *ReadEvent = NULL, *WriteEvent = NULL; - - if (action == POLL_LOOP_ACTION_INIT) { - ReadEvent = (fd_set *) - calloc(1, sizeof(fd_set) * (global.maxsock + FD_SETSIZE - 1) / FD_SETSIZE); - WriteEvent = (fd_set *) - calloc(1, sizeof(fd_set) * (global.maxsock + FD_SETSIZE - 1) / FD_SETSIZE); - return 1; - } - else if (action == POLL_LOOP_ACTION_CLEAN) { - if (WriteEvent) free(WriteEvent); - if (ReadEvent) free(ReadEvent); - return 1; - } - - /* OK, it's POLL_LOOP_ACTION_RUN */ - - tv_now(&now); - - while (1) { - next_time = process_runnable_tasks(); - - /* stop when there's no connection left and we don't allow them anymore */ - if (!actconn && listeners == 0) - break; - -#if STATTIME > 0 - { - int time2; - time2 = stats(); - next_time = MINTIME(time2, next_time); - } -#endif - - if (next_time > 0) { /* FIXME */ - /* Convert to timeval */ - /* to avoid eventual select loops due to timer precision */ - next_time += SCHEDULER_RESOLUTION; - delta.tv_sec = next_time / 1000; - delta.tv_usec = (next_time % 1000) * 1000; - } - else if (next_time == 0) { /* allow select to return immediately when needed */ - delta.tv_sec = delta.tv_usec = 0; - } - - - /* let's restore fdset state */ - - readnotnull = 0; writenotnull = 0; - for (i = 0; i < (maxfd + FD_SETSIZE - 1)/(8*sizeof(int)); i++) { - readnotnull |= (*(((int*)ReadEvent)+i) = *(((int*)StaticReadEvent)+i)) != 0; - writenotnull |= (*(((int*)WriteEvent)+i) = *(((int*)StaticWriteEvent)+i)) != 0; - } - - // /* just a verification code, needs to be removed for performance */ - // for (i=0; i= 0) ? &delta : NULL); - - /* this is an experiment on the separation of the select work */ - // status = (readnotnull ? select(maxfd, ReadEvent, NULL, NULL, (next_time >= 0) ? &delta : NULL) : 0); - // status |= (writenotnull ? select(maxfd, NULL, WriteEvent, NULL, (next_time >= 0) ? &delta : NULL) : 0); - - tv_now(&now); - - if (status > 0) { /* must proceed with events */ - - int fds; - char count; - - for (fds = 0; (fds << INTBITS) < maxfd; fds++) - if ((((int *)(ReadEvent))[fds] | ((int *)(WriteEvent))[fds]) != 0) - for (count = 1< 0 -/* - * Display proxy statistics regularly. It is designed to be called from the - * select_loop(). - */ -int stats(void) { - static int lines; - static struct timeval nextevt; - static struct timeval lastevt; - static struct timeval starttime = {0,0}; - unsigned long totaltime, deltatime; - int ret; - - if (tv_cmp(&now, &nextevt) > 0) { - deltatime = (tv_diff(&lastevt, &now)?:1); - totaltime = (tv_diff(&starttime, &now)?:1); - - if (global.mode & MODE_STATS) { - if ((lines++ % 16 == 0) && !(global.mode & MODE_LOG)) - qfprintf(stderr, - "\n active total tsknew tskgood tskleft tskrght tsknsch tsklsch tskrsch\n"); - if (lines>1) { - qfprintf(stderr,"%07d %07d %07d %07d %07d %07d %07d %07d %07d\n", - actconn, totalconn, - stats_tsk_new, stats_tsk_good, - stats_tsk_left, stats_tsk_right, - stats_tsk_nsrch, stats_tsk_lsrch, stats_tsk_rsrch); - } - } - - tv_delayfrom(&nextevt, &now, STATTIME); - - lastevt=now; - } - ret = tv_remain(&now, &nextevt); - return ret; -} -#endif - - -/* - * this function enables proxies when there are enough free sessions, - * or stops them when the table is full. It is designed to be called from the - * select_loop(). It returns the time left before next expiration event - * during stop time, TIME_ETERNITY otherwise. - */ -static int maintain_proxies(void) { - struct proxy *p; - struct listener *l; - int tleft; /* time left */ - - p = proxy; - tleft = TIME_ETERNITY; /* infinite time */ - - /* if there are enough free sessions, we'll activate proxies */ - if (actconn < global.maxconn) { - while (p) { - if (p->nbconn < p->maxconn) { - if (p->state == PR_STIDLE) { - for (l = p->listen; l != NULL; l = l->next) { - FD_SET(l->fd, StaticReadEvent); - } - p->state = PR_STRUN; - } - } - else { - if (p->state == PR_STRUN) { - for (l = p->listen; l != NULL; l = l->next) { - FD_CLR(l->fd, StaticReadEvent); - } - p->state = PR_STIDLE; - } - } - p = p->next; - } - } - else { /* block all proxies */ - while (p) { - if (p->state == PR_STRUN) { - for (l = p->listen; l != NULL; l = l->next) { - FD_CLR(l->fd, StaticReadEvent); - } - p->state = PR_STIDLE; - } - p = p->next; - } - } - - if (stopping) { - p = proxy; - while (p) { - if (p->state != PR_STSTOPPED) { - int t; - t = tv_remain2(&now, &p->stop_time); - if (t == 0) { - Warning("Proxy %s stopped.\n", p->id); - send_log(p, LOG_WARNING, "Proxy %s stopped.\n", p->id); - - for (l = p->listen; l != NULL; l = l->next) { - fd_delete(l->fd); - listeners--; - } - p->state = PR_STSTOPPED; - } - else { - tleft = MINTIME(t, tleft); - } - } - p = p->next; - } - } - return tleft; -} - -/* - * this function disables health-check servers so that the process will quickly be ignored - * by load balancers. Note that if a proxy was already in the PAUSED state, then its grace - * time will not be used since it would already not listen anymore to the socket. - */ -static void soft_stop(void) { - struct proxy *p; - - stopping = 1; - p = proxy; - tv_now(&now); /* else, the old time before select will be used */ - while (p) { - if (p->state != PR_STSTOPPED) { - Warning("Stopping proxy %s in %d ms.\n", p->id, p->grace); - send_log(p, LOG_WARNING, "Stopping proxy %s in %d ms.\n", p->id, p->grace); - tv_delayfrom(&p->stop_time, &now, p->grace); - } - p = p->next; - } -} - -/* - * Linux unbinds the listen socket after a SHUT_RD, and ignores SHUT_WR. - * Solaris refuses either shutdown(). - * OpenBSD ignores SHUT_RD but closes upon SHUT_WR and refuses to rebind. - * So a common validation path involves SHUT_WR && listen && SHUT_RD. - * If disabling at least one listener returns an error, then the proxy - * state is set to PR_STERROR because we don't know how to resume from this. - */ -static void pause_proxy(struct proxy *p) { - struct listener *l; - for (l = p->listen; l != NULL; l = l->next) { - if (shutdown(l->fd, SHUT_WR) == 0 && listen(l->fd, p->maxconn) == 0 && - shutdown(l->fd, SHUT_RD) == 0) { - FD_CLR(l->fd, StaticReadEvent); - if (p->state != PR_STERROR) - p->state = PR_STPAUSED; - } - else - p->state = PR_STERROR; - } -} - -/* - * This function temporarily disables listening so that another new instance - * can start listening. It is designed to be called upon reception of a - * SIGTTOU, after which either a SIGUSR1 can be sent to completely stop - * the proxy, or a SIGTTIN can be sent to listen again. - */ -static void pause_proxies(void) { - int err; - struct proxy *p; - - err = 0; - p = proxy; - tv_now(&now); /* else, the old time before select will be used */ - while (p) { - if (p->state != PR_STERROR && p->state != PR_STSTOPPED && p->state != PR_STPAUSED) { - Warning("Pausing proxy %s.\n", p->id); - send_log(p, LOG_WARNING, "Pausing proxy %s.\n", p->id); - pause_proxy(p); - if (p->state != PR_STPAUSED) { - err |= 1; - Warning("Proxy %s failed to enter pause mode.\n", p->id); - send_log(p, LOG_WARNING, "Proxy %s failed to enter pause mode.\n", p->id); - } - } - p = p->next; - } - if (err) { - Warning("Some proxies refused to pause, performing soft stop now.\n"); - send_log(p, LOG_WARNING, "Some proxies refused to pause, performing soft stop now.\n"); - soft_stop(); - } -} - - -/* - * This function reactivates listening. This can be used after a call to - * sig_pause(), for example when a new instance has failed starting up. - * It is designed to be called upon reception of a SIGTTIN. - */ -static void listen_proxies(void) { - struct proxy *p; - struct listener *l; - - p = proxy; - tv_now(&now); /* else, the old time before select will be used */ - while (p) { - if (p->state == PR_STPAUSED) { - Warning("Enabling proxy %s.\n", p->id); - send_log(p, LOG_WARNING, "Enabling proxy %s.\n", p->id); - - for (l = p->listen; l != NULL; l = l->next) { - if (listen(l->fd, p->maxconn) == 0) { - if (actconn < global.maxconn && p->nbconn < p->maxconn) { - FD_SET(l->fd, StaticReadEvent); - p->state = PR_STRUN; - } - else - p->state = PR_STIDLE; - } else { - int port; - - if (l->addr.ss_family == AF_INET6) - port = ntohs(((struct sockaddr_in6 *)(&l->addr))->sin6_port); - else - port = ntohs(((struct sockaddr_in *)(&l->addr))->sin_port); - - Warning("Port %d busy while trying to enable proxy %s.\n", - port, p->id); - send_log(p, LOG_WARNING, "Port %d busy while trying to enable proxy %s.\n", - port, p->id); - /* Another port might have been enabled. Let's stop everything. */ - pause_proxy(p); - break; - } - } - } - p = p->next; - } -} - - -/* - * upon SIGUSR1, let's have a soft stop. - */ -void sig_soft_stop(int sig) { - soft_stop(); - signal(sig, SIG_IGN); -} - -/* - * upon SIGTTOU, we pause everything - */ -void sig_pause(int sig) { - pause_proxies(); - signal(sig, sig_pause); -} - -/* - * upon SIGTTIN, let's have a soft stop. - */ -void sig_listen(int sig) { - listen_proxies(); - signal(sig, sig_listen); -} - -/* - * this function dumps every server's state when the process receives SIGHUP. - */ -void sig_dump_state(int sig) { - struct proxy *p = proxy; - - Warning("SIGHUP received, dumping servers states.\n"); - while (p) { - struct server *s = p->srv; - - send_log(p, LOG_NOTICE, "SIGHUP received, dumping servers states for proxy %s.\n", p->id); - while (s) { - snprintf(trash, sizeof(trash), - "SIGHUP: Server %s/%s is %s. Conn: %d act, %d pend, %d tot.", - p->id, s->id, - (s->state & SRV_RUNNING) ? "UP" : "DOWN", - s->cur_sess, s->nbpend, s->cum_sess); - Warning("%s\n", trash); - send_log(p, LOG_NOTICE, "%s\n", trash); - s = s->next; - } - - if (p->srv_act == 0) { - snprintf(trash, sizeof(trash), - "SIGHUP: Proxy %s %s ! Conn: %d act, %d pend (%d unass), %d tot.", - p->id, - (p->srv_bck) ? "is running on backup servers" : "has no server available", - p->nbconn, p->totpend, p->nbpend, p->cum_conn); - } else { - snprintf(trash, sizeof(trash), - "SIGHUP: Proxy %s has %d active servers and %d backup servers available." - " Conn: %d act, %d pend (%d unass), %d tot.", - p->id, p->srv_act, p->srv_bck, - p->nbconn, p->totpend, p->nbpend, p->cum_conn); - } - Warning("%s\n", trash); - send_log(p, LOG_NOTICE, "%s\n", trash); - - p = p->next; - } - signal(sig, sig_dump_state); -} - -void dump(int sig) { - struct task *t, *tnext; - struct session *s; - - tnext = ((struct task *)LIST_HEAD(wait_queue[0]))->next; - while ((t = tnext) != LIST_HEAD(wait_queue[0])) { /* we haven't looped ? */ - tnext = t->next; - s = t->context; - qfprintf(stderr,"[dump] wq: task %p, still %ld ms, " - "cli=%d, srv=%d, cr=%d, cw=%d, sr=%d, sw=%d, " - "req=%d, rep=%d, clifd=%d\n", - s, tv_remain(&now, &t->expire), - s->cli_state, - s->srv_state, - FD_ISSET(s->cli_fd, StaticReadEvent), - FD_ISSET(s->cli_fd, StaticWriteEvent), - FD_ISSET(s->srv_fd, StaticReadEvent), - FD_ISSET(s->srv_fd, StaticWriteEvent), - s->req->l, s->rep?s->rep->l:0, s->cli_fd - ); - } -} - -#ifdef DEBUG_MEMORY -static void fast_stop(void) -{ - struct proxy *p; - p = proxy; - while (p) { - p->grace = 0; - p = p->next; - } - soft_stop(); -} - -void sig_int(int sig) { - /* This would normally be a hard stop, - but we want to be sure about deallocation, - and so on, so we do a soft stop with - 0 GRACE time - */ - fast_stop(); - /* If we are killed twice, we decide to die*/ - signal(sig, SIG_DFL); -} - -void sig_term(int sig) { - /* This would normally be a hard stop, - but we want to be sure about deallocation, - and so on, so we do a soft stop with - 0 GRACE time - */ - fast_stop(); - /* If we are killed twice, we decide to die*/ - signal(sig, SIG_DFL); -} -#endif - -/* returns the pointer to an error in the replacement string, or NULL if OK */ -char *chain_regex(struct hdr_exp **head, regex_t *preg, int action, char *replace) { - struct hdr_exp *exp; - - if (replace != NULL) { - char *err; - err = check_replace_string(replace); - if (err) - return err; - } - - while (*head != NULL) - head = &(*head)->next; - - exp = calloc(1, sizeof(struct hdr_exp)); - - exp->preg = preg; - exp->replace = replace; - exp->action = action; - *head = exp; - - return NULL; -} - - -/* - * parse a line in a section. Returns 0 if OK, -1 if error. - */ -int cfg_parse_global(char *file, int linenum, char **args) { - - if (!strcmp(args[0], "global")) { /* new section */ - /* no option, nothing special to do */ - return 0; - } - else if (!strcmp(args[0], "daemon")) { - global.mode |= MODE_DAEMON; - } - else if (!strcmp(args[0], "debug")) { - global.mode |= MODE_DEBUG; - } - else if (!strcmp(args[0], "noepoll")) { - cfg_polling_mechanism &= ~POLL_USE_EPOLL; - } - else if (!strcmp(args[0], "nopoll")) { - cfg_polling_mechanism &= ~POLL_USE_POLL; - } - else if (!strcmp(args[0], "quiet")) { - global.mode |= MODE_QUIET; - } - else if (!strcmp(args[0], "stats")) { - global.mode |= MODE_STATS; - } - else if (!strcmp(args[0], "uid")) { - if (global.uid != 0) { - Alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); - return 0; - } - if (*(args[1]) == 0) { - Alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); - return -1; - } - global.uid = atol(args[1]); - } - else if (!strcmp(args[0], "gid")) { - if (global.gid != 0) { - Alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); - return 0; - } - if (*(args[1]) == 0) { - Alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); - return -1; - } - global.gid = atol(args[1]); - } - else if (!strcmp(args[0], "nbproc")) { - if (global.nbproc != 0) { - Alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); - return 0; - } - if (*(args[1]) == 0) { - Alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); - return -1; - } - global.nbproc = atol(args[1]); - } - else if (!strcmp(args[0], "maxconn")) { - if (global.maxconn != 0) { - Alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); - return 0; - } - if (*(args[1]) == 0) { - Alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); - return -1; - } - global.maxconn = atol(args[1]); -#ifdef SYSTEM_MAXCONN - if (global.maxconn > DEFAULT_MAXCONN && cfg_maxconn <= DEFAULT_MAXCONN) { - Alert("parsing [%s:%d] : maxconn value %d too high for this system.\nLimiting to %d. Please use '-n' to force the value.\n", file, linenum, global.maxconn, DEFAULT_MAXCONN); - global.maxconn = DEFAULT_MAXCONN; - } -#endif /* SYSTEM_MAXCONN */ - } - else if (!strcmp(args[0], "ulimit-n")) { - if (global.rlimit_nofile != 0) { - Alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); - return 0; - } - if (*(args[1]) == 0) { - Alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); - return -1; - } - global.rlimit_nofile = atol(args[1]); - } - else if (!strcmp(args[0], "chroot")) { - if (global.chroot != NULL) { - Alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); - return 0; - } - if (*(args[1]) == 0) { - Alert("parsing [%s:%d] : '%s' expects a directory as an argument.\n", file, linenum, args[0]); - return -1; - } - global.chroot = strdup(args[1]); - } - else if (!strcmp(args[0], "pidfile")) { - if (global.pidfile != NULL) { - Alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); - return 0; - } - if (*(args[1]) == 0) { - Alert("parsing [%s:%d] : '%s' expects a file name as an argument.\n", file, linenum, args[0]); - return -1; - } - global.pidfile = strdup(args[1]); - } - else if (!strcmp(args[0], "log")) { /* syslog server address */ - struct sockaddr_in *sa; - int facility, level; - - if (*(args[1]) == 0 || *(args[2]) == 0) { - Alert("parsing [%s:%d] : '%s' expects

and as arguments.\n", file, linenum, args[0]); - return -1; - } - - for (facility = 0; facility < NB_LOG_FACILITIES; facility++) - if (!strcmp(log_facilities[facility], args[2])) - break; - - if (facility >= NB_LOG_FACILITIES) { - Alert("parsing [%s:%d] : unknown log facility '%s'\n", file, linenum, args[2]); - exit(1); - } - - level = 7; /* max syslog level = debug */ - if (*(args[3])) { - while (level >= 0 && strcmp(log_levels[level], args[3])) - level--; - if (level < 0) { - Alert("parsing [%s:%d] : unknown optional log level '%s'\n", file, linenum, args[3]); - exit(1); - } - } - - sa = str2sa(args[1]); - if (!sa->sin_port) - sa->sin_port = htons(SYSLOG_PORT); - - if (global.logfac1 == -1) { - global.logsrv1 = *sa; - global.logfac1 = facility; - global.loglev1 = level; - } - else if (global.logfac2 == -1) { - global.logsrv2 = *sa; - global.logfac2 = facility; - global.loglev2 = level; - } - else { - Alert("parsing [%s:%d] : too many syslog servers\n", file, linenum); - return -1; - } - - } - else { - Alert("parsing [%s:%d] : unknown keyword '%s' in '%s' section\n", file, linenum, args[0], "global"); - return -1; - } - return 0; -} - - -void init_default_instance() { - memset(&defproxy, 0, sizeof(defproxy)); - defproxy.mode = PR_MODE_TCP; - defproxy.state = PR_STNEW; - defproxy.maxconn = cfg_maxpconn; - defproxy.conn_retries = CONN_RETRIES; - defproxy.logfac1 = defproxy.logfac2 = -1; /* log disabled */ -} - -/* - * parse a line in a section. Returns 0 if OK, -1 if error. - */ -int cfg_parse_listen(char *file, int linenum, char **args) { - static struct proxy *curproxy = NULL; - struct server *newsrv = NULL; - char *err; - int rc; - - if (!strcmp(args[0], "listen")) { /* new proxy */ - if (!*args[1]) { - Alert("parsing [%s:%d] : '%s' expects an argument and\n" - " optionnally supports [addr1]:port1[-end1]{,[addr]:port[-end]}...\n", - file, linenum, args[0]); - return -1; - } - - if ((curproxy = (struct proxy *)calloc(1, sizeof(struct proxy))) == NULL) { - Alert("parsing [%s:%d] : out of memory.\n", file, linenum); - return -1; - } - - curproxy->next = proxy; - proxy = curproxy; - LIST_INIT(&curproxy->pendconns); - - curproxy->id = strdup(args[1]); - - /* parse the listener address if any */ - if (*args[2]) { - curproxy->listen = str2listener(args[2], curproxy->listen); - if (!curproxy->listen) - return -1; - global.maxsock++; - } - - /* set default values */ - curproxy->state = defproxy.state; - curproxy->maxconn = defproxy.maxconn; - curproxy->conn_retries = defproxy.conn_retries; - curproxy->options = defproxy.options; - - if (defproxy.check_req) - curproxy->check_req = strdup(defproxy.check_req); - curproxy->check_len = defproxy.check_len; - - if (defproxy.cookie_name) - curproxy->cookie_name = strdup(defproxy.cookie_name); - curproxy->cookie_len = defproxy.cookie_len; - - if (defproxy.capture_name) - curproxy->capture_name = strdup(defproxy.capture_name); - curproxy->capture_namelen = defproxy.capture_namelen; - curproxy->capture_len = defproxy.capture_len; - - if (defproxy.errmsg.msg400) - curproxy->errmsg.msg400 = strdup(defproxy.errmsg.msg400); - curproxy->errmsg.len400 = defproxy.errmsg.len400; - - if (defproxy.errmsg.msg403) - curproxy->errmsg.msg403 = strdup(defproxy.errmsg.msg403); - curproxy->errmsg.len403 = defproxy.errmsg.len403; - - if (defproxy.errmsg.msg408) - curproxy->errmsg.msg408 = strdup(defproxy.errmsg.msg408); - curproxy->errmsg.len408 = defproxy.errmsg.len408; - - if (defproxy.errmsg.msg500) - curproxy->errmsg.msg500 = strdup(defproxy.errmsg.msg500); - curproxy->errmsg.len500 = defproxy.errmsg.len500; - - if (defproxy.errmsg.msg502) - curproxy->errmsg.msg502 = strdup(defproxy.errmsg.msg502); - curproxy->errmsg.len502 = defproxy.errmsg.len502; - - if (defproxy.errmsg.msg503) - curproxy->errmsg.msg503 = strdup(defproxy.errmsg.msg503); - curproxy->errmsg.len503 = defproxy.errmsg.len503; - - if (defproxy.errmsg.msg504) - curproxy->errmsg.msg504 = strdup(defproxy.errmsg.msg504); - curproxy->errmsg.len504 = defproxy.errmsg.len504; - - curproxy->clitimeout = defproxy.clitimeout; - curproxy->contimeout = defproxy.contimeout; - curproxy->srvtimeout = defproxy.srvtimeout; - curproxy->mode = defproxy.mode; - curproxy->logfac1 = defproxy.logfac1; - curproxy->logsrv1 = defproxy.logsrv1; - curproxy->loglev1 = defproxy.loglev1; - curproxy->logfac2 = defproxy.logfac2; - curproxy->logsrv2 = defproxy.logsrv2; - curproxy->loglev2 = defproxy.loglev2; - curproxy->to_log = defproxy.to_log & ~LW_COOKIE & ~LW_REQHDR & ~ LW_RSPHDR; - curproxy->grace = defproxy.grace; - curproxy->uri_auth = defproxy.uri_auth; - curproxy->source_addr = defproxy.source_addr; - curproxy->mon_net = defproxy.mon_net; - curproxy->mon_mask = defproxy.mon_mask; - return 0; - } - else if (!strcmp(args[0], "defaults")) { /* use this one to assign default values */ - /* some variables may have already been initialized earlier */ - if (defproxy.check_req) free(defproxy.check_req); - if (defproxy.cookie_name) free(defproxy.cookie_name); - if (defproxy.capture_name) free(defproxy.capture_name); - if (defproxy.errmsg.msg400) free(defproxy.errmsg.msg400); - if (defproxy.errmsg.msg403) free(defproxy.errmsg.msg403); - if (defproxy.errmsg.msg408) free(defproxy.errmsg.msg408); - if (defproxy.errmsg.msg500) free(defproxy.errmsg.msg500); - if (defproxy.errmsg.msg502) free(defproxy.errmsg.msg502); - if (defproxy.errmsg.msg503) free(defproxy.errmsg.msg503); - if (defproxy.errmsg.msg504) free(defproxy.errmsg.msg504); - /* we cannot free uri_auth because it might already be used */ - init_default_instance(); - curproxy = &defproxy; - return 0; - } - else if (curproxy == NULL) { - Alert("parsing [%s:%d] : 'listen' or 'defaults' expected.\n", file, linenum); - return -1; - } - - if (!strcmp(args[0], "bind")) { /* new listen addresses */ - if (curproxy == &defproxy) { - Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); - return -1; - } - - if (strchr(args[1], ':') == NULL) { - Alert("parsing [%s:%d] : '%s' expects [addr1]:port1[-end1]{,[addr]:port[-end]}... as arguments.\n", - file, linenum, args[0]); - return -1; - } - curproxy->listen = str2listener(args[1], curproxy->listen); - if (!curproxy->listen) - return -1; - global.maxsock++; - return 0; - } - else if (!strcmp(args[0], "monitor-net")) { /* set the range of IPs to ignore */ - if (!*args[1] || !str2net(args[1], &curproxy->mon_net, &curproxy->mon_mask)) { - Alert("parsing [%s:%d] : '%s' expects address[/mask].\n", - file, linenum, args[0]); - return -1; - } - /* flush useless bits */ - curproxy->mon_net.s_addr &= curproxy->mon_mask.s_addr; - return 0; - } - else if (!strcmp(args[0], "mode")) { /* sets the proxy mode */ - if (!strcmp(args[1], "http")) curproxy->mode = PR_MODE_HTTP; - else if (!strcmp(args[1], "tcp")) curproxy->mode = PR_MODE_TCP; - else if (!strcmp(args[1], "health")) curproxy->mode = PR_MODE_HEALTH; - else { - Alert("parsing [%s:%d] : unknown proxy mode '%s'.\n", file, linenum, args[1]); - return -1; - } - } - else if (!strcmp(args[0], "disabled")) { /* disables this proxy */ - curproxy->state = PR_STSTOPPED; - } - else if (!strcmp(args[0], "enabled")) { /* enables this proxy (used to revert a disabled default) */ - curproxy->state = PR_STNEW; - } - else if (!strcmp(args[0], "cookie")) { /* cookie name */ - int cur_arg; -// if (curproxy == &defproxy) { -// Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); -// return -1; -// } - - if (curproxy->cookie_name != NULL) { -// Alert("parsing [%s:%d] : cookie name already specified. Continuing.\n", -// file, linenum); -// return 0; - free(curproxy->cookie_name); - } - - if (*(args[1]) == 0) { - Alert("parsing [%s:%d] : '%s' expects as argument.\n", - file, linenum, args[0]); - return -1; - } - curproxy->cookie_name = strdup(args[1]); - curproxy->cookie_len = strlen(curproxy->cookie_name); - - cur_arg = 2; - while (*(args[cur_arg])) { - if (!strcmp(args[cur_arg], "rewrite")) { - curproxy->options |= PR_O_COOK_RW; - } - else if (!strcmp(args[cur_arg], "indirect")) { - curproxy->options |= PR_O_COOK_IND; - } - else if (!strcmp(args[cur_arg], "insert")) { - curproxy->options |= PR_O_COOK_INS; - } - else if (!strcmp(args[cur_arg], "nocache")) { - curproxy->options |= PR_O_COOK_NOC; - } - else if (!strcmp(args[cur_arg], "postonly")) { - curproxy->options |= PR_O_COOK_POST; - } - else if (!strcmp(args[cur_arg], "prefix")) { - curproxy->options |= PR_O_COOK_PFX; - } - else { - Alert("parsing [%s:%d] : '%s' supports 'rewrite', 'insert', 'prefix', 'indirect', 'nocache' and 'postonly' options.\n", - file, linenum, args[0]); - return -1; - } - cur_arg++; - } - if (!POWEROF2(curproxy->options & (PR_O_COOK_RW|PR_O_COOK_IND))) { - Alert("parsing [%s:%d] : cookie 'rewrite' and 'indirect' modes are incompatible.\n", - file, linenum); - return -1; - } - - if (!POWEROF2(curproxy->options & (PR_O_COOK_RW|PR_O_COOK_INS|PR_O_COOK_PFX))) { - Alert("parsing [%s:%d] : cookie 'rewrite', 'insert' and 'prefix' modes are incompatible.\n", - file, linenum); - return -1; - } - }/* end else if (!strcmp(args[0], "cookie")) */ - else if (!strcmp(args[0], "appsession")) { /* cookie name */ -// if (curproxy == &defproxy) { -// Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); -// return -1; -// } - - if (curproxy->appsession_name != NULL) { -// Alert("parsing [%s:%d] : cookie name already specified. Continuing.\n", -// file, linenum); -// return 0; - free(curproxy->appsession_name); - } - - if (*(args[5]) == 0) { - Alert("parsing [%s:%d] : '%s' expects 'appsession' 'len' 'timeout' .\n", - file, linenum, args[0]); - return -1; - } - have_appsession = 1; - curproxy->appsession_name = strdup(args[1]); - curproxy->appsession_name_len = strlen(curproxy->appsession_name); - curproxy->appsession_len = atoi(args[3]); - curproxy->appsession_timeout = atoi(args[5]); - rc = chtbl_init(&(curproxy->htbl_proxy), TBLSIZ, hashpjw, match_str, destroy); - if (rc) { - Alert("Error Init Appsession Hashtable.\n"); - return -1; - } - } /* Url App Session */ - else if (!strcmp(args[0], "capture")) { - if (!strcmp(args[1], "cookie")) { /* name of a cookie to capture */ - // if (curproxy == &defproxy) { - // Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); - // return -1; - // } - - if (curproxy->capture_name != NULL) { - // Alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", - // file, linenum, args[0]); - // return 0; - free(curproxy->capture_name); - } - - if (*(args[4]) == 0) { - Alert("parsing [%s:%d] : '%s' expects 'cookie' 'len' .\n", - file, linenum, args[0]); - return -1; - } - curproxy->capture_name = strdup(args[2]); - curproxy->capture_namelen = strlen(curproxy->capture_name); - curproxy->capture_len = atol(args[4]); - if (curproxy->capture_len >= CAPTURE_LEN) { - Warning("parsing [%s:%d] : truncating capture length to %d bytes.\n", - file, linenum, CAPTURE_LEN - 1); - curproxy->capture_len = CAPTURE_LEN - 1; - } - curproxy->to_log |= LW_COOKIE; - } - else if (!strcmp(args[1], "request") && !strcmp(args[2], "header")) { - struct cap_hdr *hdr; - - if (curproxy == &defproxy) { - Alert("parsing [%s:%d] : '%s %s' not allowed in 'defaults' section.\n", file, linenum, args[0], args[1]); - return -1; - } - - if (*(args[3]) == 0 || strcmp(args[4], "len") != 0 || *(args[5]) == 0) { - Alert("parsing [%s:%d] : '%s %s' expects 'header' 'len' .\n", - file, linenum, args[0], args[1]); - return -1; - } - - hdr = calloc(sizeof(struct cap_hdr), 1); - hdr->next = curproxy->req_cap; - hdr->name = strdup(args[3]); - hdr->namelen = strlen(args[3]); - hdr->len = atol(args[5]); - hdr->index = curproxy->nb_req_cap++; - curproxy->req_cap = hdr; - curproxy->to_log |= LW_REQHDR; - } - else if (!strcmp(args[1], "response") && !strcmp(args[2], "header")) { - struct cap_hdr *hdr; - - if (curproxy == &defproxy) { - Alert("parsing [%s:%d] : '%s %s' not allowed in 'defaults' section.\n", file, linenum, args[0], args[1]); - return -1; - } - - if (*(args[3]) == 0 || strcmp(args[4], "len") != 0 || *(args[5]) == 0) { - Alert("parsing [%s:%d] : '%s %s' expects 'header' 'len' .\n", - file, linenum, args[0], args[1]); - return -1; - } - hdr = calloc(sizeof(struct cap_hdr), 1); - hdr->next = curproxy->rsp_cap; - hdr->name = strdup(args[3]); - hdr->namelen = strlen(args[3]); - hdr->len = atol(args[5]); - hdr->index = curproxy->nb_rsp_cap++; - curproxy->rsp_cap = hdr; - curproxy->to_log |= LW_RSPHDR; - } - else { - Alert("parsing [%s:%d] : '%s' expects 'cookie' or 'request header' or 'response header'.\n", - file, linenum, args[0]); - return -1; - } - } - else if (!strcmp(args[0], "contimeout")) { /* connect timeout */ - if (curproxy->contimeout != defproxy.contimeout) { - Alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); - return 0; - } - if (*(args[1]) == 0) { - Alert("parsing [%s:%d] : '%s' expects an integer as argument.\n", - file, linenum, args[0]); - return -1; - } - curproxy->contimeout = atol(args[1]); - } - else if (!strcmp(args[0], "clitimeout")) { /* client timeout */ - if (curproxy->clitimeout != defproxy.clitimeout) { - Alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", - file, linenum, args[0]); - return 0; - } - if (*(args[1]) == 0) { - Alert("parsing [%s:%d] : '%s' expects an integer as argument.\n", - file, linenum, args[0]); - return -1; - } - curproxy->clitimeout = atol(args[1]); - } - else if (!strcmp(args[0], "srvtimeout")) { /* server timeout */ - if (curproxy->srvtimeout != defproxy.srvtimeout) { - Alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); - return 0; - } - if (*(args[1]) == 0) { - Alert("parsing [%s:%d] : '%s' expects an integer as argument.\n", - file, linenum, args[0]); - return -1; - } - curproxy->srvtimeout = atol(args[1]); - } - else if (!strcmp(args[0], "retries")) { /* connection retries */ - if (*(args[1]) == 0) { - Alert("parsing [%s:%d] : '%s' expects an integer argument (dispatch counts for one).\n", - file, linenum, args[0]); - return -1; - } - curproxy->conn_retries = atol(args[1]); - } - else if (!strcmp(args[0], "stats")) { - if (curproxy != &defproxy && curproxy->uri_auth == defproxy.uri_auth) - curproxy->uri_auth = NULL; /* we must detach from the default config */ - - if (*(args[1]) == 0) { - Alert("parsing [%s:%d] : '%s' expects 'uri', 'realm', 'auth', 'scope' or 'enable'.\n", file, linenum, args[0]); - return -1; - } else if (!strcmp(args[1], "uri")) { - if (*(args[2]) == 0) { - Alert("parsing [%s:%d] : 'uri' needs an URI prefix.\n", file, linenum); - return -1; - } else if (!stats_set_uri(&curproxy->uri_auth, args[2])) { - Alert("parsing [%s:%d] : out of memory.\n", file, linenum); - return -1; - } - } else if (!strcmp(args[1], "realm")) { - if (*(args[2]) == 0) { - Alert("parsing [%s:%d] : 'realm' needs an realm name.\n", file, linenum); - return -1; - } else if (!stats_set_realm(&curproxy->uri_auth, args[2])) { - Alert("parsing [%s:%d] : out of memory.\n", file, linenum); - return -1; - } - } else if (!strcmp(args[1], "auth")) { - if (*(args[2]) == 0) { - Alert("parsing [%s:%d] : 'auth' needs a user:password account.\n", file, linenum); - return -1; - } else if (!stats_add_auth(&curproxy->uri_auth, args[2])) { - Alert("parsing [%s:%d] : out of memory.\n", file, linenum); - return -1; - } - } else if (!strcmp(args[1], "scope")) { - if (*(args[2]) == 0) { - Alert("parsing [%s:%d] : 'scope' needs a proxy name.\n", file, linenum); - return -1; - } else if (!stats_add_scope(&curproxy->uri_auth, args[2])) { - Alert("parsing [%s:%d] : out of memory.\n", file, linenum); - return -1; - } - } else if (!strcmp(args[1], "enable")) { - if (!stats_check_init_uri_auth(&curproxy->uri_auth)) { - Alert("parsing [%s:%d] : out of memory.\n", file, linenum); - return -1; - } - } else { - Alert("parsing [%s:%d] : unknown stats parameter '%s' (expects 'uri', 'realm', 'auth' or 'enable').\n", - file, linenum, args[0]); - return -1; - } - } - else if (!strcmp(args[0], "option")) { - if (*(args[1]) == 0) { - Alert("parsing [%s:%d] : '%s' expects an option name.\n", file, linenum, args[0]); - return -1; - } - if (!strcmp(args[1], "redispatch")) - /* enable reconnections to dispatch */ - curproxy->options |= PR_O_REDISP; -#ifdef TPROXY - else if (!strcmp(args[1], "transparent")) - /* enable transparent proxy connections */ - curproxy->options |= PR_O_TRANSP; -#endif - else if (!strcmp(args[1], "keepalive")) - /* enable keep-alive */ - curproxy->options |= PR_O_KEEPALIVE; - else if (!strcmp(args[1], "forwardfor")) - /* insert x-forwarded-for field */ - curproxy->options |= PR_O_FWDFOR; - else if (!strcmp(args[1], "logasap")) - /* log as soon as possible, without waiting for the session to complete */ - curproxy->options |= PR_O_LOGASAP; - else if (!strcmp(args[1], "abortonclose")) - /* abort connection if client closes during queue or connect() */ - curproxy->options |= PR_O_ABRT_CLOSE; - else if (!strcmp(args[1], "httpclose")) - /* force connection: close in both directions in HTTP mode */ - curproxy->options |= PR_O_HTTP_CLOSE; - else if (!strcmp(args[1], "forceclose")) - /* force connection: close in both directions in HTTP mode and enforce end of session */ - curproxy->options |= PR_O_FORCE_CLO | PR_O_HTTP_CLOSE; - else if (!strcmp(args[1], "checkcache")) - /* require examination of cacheability of the 'set-cookie' field */ - curproxy->options |= PR_O_CHK_CACHE; - else if (!strcmp(args[1], "httplog")) - /* generate a complete HTTP log */ - curproxy->to_log |= LW_DATE | LW_CLIP | LW_SVID | LW_REQ | LW_PXID | LW_RESP | LW_BYTES; - else if (!strcmp(args[1], "tcplog")) - /* generate a detailed TCP log */ - curproxy->to_log |= LW_DATE | LW_CLIP | LW_SVID | LW_PXID | LW_BYTES; - else if (!strcmp(args[1], "dontlognull")) { - /* don't log empty requests */ - curproxy->options |= PR_O_NULLNOLOG; - } - else if (!strcmp(args[1], "tcpka")) { - /* enable TCP keep-alives on client and server sessions */ - curproxy->options |= PR_O_TCP_CLI_KA | PR_O_TCP_SRV_KA; - } - else if (!strcmp(args[1], "clitcpka")) { - /* enable TCP keep-alives on client sessions */ - curproxy->options |= PR_O_TCP_CLI_KA; - } - else if (!strcmp(args[1], "srvtcpka")) { - /* enable TCP keep-alives on server sessions */ - curproxy->options |= PR_O_TCP_SRV_KA; - } - else if (!strcmp(args[1], "allbackups")) { - /* Use all backup servers simultaneously */ - curproxy->options |= PR_O_USE_ALL_BK; - } - else if (!strcmp(args[1], "httpchk")) { - /* use HTTP request to check servers' health */ - if (curproxy->check_req != NULL) { - free(curproxy->check_req); - } - curproxy->options |= PR_O_HTTP_CHK; - if (!*args[2]) { /* no argument */ - curproxy->check_req = strdup(DEF_CHECK_REQ); /* default request */ - curproxy->check_len = strlen(DEF_CHECK_REQ); - } else if (!*args[3]) { /* one argument : URI */ - int reqlen = strlen(args[2]) + strlen("OPTIONS / HTTP/1.0\r\n\r\n"); - curproxy->check_req = (char *)malloc(reqlen); - curproxy->check_len = snprintf(curproxy->check_req, reqlen, - "OPTIONS %s HTTP/1.0\r\n\r\n", args[2]); /* URI to use */ - } else { /* more arguments : METHOD URI [HTTP_VER] */ - int reqlen = strlen(args[2]) + strlen(args[3]) + 3 + strlen("\r\n\r\n"); - if (*args[4]) - reqlen += strlen(args[4]); - else - reqlen += strlen("HTTP/1.0"); - - curproxy->check_req = (char *)malloc(reqlen); - curproxy->check_len = snprintf(curproxy->check_req, reqlen, - "%s %s %s\r\n\r\n", args[2], args[3], *args[4]?args[4]:"HTTP/1.0"); - } - } - else if (!strcmp(args[1], "persist")) { - /* persist on using the server specified by the cookie, even when it's down */ - curproxy->options |= PR_O_PERSIST; - } - else { - Alert("parsing [%s:%d] : unknown option '%s'.\n", file, linenum, args[1]); - return -1; - } - return 0; - } - else if (!strcmp(args[0], "redispatch") || !strcmp(args[0], "redisp")) { - /* enable reconnections to dispatch */ - curproxy->options |= PR_O_REDISP; - } -#ifdef TPROXY - else if (!strcmp(args[0], "transparent")) { - /* enable transparent proxy connections */ - curproxy->options |= PR_O_TRANSP; - } -#endif - else if (!strcmp(args[0], "maxconn")) { /* maxconn */ - if (*(args[1]) == 0) { - Alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); - return -1; - } - curproxy->maxconn = atol(args[1]); - } - else if (!strcmp(args[0], "grace")) { /* grace time (ms) */ - if (*(args[1]) == 0) { - Alert("parsing [%s:%d] : '%s' expects a time in milliseconds.\n", file, linenum, args[0]); - return -1; - } - curproxy->grace = atol(args[1]); - } - else if (!strcmp(args[0], "dispatch")) { /* dispatch address */ - if (curproxy == &defproxy) { - Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); - return -1; - } - if (strchr(args[1], ':') == NULL) { - Alert("parsing [%s:%d] : '%s' expects as argument.\n", file, linenum, args[0]); - return -1; - } - curproxy->dispatch_addr = *str2sa(args[1]); - } - else if (!strcmp(args[0], "balance")) { /* set balancing with optional algorithm */ - if (*(args[1])) { - if (!strcmp(args[1], "roundrobin")) { - curproxy->options |= PR_O_BALANCE_RR; - } - else if (!strcmp(args[1], "source")) { - curproxy->options |= PR_O_BALANCE_SH; - } - else { - Alert("parsing [%s:%d] : '%s' only supports 'roundrobin' and 'source' options.\n", file, linenum, args[0]); - return -1; - } - } - else /* if no option is set, use round-robin by default */ - curproxy->options |= PR_O_BALANCE_RR; - } - else if (!strcmp(args[0], "server")) { /* server address */ - int cur_arg; - char *rport; - char *raddr; - short realport; - int do_check; - - if (curproxy == &defproxy) { - Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); - return -1; - } - - if (!*args[2]) { - Alert("parsing [%s:%d] : '%s' expects and [:] as arguments.\n", - file, linenum, args[0]); - return -1; - } - if ((newsrv = (struct server *)calloc(1, sizeof(struct server))) == NULL) { - Alert("parsing [%s:%d] : out of memory.\n", file, linenum); - return -1; - } - - /* the servers are linked backwards first */ - newsrv->next = curproxy->srv; - curproxy->srv = newsrv; - newsrv->proxy = curproxy; - - LIST_INIT(&newsrv->pendconns); - do_check = 0; - newsrv->state = SRV_RUNNING; /* early server setup */ - newsrv->id = strdup(args[1]); - - /* several ways to check the port component : - * - IP => port=+0, relative - * - IP: => port=+0, relative - * - IP:N => port=N, absolute - * - IP:+N => port=+N, relative - * - IP:-N => port=-N, relative - */ - raddr = strdup(args[2]); - rport = strchr(raddr, ':'); - if (rport) { - *rport++ = 0; - realport = atol(rport); - if (!isdigit((int)*rport)) - newsrv->state |= SRV_MAPPORTS; - } else { - realport = 0; - newsrv->state |= SRV_MAPPORTS; - } - - newsrv->addr = *str2sa(raddr); - newsrv->addr.sin_port = htons(realport); - free(raddr); - - newsrv->curfd = -1; /* no health-check in progress */ - newsrv->inter = DEF_CHKINTR; - newsrv->rise = DEF_RISETIME; - newsrv->fall = DEF_FALLTIME; - newsrv->health = newsrv->rise; /* up, but will fall down at first failure */ - cur_arg = 3; - while (*args[cur_arg]) { - if (!strcmp(args[cur_arg], "cookie")) { - newsrv->cookie = strdup(args[cur_arg + 1]); - newsrv->cklen = strlen(args[cur_arg + 1]); - cur_arg += 2; - } - else if (!strcmp(args[cur_arg], "rise")) { - newsrv->rise = atol(args[cur_arg + 1]); - newsrv->health = newsrv->rise; - cur_arg += 2; - } - else if (!strcmp(args[cur_arg], "fall")) { - newsrv->fall = atol(args[cur_arg + 1]); - cur_arg += 2; - } - else if (!strcmp(args[cur_arg], "inter")) { - newsrv->inter = atol(args[cur_arg + 1]); - cur_arg += 2; - } - else if (!strcmp(args[cur_arg], "port")) { - newsrv->check_port = atol(args[cur_arg + 1]); - cur_arg += 2; - } - else if (!strcmp(args[cur_arg], "backup")) { - newsrv->state |= SRV_BACKUP; - cur_arg ++; - } - else if (!strcmp(args[cur_arg], "weight")) { - int w; - w = atol(args[cur_arg + 1]); - if (w < 1 || w > 256) { - Alert("parsing [%s:%d] : weight of server %s is not within 1 and 256 (%d).\n", - file, linenum, newsrv->id, w); - return -1; - } - newsrv->uweight = w - 1; - cur_arg += 2; - } - else if (!strcmp(args[cur_arg], "minconn")) { - newsrv->minconn = atol(args[cur_arg + 1]); - cur_arg += 2; - } - else if (!strcmp(args[cur_arg], "maxconn")) { - newsrv->maxconn = atol(args[cur_arg + 1]); - cur_arg += 2; - } - else if (!strcmp(args[cur_arg], "check")) { - global.maxsock++; - do_check = 1; - cur_arg += 1; - } - else if (!strcmp(args[cur_arg], "source")) { /* address to which we bind when connecting */ - if (!*args[cur_arg + 1]) { - Alert("parsing [%s:%d] : '%s' expects [:] as argument.\n", - file, linenum, "source"); - return -1; - } - newsrv->state |= SRV_BIND_SRC; - newsrv->source_addr = *str2sa(args[cur_arg + 1]); - cur_arg += 2; - } - else { - Alert("parsing [%s:%d] : server %s only supports options 'backup', 'cookie', 'check', 'inter', 'rise', 'fall', 'port', 'source', 'minconn', 'maxconn' and 'weight'.\n", - file, linenum, newsrv->id); - return -1; - } - } - - if (do_check) { - if (!newsrv->check_port && !(newsrv->state & SRV_MAPPORTS)) - newsrv->check_port = realport; /* by default */ - if (!newsrv->check_port) { - Alert("parsing [%s:%d] : server %s has neither service port nor check port. Check has been disabled.\n", - file, linenum, newsrv->id); - return -1; - } - newsrv->state |= SRV_CHECKED; - } - - if (newsrv->state & SRV_BACKUP) - curproxy->srv_bck++; - else - curproxy->srv_act++; - } - else if (!strcmp(args[0], "log")) { /* syslog server address */ - struct sockaddr_in *sa; - int facility; - - if (*(args[1]) && *(args[2]) == 0 && !strcmp(args[1], "global")) { - curproxy->logfac1 = global.logfac1; - curproxy->logsrv1 = global.logsrv1; - curproxy->loglev1 = global.loglev1; - curproxy->logfac2 = global.logfac2; - curproxy->logsrv2 = global.logsrv2; - curproxy->loglev2 = global.loglev2; - } - else if (*(args[1]) && *(args[2])) { - int level; - - for (facility = 0; facility < NB_LOG_FACILITIES; facility++) - if (!strcmp(log_facilities[facility], args[2])) - break; - - if (facility >= NB_LOG_FACILITIES) { - Alert("parsing [%s:%d] : unknown log facility '%s'\n", file, linenum, args[2]); - exit(1); - } - - level = 7; /* max syslog level = debug */ - if (*(args[3])) { - while (level >= 0 && strcmp(log_levels[level], args[3])) - level--; - if (level < 0) { - Alert("parsing [%s:%d] : unknown optional log level '%s'\n", file, linenum, args[3]); - exit(1); - } - } - - sa = str2sa(args[1]); - if (!sa->sin_port) - sa->sin_port = htons(SYSLOG_PORT); - - if (curproxy->logfac1 == -1) { - curproxy->logsrv1 = *sa; - curproxy->logfac1 = facility; - curproxy->loglev1 = level; - } - else if (curproxy->logfac2 == -1) { - curproxy->logsrv2 = *sa; - curproxy->logfac2 = facility; - curproxy->loglev2 = level; - } - else { - Alert("parsing [%s:%d] : too many syslog servers\n", file, linenum); - return -1; - } - } - else { - Alert("parsing [%s:%d] : 'log' expects either and or 'global' as arguments.\n", - file, linenum); - return -1; - } - } - else if (!strcmp(args[0], "source")) { /* address to which we bind when connecting */ - if (!*args[1]) { - Alert("parsing [%s:%d] : '%s' expects [:] as argument.\n", - file, linenum, "source"); - return -1; - } - - curproxy->source_addr = *str2sa(args[1]); - curproxy->options |= PR_O_BIND_SRC; - } - else if (!strcmp(args[0], "cliexp") || !strcmp(args[0], "reqrep")) { /* replace request header from a regex */ - regex_t *preg; - if (curproxy == &defproxy) { - Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); - return -1; - } - - if (*(args[1]) == 0 || *(args[2]) == 0) { - Alert("parsing [%s:%d] : '%s' expects and as arguments.\n", - file, linenum, args[0]); - return -1; - } - - preg = calloc(1, sizeof(regex_t)); - if (regcomp(preg, args[1], REG_EXTENDED) != 0) { - Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]); - return -1; - } - - err = chain_regex(&curproxy->req_exp, preg, ACT_REPLACE, strdup(args[2])); - if (err) { - Alert("parsing [%s:%d] : invalid character or unterminated sequence in replacement string near '%c'.\n", - file, linenum, *err); - return -1; - } - } - else if (!strcmp(args[0], "reqdel")) { /* delete request header from a regex */ - regex_t *preg; - if (curproxy == &defproxy) { - Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); - return -1; - } - - if (*(args[1]) == 0) { - Alert("parsing [%s:%d] : '%s' expects as an argument.\n", file, linenum, args[0]); - return -1; - } - - preg = calloc(1, sizeof(regex_t)); - if (regcomp(preg, args[1], REG_EXTENDED) != 0) { - Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]); - return -1; - } - - chain_regex(&curproxy->req_exp, preg, ACT_REMOVE, NULL); - } - else if (!strcmp(args[0], "reqdeny")) { /* deny a request if a header matches this regex */ - regex_t *preg; - if (curproxy == &defproxy) { - Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); - return -1; - } - - if (*(args[1]) == 0) { - Alert("parsing [%s:%d] : '%s' expects as an argument.\n", file, linenum, args[0]); - return -1; - } - - preg = calloc(1, sizeof(regex_t)); - if (regcomp(preg, args[1], REG_EXTENDED) != 0) { - Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]); - return -1; - } - - chain_regex(&curproxy->req_exp, preg, ACT_DENY, NULL); - } - else if (!strcmp(args[0], "reqpass")) { /* pass this header without allowing or denying the request */ - regex_t *preg; - if (curproxy == &defproxy) { - Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); - return -1; - } - - if (*(args[1]) == 0) { - Alert("parsing [%s:%d] : '%s' expects as an argument.\n", file, linenum, args[0]); - return -1; - } - - preg = calloc(1, sizeof(regex_t)); - if (regcomp(preg, args[1], REG_EXTENDED) != 0) { - Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]); - return -1; - } - - chain_regex(&curproxy->req_exp, preg, ACT_PASS, NULL); - } - else if (!strcmp(args[0], "reqallow")) { /* allow a request if a header matches this regex */ - regex_t *preg; - if (curproxy == &defproxy) { - Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); - return -1; - } - - if (*(args[1]) == 0) { - Alert("parsing [%s:%d] : '%s' expects as an argument.\n", file, linenum, args[0]); - return -1; - } - - preg = calloc(1, sizeof(regex_t)); - if (regcomp(preg, args[1], REG_EXTENDED) != 0) { - Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]); - return -1; - } - - chain_regex(&curproxy->req_exp, preg, ACT_ALLOW, NULL); - } - else if (!strcmp(args[0], "reqirep")) { /* replace request header from a regex, ignoring case */ - regex_t *preg; - if (curproxy == &defproxy) { - Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); - return -1; - } - - if (*(args[1]) == 0 || *(args[2]) == 0) { - Alert("parsing [%s:%d] : '%s' expects and as arguments.\n", - file, linenum, args[0]); - return -1; - } - - preg = calloc(1, sizeof(regex_t)); - if (regcomp(preg, args[1], REG_EXTENDED | REG_ICASE) != 0) { - Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]); - return -1; - } - - err = chain_regex(&curproxy->req_exp, preg, ACT_REPLACE, strdup(args[2])); - if (err) { - Alert("parsing [%s:%d] : invalid character or unterminated sequence in replacement string near '%c'.\n", - file, linenum, *err); - return -1; - } - } - else if (!strcmp(args[0], "reqidel")) { /* delete request header from a regex ignoring case */ - regex_t *preg; - if (curproxy == &defproxy) { - Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); - return -1; - } - - if (*(args[1]) == 0) { - Alert("parsing [%s:%d] : '%s' expects as an argument.\n", file, linenum, args[0]); - return -1; - } - - preg = calloc(1, sizeof(regex_t)); - if (regcomp(preg, args[1], REG_EXTENDED | REG_ICASE) != 0) { - Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]); - return -1; - } - - chain_regex(&curproxy->req_exp, preg, ACT_REMOVE, NULL); - } - else if (!strcmp(args[0], "reqideny")) { /* deny a request if a header matches this regex ignoring case */ - regex_t *preg; - if (curproxy == &defproxy) { - Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); - return -1; - } - - if (*(args[1]) == 0) { - Alert("parsing [%s:%d] : '%s' expects as an argument.\n", file, linenum, args[0]); - return -1; - } - - preg = calloc(1, sizeof(regex_t)); - if (regcomp(preg, args[1], REG_EXTENDED | REG_ICASE) != 0) { - Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]); - return -1; - } - - chain_regex(&curproxy->req_exp, preg, ACT_DENY, NULL); - } - else if (!strcmp(args[0], "reqipass")) { /* pass this header without allowing or denying the request */ - regex_t *preg; - if (curproxy == &defproxy) { - Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); - return -1; - } - - if (*(args[1]) == 0) { - Alert("parsing [%s:%d] : '%s' expects as an argument.\n", file, linenum, args[0]); - return -1; - } - - preg = calloc(1, sizeof(regex_t)); - if (regcomp(preg, args[1], REG_EXTENDED | REG_ICASE) != 0) { - Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]); - return -1; - } - - chain_regex(&curproxy->req_exp, preg, ACT_PASS, NULL); - } - else if (!strcmp(args[0], "reqiallow")) { /* allow a request if a header matches this regex ignoring case */ - regex_t *preg; - if (curproxy == &defproxy) { - Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); - return -1; - } - - if (*(args[1]) == 0) { - Alert("parsing [%s:%d] : '%s' expects as an argument.\n", file, linenum, args[0]); - return -1; - } - - preg = calloc(1, sizeof(regex_t)); - if (regcomp(preg, args[1], REG_EXTENDED | REG_ICASE) != 0) { - Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]); - return -1; - } - - chain_regex(&curproxy->req_exp, preg, ACT_ALLOW, NULL); - } - else if (!strcmp(args[0], "reqadd")) { /* add request header */ - if (curproxy == &defproxy) { - Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); - return -1; - } - - if (curproxy->nb_reqadd >= MAX_NEWHDR) { - Alert("parsing [%s:%d] : too many '%s'. Continuing.\n", file, linenum, args[0]); - return 0; - } - - if (*(args[1]) == 0) { - Alert("parsing [%s:%d] : '%s' expects
as an argument.\n", file, linenum, args[0]); - return -1; - } - - curproxy->req_add[curproxy->nb_reqadd++] = strdup(args[1]); - } - else if (!strcmp(args[0], "srvexp") || !strcmp(args[0], "rsprep")) { /* replace response header from a regex */ - regex_t *preg; - - if (*(args[1]) == 0 || *(args[2]) == 0) { - Alert("parsing [%s:%d] : '%s' expects and as arguments.\n", - file, linenum, args[0]); - return -1; - } - - preg = calloc(1, sizeof(regex_t)); - if (regcomp(preg, args[1], REG_EXTENDED) != 0) { - Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]); - return -1; - } - - err = chain_regex(&curproxy->rsp_exp, preg, ACT_REPLACE, strdup(args[2])); - if (err) { - Alert("parsing [%s:%d] : invalid character or unterminated sequence in replacement string near '%c'.\n", - file, linenum, *err); - return -1; - } - } - else if (!strcmp(args[0], "rspdel")) { /* delete response header from a regex */ - regex_t *preg; - if (curproxy == &defproxy) { - Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); - return -1; - } - - if (*(args[1]) == 0) { - Alert("parsing [%s:%d] : '%s' expects as an argument.\n", file, linenum, args[0]); - return -1; - } - - preg = calloc(1, sizeof(regex_t)); - if (regcomp(preg, args[1], REG_EXTENDED) != 0) { - Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]); - return -1; - } - - err = chain_regex(&curproxy->rsp_exp, preg, ACT_REMOVE, strdup(args[2])); - if (err) { - Alert("parsing [%s:%d] : invalid character or unterminated sequence in replacement string near '%c'.\n", - file, linenum, *err); - return -1; - } - } - else if (!strcmp(args[0], "rspdeny")) { /* block response header from a regex */ - regex_t *preg; - if (curproxy == &defproxy) { - Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); - return -1; - } - - if (*(args[1]) == 0) { - Alert("parsing [%s:%d] : '%s' expects as an argument.\n", file, linenum, args[0]); - return -1; - } - - preg = calloc(1, sizeof(regex_t)); - if (regcomp(preg, args[1], REG_EXTENDED) != 0) { - Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]); - return -1; - } - - err = chain_regex(&curproxy->rsp_exp, preg, ACT_DENY, strdup(args[2])); - if (err) { - Alert("parsing [%s:%d] : invalid character or unterminated sequence in replacement string near '%c'.\n", - file, linenum, *err); - return -1; - } - } - else if (!strcmp(args[0], "rspirep")) { /* replace response header from a regex ignoring case */ - regex_t *preg; - if (curproxy == &defproxy) { - Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); - return -1; - } - - if (*(args[1]) == 0 || *(args[2]) == 0) { - Alert("parsing [%s:%d] : '%s' expects and as arguments.\n", - file, linenum, args[0]); - return -1; - } - - preg = calloc(1, sizeof(regex_t)); - if (regcomp(preg, args[1], REG_EXTENDED | REG_ICASE) != 0) { - Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]); - return -1; - } - - err = chain_regex(&curproxy->rsp_exp, preg, ACT_REPLACE, strdup(args[2])); - if (err) { - Alert("parsing [%s:%d] : invalid character or unterminated sequence in replacement string near '%c'.\n", - file, linenum, *err); - return -1; - } - } - else if (!strcmp(args[0], "rspidel")) { /* delete response header from a regex ignoring case */ - regex_t *preg; - if (curproxy == &defproxy) { - Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); - return -1; - } - - if (*(args[1]) == 0) { - Alert("parsing [%s:%d] : '%s' expects as an argument.\n", file, linenum, args[0]); - return -1; - } - - preg = calloc(1, sizeof(regex_t)); - if (regcomp(preg, args[1], REG_EXTENDED | REG_ICASE) != 0) { - Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]); - return -1; - } - - err = chain_regex(&curproxy->rsp_exp, preg, ACT_REMOVE, strdup(args[2])); - if (err) { - Alert("parsing [%s:%d] : invalid character or unterminated sequence in replacement string near '%c'.\n", - file, linenum, *err); - return -1; - } - } - else if (!strcmp(args[0], "rspideny")) { /* block response header from a regex ignoring case */ - regex_t *preg; - if (curproxy == &defproxy) { - Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); - return -1; - } - - if (*(args[1]) == 0) { - Alert("parsing [%s:%d] : '%s' expects as an argument.\n", file, linenum, args[0]); - return -1; - } - - preg = calloc(1, sizeof(regex_t)); - if (regcomp(preg, args[1], REG_EXTENDED | REG_ICASE) != 0) { - Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]); - return -1; - } - - err = chain_regex(&curproxy->rsp_exp, preg, ACT_DENY, strdup(args[2])); - if (err) { - Alert("parsing [%s:%d] : invalid character or unterminated sequence in replacement string near '%c'.\n", - file, linenum, *err); - return -1; - } - } - else if (!strcmp(args[0], "rspadd")) { /* add response header */ - if (curproxy == &defproxy) { - Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); - return -1; - } - - if (curproxy->nb_rspadd >= MAX_NEWHDR) { - Alert("parsing [%s:%d] : too many '%s'. Continuing.\n", file, linenum, args[0]); - return 0; - } - - if (*(args[1]) == 0) { - Alert("parsing [%s:%d] : '%s' expects
as an argument.\n", file, linenum, args[0]); - return -1; - } - - curproxy->rsp_add[curproxy->nb_rspadd++] = strdup(args[1]); - } - else if (!strcmp(args[0], "errorloc") || - !strcmp(args[0], "errorloc302") || - !strcmp(args[0], "errorloc303")) { /* error location */ - int errnum, errlen; - char *err; - - // if (curproxy == &defproxy) { - // Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); - // return -1; - // } - - if (*(args[2]) == 0) { - Alert("parsing [%s:%d] : expects and as arguments.\n", file, linenum); - return -1; - } - - errnum = atol(args[1]); - if (!strcmp(args[0], "errorloc303")) { - err = malloc(strlen(HTTP_303) + strlen(args[2]) + 5); - errlen = sprintf(err, "%s%s\r\n\r\n", HTTP_303, args[2]); - } else { - err = malloc(strlen(HTTP_302) + strlen(args[2]) + 5); - errlen = sprintf(err, "%s%s\r\n\r\n", HTTP_302, args[2]); - } - - if (errnum == 400) { - if (curproxy->errmsg.msg400) { - //Warning("parsing [%s:%d] : error %d already defined.\n", file, linenum, errnum); - free(curproxy->errmsg.msg400); - } - curproxy->errmsg.msg400 = err; - curproxy->errmsg.len400 = errlen; - } - else if (errnum == 403) { - if (curproxy->errmsg.msg403) { - //Warning("parsing [%s:%d] : error %d already defined.\n", file, linenum, errnum); - free(curproxy->errmsg.msg403); - } - curproxy->errmsg.msg403 = err; - curproxy->errmsg.len403 = errlen; - } - else if (errnum == 408) { - if (curproxy->errmsg.msg408) { - //Warning("parsing [%s:%d] : error %d already defined.\n", file, linenum, errnum); - free(curproxy->errmsg.msg408); - } - curproxy->errmsg.msg408 = err; - curproxy->errmsg.len408 = errlen; - } - else if (errnum == 500) { - if (curproxy->errmsg.msg500) { - //Warning("parsing [%s:%d] : error %d already defined.\n", file, linenum, errnum); - free(curproxy->errmsg.msg500); - } - curproxy->errmsg.msg500 = err; - curproxy->errmsg.len500 = errlen; - } - else if (errnum == 502) { - if (curproxy->errmsg.msg502) { - //Warning("parsing [%s:%d] : error %d already defined.\n", file, linenum, errnum); - free(curproxy->errmsg.msg502); - } - curproxy->errmsg.msg502 = err; - curproxy->errmsg.len502 = errlen; - } - else if (errnum == 503) { - if (curproxy->errmsg.msg503) { - //Warning("parsing [%s:%d] : error %d already defined.\n", file, linenum, errnum); - free(curproxy->errmsg.msg503); - } - curproxy->errmsg.msg503 = err; - curproxy->errmsg.len503 = errlen; - } - else if (errnum == 504) { - if (curproxy->errmsg.msg504) { - //Warning("parsing [%s:%d] : error %d already defined.\n", file, linenum, errnum); - free(curproxy->errmsg.msg504); - } - curproxy->errmsg.msg504 = err; - curproxy->errmsg.len504 = errlen; - } - else { - Warning("parsing [%s:%d] : error %d relocation will be ignored.\n", file, linenum, errnum); - free(err); - } - } - else { - Alert("parsing [%s:%d] : unknown keyword '%s' in '%s' section\n", file, linenum, args[0], "listen"); - return -1; - } - return 0; -} - - -/* - * This function reads and parses the configuration file given in the argument. - * returns 0 if OK, -1 if error. - */ -int readcfgfile(char *file) { - char thisline[256]; - char *line; - FILE *f; - int linenum = 0; - char *end; - char *args[MAX_LINE_ARGS]; - int arg; - int cfgerr = 0; - int nbchk, mininter; - int confsect = CFG_NONE; - - struct proxy *curproxy = NULL; - struct server *newsrv = NULL; - - if ((f=fopen(file,"r")) == NULL) - return -1; - - init_default_instance(); - - while (fgets(line = thisline, sizeof(thisline), f) != NULL) { - linenum++; - - end = line + strlen(line); - - /* skip leading spaces */ - while (isspace((int)*line)) - line++; - - arg = 0; - args[arg] = line; - - while (*line && arg < MAX_LINE_ARGS) { - /* first, we'll replace \\, \, \#, \r, \n, \t, \xXX with their - * C equivalent value. Other combinations left unchanged (eg: \1). - */ - if (*line == '\\') { - int skip = 0; - if (line[1] == ' ' || line[1] == '\\' || line[1] == '#') { - *line = line[1]; - skip = 1; - } - else if (line[1] == 'r') { - *line = '\r'; - skip = 1; - } - else if (line[1] == 'n') { - *line = '\n'; - skip = 1; - } - else if (line[1] == 't') { - *line = '\t'; - skip = 1; - } - else if (line[1] == 'x') { - if ((line + 3 < end ) && ishex(line[2]) && ishex(line[3])) { - unsigned char hex1, hex2; - hex1 = toupper(line[2]) - '0'; - hex2 = toupper(line[3]) - '0'; - if (hex1 > 9) hex1 -= 'A' - '9' - 1; - if (hex2 > 9) hex2 -= 'A' - '9' - 1; - *line = (hex1<<4) + hex2; - skip = 3; - } - else { - Alert("parsing [%s:%d] : invalid or incomplete '\\x' sequence in '%s'.\n", file, linenum, args[0]); - return -1; - } - } - if (skip) { - memmove(line + 1, line + 1 + skip, end - (line + skip + 1)); - end -= skip; - } - line++; - } - else if (*line == '#' || *line == '\n' || *line == '\r') { - /* end of string, end of loop */ - *line = 0; - break; - } - else if (isspace((int)*line)) { - /* a non-escaped space is an argument separator */ - *line++ = 0; - while (isspace((int)*line)) - line++; - args[++arg] = line; - } - else { - line++; - } - } - - /* empty line */ - if (!**args) - continue; - - /* zero out remaining args */ - while (++arg < MAX_LINE_ARGS) { - args[arg] = line; - } - - if (!strcmp(args[0], "listen") || !strcmp(args[0], "defaults")) /* new proxy */ - confsect = CFG_LISTEN; - else if (!strcmp(args[0], "global")) /* global config */ - confsect = CFG_GLOBAL; - /* else it's a section keyword */ - - switch (confsect) { - case CFG_LISTEN: - if (cfg_parse_listen(file, linenum, args) < 0) - return -1; - break; - case CFG_GLOBAL: - if (cfg_parse_global(file, linenum, args) < 0) - return -1; - break; - default: - Alert("parsing [%s:%d] : unknown keyword '%s' out of section.\n", file, linenum, args[0]); - return -1; - } - - - } - fclose(f); - - /* - * Now, check for the integrity of all that we have collected. - */ - - /* will be needed further to delay some tasks */ - tv_now(&now); - - if ((curproxy = proxy) == NULL) { - Alert("parsing %s : no line. Nothing to do !\n", - file); - return -1; - } - - while (curproxy != NULL) { - if (curproxy->state == PR_STSTOPPED) { - curproxy = curproxy->next; - continue; - } - - if (curproxy->listen == NULL) { - Alert("parsing %s : listener %s has no listen address. Please either specify a valid address on the line, or use the keyword.\n", file, curproxy->id); - cfgerr++; - } - else if ((curproxy->mode != PR_MODE_HEALTH) && - !(curproxy->options & (PR_O_TRANSP | PR_O_BALANCE)) && - (*(int *)&curproxy->dispatch_addr.sin_addr == 0)) { - Alert("parsing %s : listener %s has no dispatch address and is not in transparent or balance mode.\n", - file, curproxy->id); - cfgerr++; - } - else if ((curproxy->mode != PR_MODE_HEALTH) && (curproxy->options & PR_O_BALANCE)) { - if (curproxy->options & PR_O_TRANSP) { - Alert("parsing %s : listener %s cannot use both transparent and balance mode.\n", - file, curproxy->id); - cfgerr++; - } -#ifdef WE_DONT_SUPPORT_SERVERLESS_LISTENERS - else if (curproxy->srv == NULL) { - Alert("parsing %s : listener %s needs at least 1 server in balance mode.\n", - file, curproxy->id); - cfgerr++; - } -#endif - else if (*(int *)&curproxy->dispatch_addr.sin_addr != 0) { - Warning("parsing %s : dispatch address of listener %s will be ignored in balance mode.\n", - file, curproxy->id); - } - } - else if (curproxy->mode == PR_MODE_TCP || curproxy->mode == PR_MODE_HEALTH) { /* TCP PROXY or HEALTH CHECK */ - if (curproxy->cookie_name != NULL) { - Warning("parsing %s : cookie will be ignored for listener %s.\n", - file, curproxy->id); - } - if ((newsrv = curproxy->srv) != NULL) { - Warning("parsing %s : servers will be ignored for listener %s.\n", - file, curproxy->id); - } - if (curproxy->rsp_exp != NULL) { - Warning("parsing %s : server regular expressions will be ignored for listener %s.\n", - file, curproxy->id); - } - if (curproxy->req_exp != NULL) { - Warning("parsing %s : client regular expressions will be ignored for listener %s.\n", - file, curproxy->id); - } - } - else if (curproxy->mode == PR_MODE_HTTP) { /* HTTP PROXY */ - if ((curproxy->cookie_name != NULL) && ((newsrv = curproxy->srv) == NULL)) { - Alert("parsing %s : HTTP proxy %s has a cookie but no server list !\n", - file, curproxy->id); - cfgerr++; - } - } - - /* first, we will invert the servers list order */ - newsrv = NULL; - while (curproxy->srv) { - struct server *next; - - next = curproxy->srv->next; - curproxy->srv->next = newsrv; - newsrv = curproxy->srv; - if (!next) - break; - curproxy->srv = next; - } - - /* now, newsrv == curproxy->srv */ - if (newsrv) { - struct server *srv; - int pgcd; - int act, bck; - - /* We will factor the weights to reduce the table, - * using Euclide's largest common divisor algorithm - */ - pgcd = newsrv->uweight + 1; - for (srv = newsrv->next; srv && pgcd > 1; srv = srv->next) { - int t, w; - - w = srv->uweight + 1; - while (w) { - t = pgcd % w; - pgcd = w; - w = t; - } - } - - act = bck = 0; - for (srv = newsrv; srv; srv = srv->next) { - srv->eweight = ((srv->uweight + 1) / pgcd) - 1; - if (srv->state & SRV_BACKUP) - bck += srv->eweight + 1; - else - act += srv->eweight + 1; - } - - /* this is the largest map we will ever need for this servers list */ - if (act < bck) - act = bck; - - curproxy->srv_map = (struct server **)calloc(act, sizeof(struct server *)); - /* recounts servers and their weights */ - recount_servers(curproxy); - recalc_server_map(curproxy); - } - - if (curproxy->options & PR_O_LOGASAP) - curproxy->to_log &= ~LW_BYTES; - - if (curproxy->errmsg.msg400 == NULL) { - curproxy->errmsg.msg400 = (char *)HTTP_400; - curproxy->errmsg.len400 = strlen(HTTP_400); - } - if (curproxy->errmsg.msg403 == NULL) { - curproxy->errmsg.msg403 = (char *)HTTP_403; - curproxy->errmsg.len403 = strlen(HTTP_403); - } - if (curproxy->errmsg.msg408 == NULL) { - curproxy->errmsg.msg408 = (char *)HTTP_408; - curproxy->errmsg.len408 = strlen(HTTP_408); - } - if (curproxy->errmsg.msg500 == NULL) { - curproxy->errmsg.msg500 = (char *)HTTP_500; - curproxy->errmsg.len500 = strlen(HTTP_500); - } - if (curproxy->errmsg.msg502 == NULL) { - curproxy->errmsg.msg502 = (char *)HTTP_502; - curproxy->errmsg.len502 = strlen(HTTP_502); - } - if (curproxy->errmsg.msg503 == NULL) { - curproxy->errmsg.msg503 = (char *)HTTP_503; - curproxy->errmsg.len503 = strlen(HTTP_503); - } - if (curproxy->errmsg.msg504 == NULL) { - curproxy->errmsg.msg504 = (char *)HTTP_504; - curproxy->errmsg.len504 = strlen(HTTP_504); - } - - /* - * If this server supports a maxconn parameter, it needs a dedicated - * tasks to fill the emptied slots when a connection leaves. - */ - newsrv = curproxy->srv; - while (newsrv != NULL) { - if (newsrv->minconn >= newsrv->maxconn) { - /* Only 'minconn' was specified, or it was higher than or equal - * to 'maxconn'. Let's turn this into maxconn and clean it, as - * this will avoid further useless expensive computations. - */ - newsrv->maxconn = newsrv->minconn; - newsrv->minconn = 0; - } - - if (newsrv->maxconn > 0) { - struct task *t; - - if ((t = pool_alloc(task)) == NULL) { - Alert("parsing [%s:%d] : out of memory.\n", file, linenum); - return -1; - } - - t->next = t->prev = t->rqnext = NULL; /* task not in run queue yet */ - t->wq = LIST_HEAD(wait_queue[1]); /* already assigned to the eternity queue */ - t->state = TASK_IDLE; - t->process = process_srv_queue; - t->context = newsrv; - newsrv->queue_mgt = t; - - /* never run it unless specifically woken up */ - tv_eternity(&t->expire); - task_queue(t); - } - newsrv = newsrv->next; - } - - /* now we'll start this proxy's health checks if any */ - /* 1- count the checkers to run simultaneously */ - nbchk = 0; - mininter = 0; - newsrv = curproxy->srv; - while (newsrv != NULL) { - if (newsrv->state & SRV_CHECKED) { - if (!mininter || mininter > newsrv->inter) - mininter = newsrv->inter; - nbchk++; - } - newsrv = newsrv->next; - } - - /* 2- start them as far as possible from each others while respecting - * their own intervals. For this, we will start them after their own - * interval added to the min interval divided by the number of servers, - * weighted by the server's position in the list. - */ - if (nbchk > 0) { - struct task *t; - int srvpos; - - newsrv = curproxy->srv; - srvpos = 0; - while (newsrv != NULL) { - /* should this server be checked ? */ - if (newsrv->state & SRV_CHECKED) { - if ((t = pool_alloc(task)) == NULL) { - Alert("parsing [%s:%d] : out of memory.\n", file, linenum); - return -1; - } - - t->next = t->prev = t->rqnext = NULL; /* task not in run queue yet */ - t->wq = LIST_HEAD(wait_queue[0]); /* but already has a wait queue assigned */ - t->state = TASK_IDLE; - t->process = process_chk; - t->context = newsrv; - - /* check this every ms */ - tv_delayfrom(&t->expire, &now, - newsrv->inter + mininter * srvpos / nbchk); - task_queue(t); - //task_wakeup(&rq, t); - srvpos++; - } - newsrv = newsrv->next; - } - } - - curproxy = curproxy->next; - } - if (cfgerr > 0) { - Alert("Errors found in configuration file, aborting.\n"); - return -1; - } - else - return 0; -} - - -/* - * This function initializes all the necessary variables. It only returns - * if everything is OK. If something fails, it exits. - */ -void init(int argc, char **argv) { - int i; - int arg_mode = 0; /* MODE_DEBUG, ... */ - char *old_argv = *argv; - char *tmp; - char *cfg_pidfile = NULL; - - if (1<= 127 ). - * URL encoding only requires '"', '#' to be encoded as well as non- - * printable characters above. - */ - memset(hdr_encode_map, 0, sizeof(hdr_encode_map)); - memset(url_encode_map, 0, sizeof(url_encode_map)); - for (i = 0; i < 32; i++) { - FD_SET(i, hdr_encode_map); - FD_SET(i, url_encode_map); - } - for (i = 127; i < 256; i++) { - FD_SET(i, hdr_encode_map); - FD_SET(i, url_encode_map); - } - - tmp = "\"#{|}"; - while (*tmp) { - FD_SET(*tmp, hdr_encode_map); - tmp++; - } - - tmp = "\"#"; - while (*tmp) { - FD_SET(*tmp, url_encode_map); - tmp++; - } - - cfg_polling_mechanism = POLL_USE_SELECT; /* select() is always available */ -#if defined(ENABLE_POLL) - cfg_polling_mechanism |= POLL_USE_POLL; -#endif -#if defined(ENABLE_EPOLL) - cfg_polling_mechanism |= POLL_USE_EPOLL; -#endif - - pid = getpid(); - progname = *argv; - while ((tmp = strchr(progname, '/')) != NULL) - progname = tmp + 1; - - argc--; argv++; - while (argc > 0) { - char *flag; - - if (**argv == '-') { - flag = *argv+1; - - /* 1 arg */ - if (*flag == 'v') { - display_version(); - exit(0); - } -#if defined(ENABLE_EPOLL) - else if (*flag == 'd' && flag[1] == 'e') - cfg_polling_mechanism &= ~POLL_USE_EPOLL; -#endif -#if defined(ENABLE_POLL) - else if (*flag == 'd' && flag[1] == 'p') - cfg_polling_mechanism &= ~POLL_USE_POLL; -#endif - else if (*flag == 'V') - arg_mode |= MODE_VERBOSE; - else if (*flag == 'd' && flag[1] == 'b') - arg_mode |= MODE_FOREGROUND; - else if (*flag == 'd') - arg_mode |= MODE_DEBUG; - else if (*flag == 'c') - arg_mode |= MODE_CHECK; - else if (*flag == 'D') - arg_mode |= MODE_DAEMON | MODE_QUIET; - else if (*flag == 'q') - arg_mode |= MODE_QUIET; - else if (*flag == 's' && (flag[1] == 'f' || flag[1] == 't')) { - /* list of pids to finish ('f') or terminate ('t') */ - - if (flag[1] == 'f') - oldpids_sig = SIGUSR1; /* finish then exit */ - else - oldpids_sig = SIGTERM; /* terminate immediately */ - argv++; argc--; - - if (argc > 0) { - oldpids = calloc(argc, sizeof(int)); - while (argc > 0) { - oldpids[nb_oldpids] = atol(*argv); - if (oldpids[nb_oldpids] <= 0) - usage(old_argv); - argc--; argv++; - nb_oldpids++; - } - } - } -#if STATTIME > 0 - else if (*flag == 's') - arg_mode |= MODE_STATS; - else if (*flag == 'l') - arg_mode |= MODE_LOG; -#endif - else { /* >=2 args */ - argv++; argc--; - if (argc == 0) - usage(old_argv); - - switch (*flag) { - case 'n' : cfg_maxconn = atol(*argv); break; - case 'm' : global.rlimit_memmax = atol(*argv); break; - case 'N' : cfg_maxpconn = atol(*argv); break; - case 'f' : cfg_cfgfile = *argv; break; - case 'p' : cfg_pidfile = *argv; break; - default: usage(old_argv); - } - } - } - else - usage(old_argv); - argv++; argc--; - } - - global.mode = MODE_STARTING | /* during startup, we want most of the alerts */ - (arg_mode & (MODE_DAEMON | MODE_FOREGROUND | MODE_VERBOSE - | MODE_QUIET | MODE_CHECK | MODE_DEBUG)); - - if (!cfg_cfgfile) - usage(old_argv); - - gethostname(hostname, MAX_HOSTNAME_LEN); - - have_appsession = 0; - global.maxsock = 10; /* reserve 10 fds ; will be incremented by socket eaters */ - if (readcfgfile(cfg_cfgfile) < 0) { - Alert("Error reading configuration file : %s\n", cfg_cfgfile); - exit(1); - } - if (have_appsession) - appsession_init(); - - if (global.mode & MODE_CHECK) { - qfprintf(stdout, "Configuration file is valid : %s\n", cfg_cfgfile); - exit(0); - } - - if (cfg_maxconn > 0) - global.maxconn = cfg_maxconn; - - if (cfg_pidfile) { - if (global.pidfile) - free(global.pidfile); - global.pidfile = strdup(cfg_pidfile); - } - - if (global.maxconn == 0) - global.maxconn = DEFAULT_MAXCONN; - - global.maxsock += global.maxconn * 2; /* each connection needs two sockets */ - - if (arg_mode & (MODE_DEBUG | MODE_FOREGROUND)) { - /* command line debug mode inhibits configuration mode */ - global.mode &= ~(MODE_DAEMON | MODE_QUIET); - } - global.mode |= (arg_mode & (MODE_DAEMON | MODE_FOREGROUND | MODE_QUIET | - MODE_VERBOSE | MODE_DEBUG | MODE_STATS | MODE_LOG)); - - if ((global.mode & MODE_DEBUG) && (global.mode & (MODE_DAEMON | MODE_QUIET))) { - Warning(" mode incompatible with and . Keeping only.\n"); - global.mode &= ~(MODE_DAEMON | MODE_QUIET); - } - - if ((global.nbproc > 1) && !(global.mode & MODE_DAEMON)) { - if (!(global.mode & (MODE_FOREGROUND | MODE_DEBUG))) - Warning(" is only meaningful in daemon mode. Setting limit to 1 process.\n"); - global.nbproc = 1; - } - - if (global.nbproc < 1) - global.nbproc = 1; - - StaticReadEvent = (fd_set *)calloc(1, - sizeof(fd_set) * - (global.maxsock + FD_SETSIZE - 1) / FD_SETSIZE); - StaticWriteEvent = (fd_set *)calloc(1, - sizeof(fd_set) * - (global.maxsock + FD_SETSIZE - 1) / FD_SETSIZE); - - fdtab = (struct fdtab *)calloc(1, - sizeof(struct fdtab) * (global.maxsock)); - for (i = 0; i < global.maxsock; i++) { - fdtab[i].state = FD_STCLOSE; - } -} - -/* - * this function starts all the proxies. Its return value is composed from - * ERR_NONE, ERR_RETRYABLE and ERR_FATAL. Retryable errors will only be printed - * if is not zero. - */ -int start_proxies(int verbose) { - struct proxy *curproxy; - struct listener *listener; - int err = ERR_NONE; - int fd, pxerr; - - for (curproxy = proxy; curproxy != NULL; curproxy = curproxy->next) { - if (curproxy->state != PR_STNEW) - continue; /* already initialized */ - - pxerr = 0; - for (listener = curproxy->listen; listener != NULL; listener = listener->next) { - if (listener->fd != -1) - continue; /* already initialized */ - - if ((fd = socket(listener->addr.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) { - if (verbose) - Alert("cannot create listening socket for proxy %s. Aborting.\n", - curproxy->id); - err |= ERR_RETRYABLE; - pxerr |= 1; - continue; - } - - if (fd >= global.maxsock) { - Alert("socket(): not enough free sockets for proxy %s. Raise -n argument. Aborting.\n", - curproxy->id); - close(fd); - err |= ERR_FATAL; - pxerr |= 1; - break; - } - - if ((fcntl(fd, F_SETFL, O_NONBLOCK) == -1) || - (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, - (char *) &one, sizeof(one)) == -1)) { - Alert("cannot make socket non-blocking for proxy %s. Aborting.\n", - curproxy->id); - close(fd); - err |= ERR_FATAL; - pxerr |= 1; - break; - } - - if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(one)) == -1) { - Alert("cannot do so_reuseaddr for proxy %s. Continuing.\n", - curproxy->id); - } - -#ifdef SO_REUSEPORT - /* OpenBSD supports this. As it's present in old libc versions of Linux, - * it might return an error that we will silently ignore. - */ - setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (char *) &one, sizeof(one)); -#endif - if (bind(fd, - (struct sockaddr *)&listener->addr, - listener->addr.ss_family == AF_INET6 ? - sizeof(struct sockaddr_in6) : - sizeof(struct sockaddr_in)) == -1) { - if (verbose) - Alert("cannot bind socket for proxy %s. Aborting.\n", - curproxy->id); - close(fd); - err |= ERR_RETRYABLE; - pxerr |= 1; - continue; - } - - if (listen(fd, curproxy->maxconn) == -1) { - if (verbose) - Alert("cannot listen to socket for proxy %s. Aborting.\n", - curproxy->id); - close(fd); - err |= ERR_RETRYABLE; - pxerr |= 1; - continue; - } - - /* the socket is ready */ - listener->fd = fd; - - /* the function for the accept() event */ - fdtab[fd].read = &event_accept; - fdtab[fd].write = NULL; /* never called */ - fdtab[fd].owner = (struct task *)curproxy; /* reference the proxy instead of a task */ - fdtab[fd].state = FD_STLISTEN; - FD_SET(fd, StaticReadEvent); - fd_insert(fd); - listeners++; - } - - if (!pxerr) { - curproxy->state = PR_STRUN; - send_log(curproxy, LOG_NOTICE, "Proxy %s started.\n", curproxy->id); - } - } - - return err; -} - -int match_str(const void *key1, const void *key2) { - - appsess *temp1,*temp2; - temp1 = (appsess *)key1; - temp2 = (appsess *)key2; - - //fprintf(stdout,">>>>>>>>>>>>>>temp1->sessid :%s:\n",temp1->sessid); - //fprintf(stdout,">>>>>>>>>>>>>>temp2->sessid :%s:\n",temp2->sessid); - - return (strcmp(temp1->sessid,temp2->sessid) == 0); -}/* end match_str */ - -void destroy(void *data) { - appsess *temp1; - - //printf("destroy called\n"); - temp1 = (appsess *)data; - - if (temp1->sessid) - pool_free_to(apools.sessid, temp1->sessid); - - if (temp1->serverid) - pool_free_to(apools.serverid, temp1->serverid); - - pool_free(appsess, temp1); -} /* end destroy */ - -void appsession_cleanup( void ) -{ - struct proxy *p = proxy; - - while(p) { - chtbl_destroy(&(p->htbl_proxy)); - p = p->next; - } -}/* end appsession_cleanup() */ - -void pool_destroy(void **pool) -{ - void *temp, *next; - next = pool; - while (next) { - temp = next; - next = *(void **)temp; - free(temp); - } -}/* end pool_destroy() */ - -void deinit(void) { - struct proxy *p = proxy; - struct cap_hdr *h,*h_next; - struct server *s,*s_next; - struct listener *l,*l_next; - - while (p) { - if (p->id) - free(p->id); - - if (p->check_req) - free(p->check_req); - - if (p->cookie_name) - free(p->cookie_name); - - if (p->capture_name) - free(p->capture_name); - - /* only strup if the user have set in config. - When should we free it?! - if (p->errmsg.msg400) free(p->errmsg.msg400); - if (p->errmsg.msg403) free(p->errmsg.msg403); - if (p->errmsg.msg408) free(p->errmsg.msg408); - if (p->errmsg.msg500) free(p->errmsg.msg500); - if (p->errmsg.msg502) free(p->errmsg.msg502); - if (p->errmsg.msg503) free(p->errmsg.msg503); - if (p->errmsg.msg504) free(p->errmsg.msg504); - */ - if (p->appsession_name) - free(p->appsession_name); - - h = p->req_cap; - while (h) { - h_next = h->next; - if (h->name) - free(h->name); - pool_destroy(h->pool); - free(h); - h = h_next; - }/* end while(h) */ - - h = p->rsp_cap; - while (h) { - h_next = h->next; - if (h->name) - free(h->name); - - pool_destroy(h->pool); - free(h); - h = h_next; - }/* end while(h) */ - - s = p->srv; - while (s) { - s_next = s->next; - if (s->id) - free(s->id); - - if (s->cookie) - free(s->cookie); - - free(s); - s = s_next; - }/* end while(s) */ - - l = p->listen; - while (l) { - l_next = l->next; - free(l); - l = l_next; - }/* end while(l) */ - - pool_destroy((void **) p->req_cap_pool); - pool_destroy((void **) p->rsp_cap_pool); - p = p->next; - }/* end while(p) */ - - if (global.chroot) free(global.chroot); - if (global.pidfile) free(global.pidfile); - - if (StaticReadEvent) free(StaticReadEvent); - if (StaticWriteEvent) free(StaticWriteEvent); - if (fdtab) free(fdtab); - - pool_destroy(pool_session); - pool_destroy(pool_buffer); - pool_destroy(pool_fdtab); - pool_destroy(pool_requri); - pool_destroy(pool_task); - pool_destroy(pool_capture); - pool_destroy(pool_appsess); - - if (have_appsession) { - pool_destroy(apools.serverid); - pool_destroy(apools.sessid); - } -} /* end deinit() */ - -/* sends the signal to all pids found in */ -static void tell_old_pids(int sig) { - int p; - for (p = 0; p < nb_oldpids; p++) - kill(oldpids[p], sig); -} - -int main(int argc, char **argv) { - int err, retry; - struct rlimit limit; - FILE *pidfile = NULL; - init(argc, argv); - - signal(SIGQUIT, dump); - signal(SIGUSR1, sig_soft_stop); - signal(SIGHUP, sig_dump_state); -#ifdef DEBUG_MEMORY - signal(SIGINT, sig_int); - signal(SIGTERM, sig_term); -#endif - - /* on very high loads, a sigpipe sometimes happen just between the - * getsockopt() which tells "it's OK to write", and the following write :-( - */ -#ifndef MSG_NOSIGNAL - signal(SIGPIPE, SIG_IGN); -#endif - - /* We will loop at most 100 times with 10 ms delay each time. - * That's at most 1 second. We only send a signal to old pids - * if we cannot grab at least one port. - */ - retry = MAX_START_RETRIES; - err = ERR_NONE; - while (retry >= 0) { - struct timeval w; - err = start_proxies(retry == 0 || nb_oldpids == 0); - if (err != ERR_RETRYABLE) - break; - if (nb_oldpids == 0) - break; - - /* FIXME-20060514: Solaris and OpenBSD do not support shutdown() on - * listening sockets. So on those platforms, it would be wiser to - * simply send SIGUSR1, which will not be undoable. - */ - tell_old_pids(SIGTTOU); - /* give some time to old processes to stop listening */ - w.tv_sec = 0; - w.tv_usec = 10*1000; - select(0, NULL, NULL, NULL, &w); - retry--; - } - - /* Note: start_proxies() sends an alert when it fails. */ - if (err != ERR_NONE) { - if (retry != MAX_START_RETRIES && nb_oldpids) - tell_old_pids(SIGTTIN); - exit(1); - } - - if (listeners == 0) { - Alert("[%s.main()] No enabled listener found (check the keywords) ! Exiting.\n", argv[0]); - /* Note: we don't have to send anything to the old pids because we - * never stopped them. */ - exit(1); - } - - /* prepare pause/play signals */ - signal(SIGTTOU, sig_pause); - signal(SIGTTIN, sig_listen); - - if (global.mode & MODE_DAEMON) { - global.mode &= ~MODE_VERBOSE; - global.mode |= MODE_QUIET; - } - - /* MODE_QUIET can inhibit alerts and warnings below this line */ - - global.mode &= ~MODE_STARTING; - if ((global.mode & MODE_QUIET) && !(global.mode & MODE_VERBOSE)) { - /* detach from the tty */ - fclose(stdin); fclose(stdout); fclose(stderr); - close(0); close(1); close(2); - } - - /* open log & pid files before the chroot */ - if (global.mode & MODE_DAEMON && global.pidfile != NULL) { - int pidfd; - unlink(global.pidfile); - pidfd = open(global.pidfile, O_CREAT | O_WRONLY | O_TRUNC, 0644); - if (pidfd < 0) { - Alert("[%s.main()] Cannot create pidfile %s\n", argv[0], global.pidfile); - if (nb_oldpids) - tell_old_pids(SIGTTIN); - exit(1); - } - pidfile = fdopen(pidfd, "w"); - } - - /* chroot if needed */ - if (global.chroot != NULL) { - if (chroot(global.chroot) == -1) { - Alert("[%s.main()] Cannot chroot(%s).\n", argv[0], global.chroot); - if (nb_oldpids) - tell_old_pids(SIGTTIN); - } - chdir("/"); - } - - /* ulimits */ - if (!global.rlimit_nofile) - global.rlimit_nofile = global.maxsock; - - if (global.rlimit_nofile) { - limit.rlim_cur = limit.rlim_max = global.rlimit_nofile; - if (setrlimit(RLIMIT_NOFILE, &limit) == -1) { - Warning("[%s.main()] Cannot raise FD limit to %d.\n", argv[0], global.rlimit_nofile); - } - } - - if (global.rlimit_memmax) { - limit.rlim_cur = limit.rlim_max = - global.rlimit_memmax * 1048576 / global.nbproc; -#ifdef RLIMIT_AS - if (setrlimit(RLIMIT_AS, &limit) == -1) { - Warning("[%s.main()] Cannot fix MEM limit to %d megs.\n", - argv[0], global.rlimit_memmax); - } -#else - if (setrlimit(RLIMIT_DATA, &limit) == -1) { - Warning("[%s.main()] Cannot fix MEM limit to %d megs.\n", - argv[0], global.rlimit_memmax); - } -#endif - } - - if (nb_oldpids) - tell_old_pids(oldpids_sig); - - /* Note that any error at this stage will be fatal because we will not - * be able to restart the old pids. - */ - - /* setgid / setuid */ - if (global.gid && setgid(global.gid) == -1) { - Alert("[%s.main()] Cannot set gid %d.\n", argv[0], global.gid); - exit(1); - } - - if (global.uid && setuid(global.uid) == -1) { - Alert("[%s.main()] Cannot set uid %d.\n", argv[0], global.uid); - exit(1); - } - - /* check ulimits */ - limit.rlim_cur = limit.rlim_max = 0; - getrlimit(RLIMIT_NOFILE, &limit); - if (limit.rlim_cur < global.maxsock) { - Warning("[%s.main()] FD limit (%d) too low for maxconn=%d/maxsock=%d. Please raise 'ulimit-n' to %d or more to avoid any trouble.\n", - argv[0], limit.rlim_cur, global.maxconn, global.maxsock, global.maxsock); - } - - if (global.mode & MODE_DAEMON) { - int ret = 0; - int proc; - - /* the father launches the required number of processes */ - for (proc = 0; proc < global.nbproc; proc++) { - ret = fork(); - if (ret < 0) { - Alert("[%s.main()] Cannot fork.\n", argv[0]); - if (nb_oldpids) - exit(1); /* there has been an error */ - } - else if (ret == 0) /* child breaks here */ - break; - if (pidfile != NULL) { - fprintf(pidfile, "%d\n", ret); - fflush(pidfile); - } - } - /* close the pidfile both in children and father */ - if (pidfile != NULL) - fclose(pidfile); - free(global.pidfile); - - if (proc == global.nbproc) - exit(0); /* parent must leave */ - - /* if we're NOT in QUIET mode, we should now close the 3 first FDs to ensure - * that we can detach from the TTY. We MUST NOT do it in other cases since - * it would have already be done, and 0-2 would have been affected to listening - * sockets - */ - if (!(global.mode & MODE_QUIET)) { - /* detach from the tty */ - fclose(stdin); fclose(stdout); fclose(stderr); - close(0); close(1); close(2); /* close all fd's */ - global.mode |= MODE_QUIET; /* ensure that we won't say anything from now */ - } - pid = getpid(); /* update child's pid */ - setsid(); - } - -#if defined(ENABLE_EPOLL) - if (cfg_polling_mechanism & POLL_USE_EPOLL) { - if (epoll_loop(POLL_LOOP_ACTION_INIT)) { - epoll_loop(POLL_LOOP_ACTION_RUN); - epoll_loop(POLL_LOOP_ACTION_CLEAN); - cfg_polling_mechanism &= POLL_USE_EPOLL; - } - else { - Warning("epoll() is not available. Using poll()/select() instead.\n"); - cfg_polling_mechanism &= ~POLL_USE_EPOLL; - } - } -#endif - -#if defined(ENABLE_POLL) - if (cfg_polling_mechanism & POLL_USE_POLL) { - if (poll_loop(POLL_LOOP_ACTION_INIT)) { - poll_loop(POLL_LOOP_ACTION_RUN); - poll_loop(POLL_LOOP_ACTION_CLEAN); - cfg_polling_mechanism &= POLL_USE_POLL; - } - else { - Warning("poll() is not available. Using select() instead.\n"); - cfg_polling_mechanism &= ~POLL_USE_POLL; - } - } -#endif - if (cfg_polling_mechanism & POLL_USE_SELECT) { - if (select_loop(POLL_LOOP_ACTION_INIT)) { - select_loop(POLL_LOOP_ACTION_RUN); - select_loop(POLL_LOOP_ACTION_CLEAN); - cfg_polling_mechanism &= POLL_USE_SELECT; - } - } - - - /* Free all Hash Keys and all Hash elements */ - appsession_cleanup(); - /* Do some cleanup */ - deinit(); - - exit(0); -} - -#if defined(DEBUG_HASH) -static void print_table(const CHTbl *htbl) { - - ListElmt *element; - int i; - appsess *asession; - - /***************************************************************************** - * * - * Display the chained hash table. * - * * - *****************************************************************************/ - - fprintf(stdout, "Table size is %d\n", chtbl_size(htbl)); - - for (i = 0; i < TBLSIZ; i++) { - fprintf(stdout, "Bucket[%03d]\n", i); - - for (element = list_head(&htbl->table[i]); element != NULL; element = list_next(element)) { - //fprintf(stdout, "%c", *(char *)list_data(element)); - asession = (appsess *)list_data(element); - fprintf(stdout, "ELEM :%s:", asession->sessid); - fprintf(stdout, " Server :%s: \n", asession->serverid); - //fprintf(stdout, " Server request_count :%li:\n",asession->request_count); - } - - fprintf(stdout, "\n"); - } - return; -} /* end print_table */ -#endif - -static int appsession_init(void) -{ - static int initialized = 0; - int idlen; - struct server *s; - struct proxy *p = proxy; - - if (!initialized) { - if (!appsession_task_init()) { - apools.sessid = NULL; - apools.serverid = NULL; - apools.ser_waste = 0; - apools.ser_use = 0; - apools.ser_msize = sizeof(void *); - apools.ses_waste = 0; - apools.ses_use = 0; - apools.ses_msize = sizeof(void *); - while (p) { - s = p->srv; - if (apools.ses_msize < p->appsession_len) - apools.ses_msize = p->appsession_len; - while (s) { - idlen = strlen(s->id); - if (apools.ser_msize < idlen) - apools.ser_msize = idlen; - s = s->next; - } - p = p->next; - } - apools.ser_msize ++; /* we use strings, so reserve space for '\0' */ - apools.ses_msize ++; - } - else { - fprintf(stderr, "appsession_task_init failed\n"); - return -1; - } - initialized ++; - } - return 0; -} - -static int appsession_task_init(void) -{ - static int initialized = 0; - struct task *t; - if (!initialized) { - if ((t = pool_alloc(task)) == NULL) - return -1; - t->next = t->prev = t->rqnext = NULL; - t->wq = LIST_HEAD(wait_queue[0]); - t->state = TASK_IDLE; - t->context = NULL; - tv_delayfrom(&t->expire, &now, TBLCHKINT); - task_queue(t); - t->process = appsession_refresh; - initialized ++; - } - return 0; -} - -static int appsession_refresh(struct task *t) { - struct proxy *p = proxy; - CHTbl *htbl; - ListElmt *element, *last; - int i; - appsess *asession; - void *data; - - while (p) { - if (p->appsession_name != NULL) { - htbl = &p->htbl_proxy; - /* if we ever give up the use of TBLSIZ, we need to change this */ - for (i = 0; i < TBLSIZ; i++) { - last = NULL; - for (element = list_head(&htbl->table[i]); element != NULL; element = list_next(element)) { - asession = (appsess *)list_data(element); - if (tv_cmp2_ms(&asession->expire, &now) <= 0) { - if ((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) { - int len; - /* - on Linux NULL pointers are catched by sprintf, on solaris -> segfault - */ - len = sprintf(trash, "appsession_refresh: cleaning up expired Session '%s' on Server %s\n", - asession->sessid, asession->serverid?asession->serverid:"(null)"); - write(1, trash, len); - } - /* delete the expired element from within the hash table */ - if ((list_rem_next(&htbl->table[i], last, (void **)&data) == 0) - && (htbl->table[i].destroy != NULL)) { - htbl->table[i].destroy(data); - } - if (last == NULL) {/* patient lost his head, get a new one */ - element = list_head(&htbl->table[i]); - if (element == NULL) break; /* no heads left, go to next patient */ - } - else - element = last; - }/* end if (tv_cmp2_ms(&asession->expire, &now) <= 0) */ - else - last = element; - }/* end for (element = list_head(&htbl->table[i]); element != NULL; element = list_next(element)) */ - } - } - p = p->next; - } - tv_delayfrom(&t->expire, &now, TBLCHKINT); /* check expiration every 5 seconds */ - return TBLCHKINT; -} /* end appsession_refresh */ - - -/* - * Local variables: - * c-indent-level: 4 - * c-basic-offset: 4 - * End: - */ diff --git a/include/appsession.h b/include/appsession.h deleted file mode 100644 index 8a3103459..000000000 --- a/include/appsession.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef _APPSESS_H -#define _APPSESS_H - -#define TBLSIZ 10 -#define TBLCHKINT 5000 /* The time between two calls of appsession_refresh in ms */ - -/* - These Parts are copied from - - http://www.oreilly.com/catalog/masteralgoc/index.html - Mastering Algorithms with C - By Kyle Loudon - ISBN: 1-56592-453-3 - Publishd by O'Reilly - - We have added our own struct to these function. - */ - -#include -#include -#include -/* end of copied parts */ - -struct app_pool { - void **sessid; - void **serverid; - int ses_waste, ses_use, ses_msize; - int ser_waste, ser_use, ser_msize; -}; - -struct app_pool apools; -int have_appsession; - -/* Callback for hash_lookup */ -int match_str(const void *key1, const void *key2); - -/* Callback for destroy */ -void destroy(void *data); - -#if defined(DEBUG_HASH) -static void print_table(const CHTbl *htbl); -#endif - -#endif diff --git a/include/haproxy/appsession.h b/include/haproxy/appsession.h new file mode 100644 index 000000000..94a7e65cc --- /dev/null +++ b/include/haproxy/appsession.h @@ -0,0 +1,58 @@ +#ifndef _HAPROXY_APPSESS_H +#define _HAPROXY_APPSESS_H + +#define TBLSIZ 10 +#define TBLCHKINT 5000 /* The time between two calls of appsession_refresh in ms */ + +#include + +#include +#include +#include + +#include + +typedef struct appsessions { + char *sessid; + char *serverid; + struct timeval expire; /* next expiration time for this application session */ + unsigned long int request_count; +} appsess; + +#define sizeof_appsess sizeof(struct appsessions) +extern void **pool_appsess; + +struct app_pool { + void **sessid; + void **serverid; + int ses_waste, ses_use, ses_msize; + int ser_waste, ser_use, ser_msize; +}; + +extern struct app_pool apools; +extern int have_appsession; + + +/* Callback for hash_lookup */ +int match_str(const void *key1, const void *key2); + +/* Callback for destroy */ +void destroy(void *data); + +#if defined(DEBUG_HASH) +static void print_table(const CHTbl *htbl); +#endif + +int appsession_refresh(struct task *t); +int appsession_task_init(void); +int appsession_init(void); +void appsession_cleanup(void); + +#endif /* _HAPROXY_APPSESS_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/base64.h b/include/haproxy/base64.h similarity index 66% rename from include/base64.h rename to include/haproxy/base64.h index 6408acfd0..d22d903a4 100644 --- a/include/base64.h +++ b/include/haproxy/base64.h @@ -1,6 +1,8 @@ /* + * include/haproxy/base64.h * Ascii to Base64 conversion as described in RFC1421. - * Copyright 2006 Willy Tarreau + * + * Copyright 2006 Willy Tarreau * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -9,9 +11,10 @@ * */ -#ifndef BASE64_H -#define BASE64_H +#ifndef _HAPROXY_BASE64_H +#define _HAPROXY_BASE64_H int a2base64(char *in, int ilen, char *out, int olen); +extern const char base64tab[]; -#endif /* BASE64_H */ +#endif /* _HAPROXY_BASE64_H */ diff --git a/include/haproxy/cfgparse.h b/include/haproxy/cfgparse.h new file mode 100644 index 000000000..f945d9c20 --- /dev/null +++ b/include/haproxy/cfgparse.h @@ -0,0 +1,45 @@ +/* + include/haproxy/cfgparse.h + Configuration parsing functions. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _HAPROXY_CFGPARSE_H +#define _HAPROXY_CFGPARSE_H + +/* configuration sections */ +#define CFG_NONE 0 +#define CFG_GLOBAL 1 +#define CFG_LISTEN 2 + +extern int cfg_maxpconn; +extern int cfg_maxconn; + +int cfg_parse_global(char *file, int linenum, char **args); +int cfg_parse_listen(char *file, int linenum, char **args); +int readcfgfile(char *file); + + +#endif /* _HAPROXY_CFGPARSE_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/chtbl.h b/include/haproxy/chtbl.h similarity index 96% rename from include/chtbl.h rename to include/haproxy/chtbl.h index 87b777ac1..fe4de70e4 100644 --- a/include/chtbl.h +++ b/include/haproxy/chtbl.h @@ -15,8 +15,8 @@ * * *****************************************************************************/ -#ifndef CHTBL_H -#define CHTBL_H +#ifndef _HAPROXY_CHTBL_H +#define _HAPROXY_CHTBL_H #include @@ -59,4 +59,5 @@ int chtbl_lookup(const CHTbl *htbl, void **data); #define chtbl_size(htbl) ((htbl)->size) -#endif +#endif /* _HAPROXY_CHTBL_H */ + diff --git a/include/haproxy/compat.h b/include/haproxy/compat.h new file mode 100644 index 000000000..bf4c843c7 --- /dev/null +++ b/include/haproxy/compat.h @@ -0,0 +1,62 @@ +/* + include/haproxy/compat.h + Operating system compatibility interface. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _HAPROXY_COMPAT_H +#define _HAPROXY_COMPAT_H + +/* This is needed on Linux for Netfilter includes */ +#include + +/* INTBITS + * how many bits are needed to code the size of an int on the target platform. + * (eg: 32bits -> 5) + */ +#define INTBITS 5 + +/* this is for libc5 for example */ +#ifndef TCP_NODELAY +#define TCP_NODELAY 1 +#endif + +#ifndef SHUT_RD +#define SHUT_RD 0 +#endif + +#ifndef SHUT_WR +#define SHUT_WR 1 +#endif + +#if defined(TPROXY) && defined(NETFILTER) +#include +#endif + +#if defined(__dietlibc__) +#include +#endif + +#endif /* _HAPROXY_COMPAT_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/haproxy/config.h b/include/haproxy/config.h new file mode 100644 index 000000000..968e3c151 --- /dev/null +++ b/include/haproxy/config.h @@ -0,0 +1,34 @@ +/* + include/haproxy/config.h + This files contains most of the user-configurable settings. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _HAPROXY_CONFIG_H +#define _HAPROXY_CONFIG_H + +#include + +/* this reduces the number of calls to select() by choosing appropriate + * sheduler precision in milliseconds. It should be near the minimum + * time that is needed by select() to collect all events. All timeouts + * are rounded up by adding this value prior to pass it to select(). + */ +#define SCHEDULER_RESOLUTION 9 + +#endif /* _HAPROXY_CONFIG_H */ diff --git a/include/haproxy/defaults.h b/include/haproxy/defaults.h new file mode 100644 index 000000000..f01dc67f6 --- /dev/null +++ b/include/haproxy/defaults.h @@ -0,0 +1,94 @@ +/* + include/haproxy/defaults.h + Miscellaneous default values. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _HAPROXY_DEFAULTS_H +#define _HAPROXY_DEFAULTS_H + + +/* CONFIG_HAP_MEM_OPTIM + * This enables use of memory pools instead of malloc()/free(). There + * is no reason to disable it, except perhaps for rare debugging. + */ +#ifndef CONFIG_HAP_NO_MEM_OPTIM +# define CONFIG_HAP_MEM_OPTIM +#endif /* CONFIG_HAP_NO_MEM_OPTIM */ + +/* + * BUFSIZE defines the size of a read and write buffer. It is the maximum + * amount of bytes which can be stored by the proxy for each session. However, + * when reading HTTP headers, the proxy needs some spare space to add or rewrite + * headers if needed. The size of this spare is defined with MAXREWRITE. So it + * is not possible to process headers longer than BUFSIZE-MAXREWRITE bytes. By + * default, BUFSIZE=16384 bytes and MAXREWRITE=BUFSIZE/2, so the maximum length + * of headers accepted is 8192 bytes, which is in line with Apache's limits. + */ +#ifndef BUFSIZE +#define BUFSIZE 16384 +#endif + +// reserved buffer space for header rewriting +#ifndef MAXREWRITE +#define MAXREWRITE (BUFSIZE / 2) +#endif + +#define REQURI_LEN 1024 +#define CAPTURE_LEN 64 + +// max # args on a configuration line +#define MAX_LINE_ARGS 40 + +// max # of added headers per request +#define MAX_NEWHDR 10 + +// max # of matches per regexp +#define MAX_MATCH 10 + +// cookie delimitor in "prefix" mode. This character is inserted between the +// persistence cookie and the original value. The '~' is allowed by RFC2965, +// and should not be too common in server names. +#ifndef COOKIE_DELIM +#define COOKIE_DELIM '~' +#endif + +#define CONN_RETRIES 3 + +#define CHK_CONNTIME 2000 +#define DEF_CHKINTR 2000 +#define DEF_FALLTIME 3 +#define DEF_RISETIME 2 +#define DEF_CHECK_REQ "OPTIONS / HTTP/1.0\r\n\r\n" + +/* Default connections limit. + * + * A system limit can be enforced at build time in order to avoid using haproxy + * beyond reasonable system limits. For this, just define SYSTEM_MAXCONN to the + * absolute limit accepted by the system. If the configuration specifies a + * higher value, it will be capped to SYSTEM_MAXCONN and a warning will be + * emitted. The only way to override this limit will be to set it via the + * command-line '-n' argument. + */ +#ifndef SYSTEM_MAXCONN +#define DEFAULT_MAXCONN 2000 +#else +#define DEFAULT_MAXCONN SYSTEM_MAXCONN +#endif + +#endif /* _HAPROXY_DEFAULTS_H */ diff --git a/include/epoll.h b/include/haproxy/epoll.h similarity index 53% rename from include/epoll.h rename to include/haproxy/epoll.h index 189242e78..d1bd112f1 100644 --- a/include/epoll.h +++ b/include/haproxy/epoll.h @@ -1,3 +1,24 @@ +/* + include/haproxy/epoll.h + epoll definitions for older libc. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + /* * Those constants were found both in glibc and in the Linux kernel. * They are provided here because the epoll() syscall is featured in @@ -5,6 +26,9 @@ * just a basic definition. */ +#ifndef _HAPROXY_EPOLL_H +#define _HAPROXY_EPOLL_H + #include #include @@ -27,13 +51,13 @@ #endif struct epoll_event { - uint32_t events; - union { - void *ptr; - int fd; - uint32_t u32; - uint64_t u64; - } data; + uint32_t events; + union { + void *ptr; + int fd; + uint32_t u32; + uint64_t u64; + } data; }; @@ -64,6 +88,16 @@ struct epoll_event { #define __NR_epoll_wait 256 #endif -_syscall1 (int, epoll_create, int, size); -_syscall4 (int, epoll_ctl, int, epfd, int, op, int, fd, struct epoll_event *, event); -_syscall4 (int, epoll_wait, int, epfd, struct epoll_event *, events, int, maxevents, int, timeout); +extern int epoll_create(int size); +extern int epoll_ctl(int epfd, int op, int fd, struct epoll_event * event); +extern int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout); + +#endif /* _HAPROXY_EPOLL_H */ + + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/hashpjw.h b/include/haproxy/hashpjw.h similarity index 83% rename from include/hashpjw.h rename to include/haproxy/hashpjw.h index a20b5b3b2..2788395db 100644 --- a/include/hashpjw.h +++ b/include/haproxy/hashpjw.h @@ -16,17 +16,8 @@ * * *****************************************************************************/ -#ifndef HASHPJW_H -#define HASHPJW_H - -#include - -typedef struct appsessions { - char *sessid; - char *serverid; - struct timeval expire; /* next expiration time for this application session */ - unsigned long int request_count; -} appsess; /* end struct appsessions */ +#ifndef _HAPROXY_HASHPJW_H +#define _HAPROXY_HASHPJW_H /***************************************************************************** * * @@ -44,4 +35,4 @@ typedef struct appsessions { int hashpjw(const void *key); -#endif +#endif /* _HAPROXY_HASHPJW_H */ diff --git a/include/list.h b/include/haproxy/list.h similarity index 85% rename from include/list.h rename to include/haproxy/list.h index ae7f0868e..16895734c 100644 --- a/include/list.h +++ b/include/haproxy/list.h @@ -15,8 +15,8 @@ * * *****************************************************************************/ -#ifndef LIST_H -#define LIST_H +#ifndef _HAPROXY_LIST_H +#define _HAPROXY_LIST_H #include @@ -27,9 +27,8 @@ *****************************************************************************/ typedef struct ListElmt_ { - - void *data; - struct ListElmt_ *next; + void *data; + struct ListElmt_ *next; } ListElmt; /***************************************************************************** @@ -39,12 +38,12 @@ typedef struct ListElmt_ { *****************************************************************************/ typedef struct List_ { - int size; - int (*match)(const void *key1, const void *key2); - void (*destroy)(void *data); + int size; + int (*match)(const void *key1, const void *key2); + void (*destroy)(void *data); - ListElmt *head; - ListElmt *tail; + ListElmt *head; + ListElmt *tail; } List; /***************************************************************************** @@ -75,4 +74,11 @@ int list_rem_next(List *list, ListElmt *element, void **data); #define list_next(element) ((element)->next) -#endif +#endif /* _HAPROXY_LIST_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/haproxy/memory.h b/include/haproxy/memory.h new file mode 100644 index 000000000..3f269d2c8 --- /dev/null +++ b/include/haproxy/memory.h @@ -0,0 +1,122 @@ +/* + include/haproxy/memory.h + Memory management definitions.. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _HAPROXY_MEMORY_H +#define _HAPROXY_MEMORY_H + +#include + +#include + +#define sizeof_requri REQURI_LEN +#define sizeof_capture CAPTURE_LEN +/* + * Returns a pointer to an area of <__len> bytes taken from the pool or + * dynamically allocated. In the first case, <__pool> is updated to point to + * the next element in the list. + */ +#define pool_alloc_from(__pool, __len) \ +({ \ + void *__p; \ + if ((__p = (__pool)) == NULL) \ + __p = malloc(((__len) >= sizeof (void *)) ? \ + (__len) : sizeof(void *)); \ + else { \ + __pool = *(void **)(__pool); \ + } \ + __p; \ +}) + +/* + * Puts a memory area back to the corresponding pool. + * Items are chained directly through a pointer that + * is written in the beginning of the memory area, so + * there's no need for any carrier cell. This implies + * that each memory area is at least as big as one + * pointer. + */ +#define pool_free_to(__pool, __ptr) \ +({ \ + *(void **)(__ptr) = (void *)(__pool); \ + __pool = (void *)(__ptr); \ +}) + + +#ifdef CONFIG_HAP_MEM_OPTIM +/* + * Returns a pointer to type taken from the + * pool or dynamically allocated. In the + * first case, is updated to point to the + * next element in the list. + */ +#define pool_alloc(type) \ +({ \ + void *__p; \ + if ((__p = pool_##type) == NULL) \ + __p = malloc(sizeof_##type); \ + else { \ + pool_##type = *(void **)pool_##type; \ + } \ + __p; \ +}) + +/* + * Puts a memory area back to the corresponding pool. + * Items are chained directly through a pointer that + * is written in the beginning of the memory area, so + * there's no need for any carrier cell. This implies + * that each memory area is at least as big as one + * pointer. + */ +#define pool_free(type, ptr) \ +({ \ + *(void **)ptr = (void *)pool_##type; \ + pool_##type = (void *)ptr; \ +}) + +#else +#define pool_alloc(type) (calloc(1,sizeof_##type)) +#define pool_free(type, ptr) (free(ptr)) +#endif /* CONFIG_HAP_MEM_OPTIM */ + +/* + * This function destroys a pull by freeing it completely. + * This should be called only under extreme circumstances. + */ +static inline void pool_destroy(void **pool) +{ + void *temp, *next; + next = pool; + while (next) { + temp = next; + next = *(void **)temp; + free(temp); + } +} + +#endif /* _HAPROXY_MEMORY_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/mini-clist.h b/include/haproxy/mini-clist.h similarity index 95% rename from include/mini-clist.h rename to include/haproxy/mini-clist.h index 899e20c17..bf9bbe93c 100644 --- a/include/mini-clist.h +++ b/include/haproxy/mini-clist.h @@ -1,11 +1,11 @@ /* * list.h : list manipulation macros and structures. - * (C) 2002-2006 - Willy Tarreau - willy@ant-computing.com + * Copyright 2002-2006 Willy Tarreau * */ -#ifndef __MINI_CLIST_H__ -#define __MINI_CLIST_H__ +#ifndef _HAPROXY_MINI_CLIST_H +#define _HAPROXY_MINI_CLIST_H /* these are circular or bidirectionnal lists only. Each list pointer points to * another list pointer in a structure, and not the structure itself. The @@ -17,6 +17,8 @@ struct list { struct list *p; /* prev */ }; +#define LIST_HEAD(a) ((void *)(&(a))) + #define LIST_INIT(l) ((l)->n = (l)->p = (l)) /* dual linked lists : @@ -89,4 +91,4 @@ struct list { for ( ; (iterator) != (end_item); (iterator) = (backup), \ backup = LIST_ELEM((iterator)->struct_member.n, struct_type, struct_member)) -#endif /* __MINI_CLIST_H__ */ +#endif /* _HAPROXY_MINI_CLIST_H */ diff --git a/include/haproxy/regex.h b/include/haproxy/regex.h new file mode 100644 index 000000000..03b4ca690 --- /dev/null +++ b/include/haproxy/regex.h @@ -0,0 +1,61 @@ +/* + include/haproxy/regex.h + This file defines everything related to regular expressions. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _HAPROXY_REGEX_H +#define _HAPROXY_REGEX_H + +#include + +#ifdef USE_PCRE +#include +#include +#else +#include +#endif + +/* what to do when a header matches a regex */ +#define ACT_ALLOW 0 /* allow the request */ +#define ACT_REPLACE 1 /* replace the matching header */ +#define ACT_REMOVE 2 /* remove the matching header */ +#define ACT_DENY 3 /* deny the request */ +#define ACT_PASS 4 /* pass this header without allowing or denying the request */ + +struct hdr_exp { + struct hdr_exp *next; + regex_t *preg; /* expression to look for */ + int action; /* ACT_ALLOW, ACT_REPLACE, ACT_REMOVE, ACT_DENY */ + char *replace; /* expression to set instead */ +}; + +extern regmatch_t pmatch[MAX_MATCH]; + +int exp_replace(char *dst, char *src, char *str, regmatch_t *matches); +char *check_replace_string(char *str); +char *chain_regex(struct hdr_exp **head, regex_t *preg, int action, char *replace); + +#endif /* _HAPROXY_REGEX_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/haproxy/standard.h b/include/haproxy/standard.h new file mode 100644 index 000000000..05a2b0dbb --- /dev/null +++ b/include/haproxy/standard.h @@ -0,0 +1,92 @@ +/* + include/haproxy/standard.h + This files contains some general purpose functions and macros. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _HAPROXY_STANDARD_H +#define _HAPROXY_STANDARD_H + +#include +#include +#include + + +/****** string-specific macros and functions ******/ +/* if a > max, then bound to . The macro returns the new */ +#define UBOUND(a, max) ({ typeof(a) b = (max); if ((a) > b) (a) = b; (a); }) + +/* if a < min, then bound to . The macro returns the new */ +#define LBOUND(a, min) ({ typeof(a) b = (min); if ((a) < b) (a) = b; (a); }) + +/* returns 1 only if only zero or one bit is set in X, which means that X is a + * power of 2, and 0 otherwise */ +#define POWEROF2(x) (((x) & ((x)-1)) == 0) + + +/* + * copies at most chars from to . Last char is always + * set to 0, unless is 0. The number of chars copied is returned + * (excluding the terminating zero). + * This code has been optimized for size and speed : on x86, it's 45 bytes + * long, uses only registers, and consumes only 4 cycles per char. + */ +extern int strlcpy2(char *dst, const char *src, int size); + +/* + * This function simply returns a statically allocated string containing + * the ascii representation for number 'n' in decimal. + */ +extern char *ultoa(unsigned long n); + +/* + * Returns non-zero if character is a hex digit (0-9, a-f, A-F), else zero. + */ +extern int ishex(char s); + +/* + * converts to a struct sockaddr_in* which is locally allocated. + * The format is "addr:port", where "addr" can be a dotted IPv4 address, + * a host name, or empty or "*" to indicate INADDR_ANY. + */ +struct sockaddr_in *str2sa(char *str); + +/* + * converts to a two struct in_addr* which are locally allocated. + * The format is "addr[/mask]", where "addr" cannot be empty, and mask + * is optionnal and either in the dotted or CIDR notation. + * Note: "addr" can also be a hostname. Returns 1 if OK, 0 if error. + */ +int str2net(char *str, struct in_addr *addr, struct in_addr *mask); + +/* will try to encode the string replacing all characters tagged in + * with the hexadecimal representation of their ASCII-code (2 digits) + * prefixed by , and will store the result between (included) + * and (excluded), and will always terminate the string with a '\0' + * before . The position of the '\0' is returned if the conversion + * completes. If bytes are missing between and , then the + * conversion will be incomplete and truncated. If <= , the '\0' + * cannot even be stored so we return without writing the 0. + * The input string must also be zero-terminated. + */ +extern const char hextab[]; +char *encode_string(char *start, char *stop, + const char escape, const fd_set *map, + const char *string); + +#endif /* _HAPROXY_STANDARD_H */ diff --git a/include/haproxy/template.h b/include/haproxy/template.h new file mode 100644 index 000000000..da2b9d5d1 --- /dev/null +++ b/include/haproxy/template.h @@ -0,0 +1,33 @@ +/* + include/haproxy/template.h + This file serves as a template for future include files. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _HAPROXY_TEMPLATE_H +#define _HAPROXY_TEMPLATE_H + + +#endif /* _HAPROXY_TEMPLATE_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/haproxy/time.h b/include/haproxy/time.h new file mode 100644 index 000000000..dd3e70155 --- /dev/null +++ b/include/haproxy/time.h @@ -0,0 +1,176 @@ +/* + include/haproxy/time.h + Time calculation functions and macros. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _HAPROXY_TIME_H +#define _HAPROXY_TIME_H + +#include +#include + +#define TIME_ETERNITY -1 + +/* returns the lowest delay amongst and , and respects TIME_ETERNITY */ +#define MINTIME(old, new) (((new)<0)?(old):(((old)<0||(new)<(old))?(new):(old))) +#define SETNOW(a) (*a=now) + +extern struct timeval now; /* the current date at any moment */ +extern struct timeval start_date; /* the process's start date */ + + +/* + * adds ms to , set the result to and returns a pointer + */ +struct timeval *tv_delayfrom(struct timeval *tv, struct timeval *from, int ms); + +/* + * compares and modulo 1ms: returns 0 if equal, -1 if tv1 < tv2, 1 if tv1 > tv2 + * Must not be used when either argument is eternity. Use tv_cmp2_ms() for that. + */ +int tv_cmp_ms(struct timeval *tv1, struct timeval *tv2); + +/* + * compares and : returns 0 if equal, -1 if tv1 < tv2, 1 if tv1 > tv2, + * considering that 0 is the eternity. + */ +int tv_cmp2(struct timeval *tv1, struct timeval *tv2); +/* + * compares and modulo 1 ms: returns 0 if equal, -1 if tv1 < tv2, 1 if tv1 > tv2, + * considering that 0 is the eternity. + */ +int tv_cmp2_ms(struct timeval *tv1, struct timeval *tv2); + +/* + * returns the remaining time between tv1=now and event=tv2 + * if tv2 is passed, 0 is returned. + * Returns TIME_ETERNITY if tv2 is eternity. + */ +unsigned long tv_remain2(struct timeval *tv1, struct timeval *tv2); + + +/* sets to the current time */ +static inline struct timeval *tv_now(struct timeval *tv) +{ + if (tv) + gettimeofday(tv, NULL); + return tv; +} + +/* + * compares and : returns 0 if equal, -1 if tv1 < tv2, 1 if tv1 > tv2 + * Must not be used when either argument is eternity. Use tv_cmp2() for that. + */ +static inline int tv_cmp(struct timeval *tv1, struct timeval *tv2) +{ + if (tv1->tv_sec < tv2->tv_sec) + return -1; + else if (tv1->tv_sec > tv2->tv_sec) + return 1; + else if (tv1->tv_usec < tv2->tv_usec) + return -1; + else if (tv1->tv_usec > tv2->tv_usec) + return 1; + else + return 0; +} + +/* + * returns the difference, in ms, between tv1 and tv2 + * Must not be used when either argument is eternity. + */ +static inline unsigned long tv_diff(struct timeval *tv1, struct timeval *tv2) +{ + unsigned long ret; + + ret = (tv2->tv_sec - tv1->tv_sec) * 1000; + if (tv2->tv_usec > tv1->tv_usec) + ret += (tv2->tv_usec - tv1->tv_usec) / 1000; + else + ret -= (tv1->tv_usec - tv2->tv_usec) / 1000; + return (unsigned long) ret; +} + +/* + * returns the remaining time between tv1=now and event=tv2 + * if tv2 is passed, 0 is returned. + * Must not be used when either argument is eternity. + */ +static inline unsigned long tv_remain(struct timeval *tv1, struct timeval *tv2) +{ + unsigned long ret; + + if (tv_cmp_ms(tv1, tv2) >= 0) + return 0; /* event elapsed */ + + ret = (tv2->tv_sec - tv1->tv_sec) * 1000; + if (tv2->tv_usec > tv1->tv_usec) + ret += (tv2->tv_usec - tv1->tv_usec) / 1000; + else + ret -= (tv1->tv_usec - tv2->tv_usec) / 1000; + return (unsigned long) ret; +} + + +/* + * zeroes a struct timeval + */ + +static inline struct timeval *tv_eternity(struct timeval *tv) +{ + tv->tv_sec = tv->tv_usec = 0; + return tv; +} + +/* + * returns 1 if tv is null, else 0 + */ +static inline int tv_iseternity(struct timeval *tv) +{ + if (tv->tv_sec == 0 && tv->tv_usec == 0) + return 1; + else + return 0; +} + +/* + * returns the first event between tv1 and tv2 into tvmin. + * a zero tv is ignored. tvmin is returned. + */ +static inline struct timeval *tv_min(struct timeval *tvmin, + struct timeval *tv1, struct timeval *tv2) +{ + + if (tv_cmp2(tv1, tv2) <= 0) + *tvmin = *tv1; + else + *tvmin = *tv2; + + return tvmin; +} + + +#endif /* _HAPROXY_TIME_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/uri_auth.h b/include/haproxy/uri_auth.h similarity index 94% rename from include/uri_auth.h rename to include/haproxy/uri_auth.h index 80149ca4c..9eed7303b 100644 --- a/include/uri_auth.h +++ b/include/haproxy/uri_auth.h @@ -1,7 +1,7 @@ /* * URI-based user authentication using the HTTP basic method. * - * Copyright 2006 Willy Tarreau + * Copyright 2006 Willy Tarreau * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -10,8 +10,8 @@ * */ -#ifndef URI_AUTH_H -#define URI_AUTH_H +#ifndef _HAPROXY_URI_AUTH_H +#define _HAPROXY_URI_AUTH_H /* here we find a very basic list of base64-encoded 'user:passwd' strings */ struct user_auth { struct user_auth *next; /* next entry, NULL if none */ @@ -65,4 +65,4 @@ struct uri_auth *stats_set_realm(struct uri_auth **root, char *realm); struct uri_auth *stats_add_auth(struct uri_auth **root, char *user); struct uri_auth *stats_add_scope(struct uri_auth **root, char *scope); -#endif /* URI_AUTH_H */ +#endif /* _HAPROXY_URI_AUTH_H */ diff --git a/include/haproxy/version.h b/include/haproxy/version.h new file mode 100644 index 000000000..16e0d54a8 --- /dev/null +++ b/include/haproxy/version.h @@ -0,0 +1,39 @@ +/* + include/haproxy/version.h + This file serves as a template for future include files. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _HAPROXY_VERSION_H +#define _HAPROXY_VERSION_H + +#ifdef CONFIG_PRODUCT_NAME +#define PRODUCT_NAME CONFIG_PRODUCT_NAME +#else +#define PRODUCT_NAME "HAProxy" +#endif + +#ifndef HAPROXY_VERSION +#define HAPROXY_VERSION "1.3.0" +#endif + +#ifndef HAPROXY_DATE +#define HAPROXY_DATE "2006/06/26" +#endif + +#endif /* _HAPROXY_VERSION_H */ diff --git a/include/proto/backend.h b/include/proto/backend.h new file mode 100644 index 000000000..5f24be6d8 --- /dev/null +++ b/include/proto/backend.h @@ -0,0 +1,125 @@ +/* + include/proto/backend.h + Functions prototypes for the backend. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _PROTO_BACKEND_H +#define _PROTO_BACKEND_H + + +#include +#include + +#include + +int assign_server(struct session *s); +int assign_server_address(struct session *s); +int assign_server_and_queue(struct session *s); +int connect_server(struct session *s); +int srv_count_retry_down(struct session *t, int conn_err); +int srv_retryable_connect(struct session *t); +int srv_redispatch_connect(struct session *t); + +void recount_servers(struct proxy *px); +void recalc_server_map(struct proxy *px); + + +/* + * This function tries to find a running server with free connection slots for + * the proxy following the round-robin method. + * If any server is found, it will be returned and px->srv_rr_idx will be updated + * to point to the next server. If no valid server is found, NULL is returned. + */ +static inline struct server *get_server_rr_with_conns(struct proxy *px) +{ + int newidx; + struct server *srv; + + if (px->srv_map_sz == 0) + return NULL; + + if (px->srv_rr_idx < 0 || px->srv_rr_idx >= px->srv_map_sz) + px->srv_rr_idx = 0; + newidx = px->srv_rr_idx; + + do { + srv = px->srv_map[newidx++]; + if (!srv->maxconn || srv->cur_sess < srv_dynamic_maxconn(srv)) { + px->srv_rr_idx = newidx; + return srv; + } + if (newidx == px->srv_map_sz) + newidx = 0; + } while (newidx != px->srv_rr_idx); + + return NULL; +} + + +/* + * This function tries to find a running server for the proxy following + * the round-robin method. + * If any server is found, it will be returned and px->srv_rr_idx will be updated + * to point to the next server. If no valid server is found, NULL is returned. + */ +static inline struct server *get_server_rr(struct proxy *px) +{ + if (px->srv_map_sz == 0) + return NULL; + + if (px->srv_rr_idx < 0 || px->srv_rr_idx >= px->srv_map_sz) + px->srv_rr_idx = 0; + return px->srv_map[px->srv_rr_idx++]; +} + + +/* + * This function tries to find a running server for the proxy following + * the source hash method. Depending on the number of active/backup servers, + * it will either look for active servers, or for backup servers. + * If any server is found, it will be returned. If no valid server is found, + * NULL is returned. + */ +static inline struct server *get_server_sh(struct proxy *px, char *addr, int len) +{ + unsigned int h, l; + + if (px->srv_map_sz == 0) + return NULL; + + l = h = 0; + if (px->srv_act > 1 || (px->srv_act == 0 && px->srv_bck > 1)) { + while ((l + sizeof (int)) <= len) { + h ^= ntohl(*(unsigned int *)(&addr[l])); + l += sizeof (int); + } + h %= px->srv_map_sz; + } + return px->srv_map[h]; +} + + +#endif /* _PROTO_BACKEND_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/proto/buffers.h b/include/proto/buffers.h new file mode 100644 index 000000000..85557511b --- /dev/null +++ b/include/proto/buffers.h @@ -0,0 +1,85 @@ +/* + include/proto/buffers.h + Buffer management definitions, macros and inline functions. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _PROTO_BUFFERS_H +#define _PROTO_BUFFERS_H + +#include +#include + +/* returns 1 if the buffer is empty, 0 otherwise */ +static inline int buffer_isempty(struct buffer *buf) +{ + return buf->l == 0; +} + +/* returns 1 if the buffer is full, 0 otherwise */ +static inline int buffer_isfull(struct buffer *buf) { + return buf->l == BUFSIZE; +} + +/* flushes any content from buffer */ +static inline void buffer_flush(struct buffer *buf) +{ + buf->r = buf->h = buf->lr = buf->w = buf->data; + buf->l = 0; +} + + +/* returns the maximum number of bytes writable at once in this buffer */ +static inline int buffer_max(struct buffer *buf) +{ + if (buf->l == BUFSIZE) + return 0; + else if (buf->r >= buf->w) + return buf->data + BUFSIZE - buf->r; + else + return buf->w - buf->r; +} + + +/* + * Tries to realign the given buffer, and returns how many bytes can be written + * there at once without overwriting anything. + */ +static inline int buffer_realign(struct buffer *buf) +{ + if (buf->l == 0) { + /* let's realign the buffer to optimize I/O */ + buf->r = buf->w = buf->h = buf->lr = buf->data; + } + return buffer_max(buf); +} + + +int buffer_write(struct buffer *buf, const char *msg, int len); +int buffer_replace(struct buffer *b, char *pos, char *end, char *str); +int buffer_replace2(struct buffer *b, char *pos, char *end, char *str, int len); + + +#endif /* _PROTO_BUFFERS_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/proto/checks.h b/include/proto/checks.h new file mode 100644 index 000000000..d16a288bd --- /dev/null +++ b/include/proto/checks.h @@ -0,0 +1,36 @@ +/* + include/proto/checks.h + Functions prototypes for the checks. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _PROTO_CHECKS_H +#define _PROTO_CHECKS_H + +#include + +int process_chk(struct task *t); + +#endif /* _PROTO_CHECKS_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/proto/client.h b/include/proto/client.h new file mode 100644 index 000000000..e119dca6b --- /dev/null +++ b/include/proto/client.h @@ -0,0 +1,38 @@ +/* + include/proto/client.h + This file contains client-side definitions. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _PROTO_CLIENT_H +#define _PROTO_CLIENT_H + +#include + + +int event_accept(int fd); + + +#endif /* _PROTO_CLIENT_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/proto/fd.h b/include/proto/fd.h new file mode 100644 index 000000000..699047bdb --- /dev/null +++ b/include/proto/fd.h @@ -0,0 +1,52 @@ +/* + include/proto/fd.h + File descriptors states. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _PROTO_FD_H +#define _PROTO_FD_H + +#include +#include +#include + +#include + +/* Deletes an FD from the fdsets, and recomputes the maxfd limit. + * The file descriptor is also closed. + */ +void fd_delete(int fd); + + +/* recomputes the maxfd limit from the fd */ +static inline void fd_insert(int fd) +{ + if (fd + 1 > maxfd) + maxfd = fd + 1; +} + + +#endif /* _PROTO_FD_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/proto/log.h b/include/proto/log.h new file mode 100644 index 000000000..5cc568a5f --- /dev/null +++ b/include/proto/log.h @@ -0,0 +1,84 @@ +/* + include/proto/log.h + This file contains definitions of log-related functions, structures, + and macros. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _PROTO_LOG_H +#define _PROTO_LOG_H + +#include +#include + +#include +#include +#include + +/* + * Displays the message on stderr with the date and pid. Overrides the quiet + * mode during startup. + */ +void Alert(char *fmt, ...); + +/* + * Displays the message on stderr with the date and pid. + */ +void Warning(char *fmt, ...); + +/* + * Displays the message on only if quiet mode is not set. + */ +void qfprintf(FILE *out, char *fmt, ...); + +/* + * This function sends a syslog message to both log servers of a proxy, + * or to global log servers if the proxy is NULL. + * It also tries not to waste too much time computing the message header. + * It doesn't care about errors nor does it report them. + */ +void send_log(struct proxy *p, int level, char *message, ...); + +/* + * send a log for the session when we have enough info about it + */ +void sess_log(struct session *s); + +/* + * returns log level for or -1 if not found. + */ +int get_log_level(const char *lev); + +/* + * returns log facility for or -1 if not found. + */ +int get_log_facility(const char *fac); + +/* + * Initializes some data needed later. + */ +void init_log(); + +#endif /* _PROTO_LOG_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/proto/polling.h b/include/proto/polling.h new file mode 100644 index 000000000..6b349c609 --- /dev/null +++ b/include/proto/polling.h @@ -0,0 +1,72 @@ +/* + include/proto/polling.h + Polling functions. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _PROTO_POLLING_H +#define _PROTO_POLLING_H + +#include + +/* + * Main select() loop. + * does 3 actions : + * 0 (POLL_LOOP_ACTION_INIT) : initializes necessary private structures + * 1 (POLL_LOOP_ACTION_RUN) : runs the loop + * 2 (POLL_LOOP_ACTION_CLEAN) : cleans up + * + * returns 0 if initialization failed, !0 otherwise. + */ +int select_loop(int action); + +#if defined(ENABLE_POLL) +/* + * Main poll() loop. + * does 3 actions : + * 0 (POLL_LOOP_ACTION_INIT) : initializes necessary private structures + * 1 (POLL_LOOP_ACTION_RUN) : runs the loop + * 2 (POLL_LOOP_ACTION_CLEAN) : cleans up + * + * returns 0 if initialization failed, !0 otherwise. + */ +int poll_loop(int action); +#endif + +#if defined(ENABLE_EPOLL) +/* + * Main epoll() loop. + * does 3 actions : + * 0 (POLL_LOOP_ACTION_INIT) : initializes necessary private structures + * 1 (POLL_LOOP_ACTION_RUN) : runs the loop + * 2 (POLL_LOOP_ACTION_CLEAN) : cleans up + * + * returns 0 if initialization failed, !0 otherwise. + */ +int epoll_loop(int action); +#endif + + +#endif /* _PROTO_POLLING_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/proto/proto_http.h b/include/proto/proto_http.h new file mode 100644 index 000000000..e9cd4f3ea --- /dev/null +++ b/include/proto/proto_http.h @@ -0,0 +1,49 @@ +/* + include/proto/proto_http.h + This file contains HTTP protocol definitions. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _PROTO_PROTO_HTTP_H +#define _PROTO_PROTO_HTTP_H + +#include +#include +#include + + +int event_accept(int fd); +int process_session(struct task *t); +int process_cli(struct session *t); +int process_srv(struct session *t); + +void client_retnclose(struct session *s, int len, const char *msg); +void client_return(struct session *s, int len, const char *msg); +void srv_close_with_err(struct session *t, int err, int finst, + int status, int msglen, char *msg); + +int produce_content(struct session *s); + +#endif /* _PROTO_PROTO_HTTP_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/proto/proxy.h b/include/proto/proxy.h new file mode 100644 index 000000000..4a3d67c7d --- /dev/null +++ b/include/proto/proxy.h @@ -0,0 +1,43 @@ +/* + include/proto/proxy.h + This file defines function prototypes for proxy management. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _PROTO_PROXY_H +#define _PROTO_PROXY_H + + +#include + +int start_proxies(int verbose); +int maintain_proxies(void); +void soft_stop(void); +void pause_proxy(struct proxy *p); +void pause_proxies(void); +void listen_proxies(void); + + +#endif /* _PROTO_PROXY_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/proto/queue.h b/include/proto/queue.h new file mode 100644 index 000000000..9f135905c --- /dev/null +++ b/include/proto/queue.h @@ -0,0 +1,78 @@ +/* + include/proto/queue.h + This file defines everything related to queues. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _PROTO_QUEUE_H +#define _PROTO_QUEUE_H + +#include +#include + +#include +#include +#include +#include +#include + +struct session *pendconn_get_next_sess(struct server *srv, struct proxy *px); +struct pendconn *pendconn_add(struct session *sess); +void pendconn_free(struct pendconn *p); +int process_srv_queue(struct task *t); +unsigned int srv_dynamic_maxconn(struct server *s); + + + +/* Returns the first pending connection for server , which may be NULL if + * nothing is pending. + */ +static inline struct pendconn *pendconn_from_srv(struct server *s) { + if (!s->nbpend) + return NULL; + + return LIST_ELEM(s->pendconns.n, struct pendconn *, list); +} + +/* Returns the first pending connection for proxy , which may be NULL if + * nothing is pending. + */ +static inline struct pendconn *pendconn_from_px(struct proxy *px) { + if (!px->nbpend) + return NULL; + + return LIST_ELEM(px->pendconns.n, struct pendconn *, list); +} + +/* returns 0 if nothing has to be done for server regarding queued connections, + * and non-zero otherwise. Suited for and if/else usage. + */ +static inline int may_dequeue_tasks(struct server *s, struct proxy *p) { + return (s && (s->nbpend || p->nbpend) && + (!s->maxconn || s->cur_sess < srv_dynamic_maxconn(s)) && + s->queue_mgt); +} + +#endif /* _PROTO_QUEUE_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/proto/server.h b/include/proto/server.h new file mode 100644 index 000000000..b79b9702b --- /dev/null +++ b/include/proto/server.h @@ -0,0 +1,42 @@ +/* + include/proto/server.h + This file defines everything related to servers. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _PROTO_SERVER_H +#define _PROTO_SERVER_H + +#include + +#include +#include +#include + +#include + + + +#endif /* _PROTO_SERVER_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/proto/session.h b/include/proto/session.h new file mode 100644 index 000000000..6c84f4c23 --- /dev/null +++ b/include/proto/session.h @@ -0,0 +1,38 @@ +/* + include/proto/session.h + This file defines everything related to sessions. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _PROTO_SESSION_H +#define _PROTO_SESSION_H + + +#include + +void session_free(struct session *s); + + +#endif /* _PROTO_SESSION_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/proto/stream_sock.h b/include/proto/stream_sock.h new file mode 100644 index 000000000..bab3c53fd --- /dev/null +++ b/include/proto/stream_sock.h @@ -0,0 +1,63 @@ +/* + include/proto/stream_sock.h + This file contains client-side definitions. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _PROTO_STREAM_SOCK_H +#define _PROTO_STREAM_SOCK_H + +#include +#include +#include + +#include +#include + + +/* FIXME: merge those ones together */ +int event_cli_read(int fd); +int event_cli_write(int fd); +int event_srv_read(int fd); +int event_srv_write(int fd); + + +/* This either returns the sockname or the original destination address. Code + * inspired from Patrick Schaaf's example of nf_getsockname() implementation. + */ +static inline int get_original_dst(int fd, struct sockaddr_in *sa, socklen_t *salen) { +#if defined(TPROXY) && defined(SO_ORIGINAL_DST) + return getsockopt(fd, SOL_IP, SO_ORIGINAL_DST, (void *)sa, salen); +#else +#if defined(TPROXY) && defined(USE_GETSOCKNAME) + return getsockname(fd, (struct sockaddr *)sa, salen); +#else + return -1; +#endif +#endif +} + + +#endif /* _PROTO_STREAM_SOCK_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/proto/task.h b/include/proto/task.h new file mode 100644 index 000000000..8aba7950a --- /dev/null +++ b/include/proto/task.h @@ -0,0 +1,101 @@ +/* + include/proto/task.h + Functions for task management. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _PROTO_TASK_H +#define _PROTO_TASK_H + + +#include +#include +#include + + +/* puts the task in run queue , and returns */ +static inline struct task *task_wakeup(struct task **q, struct task *t) +{ + if (t->state == TASK_RUNNING) + return t; + else { + t->rqnext = *q; + t->state = TASK_RUNNING; + return *q = t; + } +} + +/* removes the task from the queue + * MUST be 's first task. + * set the run queue to point to the next one, and return it + */ +static inline struct task *task_sleep(struct task **q, struct task *t) +{ + if (t->state == TASK_RUNNING) { + *q = t->rqnext; + t->state = TASK_IDLE; /* tell that s has left the run queue */ + } + return *q; /* return next running task */ +} + +/* + * removes the task from its wait queue. It must have already been removed + * from the run queue. A pointer to the task itself is returned. + */ +static inline struct task *task_delete(struct task *t) +{ + t->prev->next = t->next; + t->next->prev = t->prev; + return t; +} + +/* + * frees a task. Its context must have been freed since it will be lost. + */ +static inline void task_free(struct task *t) +{ + pool_free(task, t); +} + +/* inserts into its assigned wait queue, where it may already be. In this case, it + * may be only moved or left where it was, depending on its timing requirements. + * is returned. + */ +struct task *task_queue(struct task *task); + +/* + * This does 4 things : + * - wake up all expired tasks + * - call all runnable tasks + * - call maintain_proxies() to enable/disable the listeners + * - return the delay till next event in ms, -1 = wait indefinitely + * Note: this part should be rewritten with the O(ln(n)) scheduler. + * + */ + +int process_runnable_tasks(); + + +#endif /* _PROTO_TASK_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/proto/template.h b/include/proto/template.h new file mode 100644 index 000000000..868634f78 --- /dev/null +++ b/include/proto/template.h @@ -0,0 +1,36 @@ +/* + include/proto/template.h + This file serves as a template for future include files. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _PROTO_TEMPLATE_H +#define _PROTO_TEMPLATE_H + + +#include + + +#endif /* _PROTO_TEMPLATE_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/types/backend.h b/include/types/backend.h new file mode 100644 index 000000000..7a6a640ad --- /dev/null +++ b/include/types/backend.h @@ -0,0 +1,60 @@ +/* + include/types/backend.h + This file rassembles definitions for backends + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _TYPES_BACKEND_H +#define _TYPES_BACKEND_H + +/* bits for proxy->options */ +#define PR_O_REDISP 0x00000001 /* allow reconnection to dispatch in case of errors */ +#define PR_O_TRANSP 0x00000002 /* transparent mode : use original DEST as dispatch */ +#define PR_O_COOK_RW 0x00000004 /* rewrite all direct cookies with the right serverid */ +#define PR_O_COOK_IND 0x00000008 /* keep only indirect cookies */ +#define PR_O_COOK_INS 0x00000010 /* insert cookies when not accessing a server directly */ +#define PR_O_COOK_PFX 0x00000020 /* rewrite all cookies by prefixing the right serverid */ +#define PR_O_COOK_ANY (PR_O_COOK_RW | PR_O_COOK_IND | PR_O_COOK_INS | PR_O_COOK_PFX) +#define PR_O_BALANCE_RR 0x00000040 /* balance in round-robin mode */ +#define PR_O_KEEPALIVE 0x00000080 /* follow keep-alive sessions */ +#define PR_O_FWDFOR 0x00000100 /* insert x-forwarded-for with client address */ +#define PR_O_BIND_SRC 0x00000200 /* bind to a specific source address when connect()ing */ +#define PR_O_NULLNOLOG 0x00000400 /* a connect without request will not be logged */ +#define PR_O_COOK_NOC 0x00000800 /* add a 'Cache-control' header with the cookie */ +#define PR_O_COOK_POST 0x00001000 /* don't insert cookies for requests other than a POST */ +#define PR_O_HTTP_CHK 0x00002000 /* use HTTP 'OPTIONS' method to check server health */ +#define PR_O_PERSIST 0x00004000 /* server persistence stays effective even when server is down */ +#define PR_O_LOGASAP 0x00008000 /* log as soon as possible, without waiting for the session to complete */ +#define PR_O_HTTP_CLOSE 0x00010000 /* force 'connection: close' in both directions */ +#define PR_O_CHK_CACHE 0x00020000 /* require examination of cacheability of the 'set-cookie' field */ +#define PR_O_TCP_CLI_KA 0x00040000 /* enable TCP keep-alive on client-side sessions */ +#define PR_O_TCP_SRV_KA 0x00080000 /* enable TCP keep-alive on server-side sessions */ +#define PR_O_USE_ALL_BK 0x00100000 /* load-balance between backup servers */ +#define PR_O_FORCE_CLO 0x00200000 /* enforce the connection close immediately after server response */ +#define PR_O_BALANCE_SH 0x00400000 /* balance on source IP hash */ +#define PR_O_BALANCE (PR_O_BALANCE_RR | PR_O_BALANCE_SH) +#define PR_O_ABRT_CLOSE 0x00800000 /* immediately abort request when client closes */ + +#endif /* _TYPES_BACKEND_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/types/buffers.h b/include/types/buffers.h new file mode 100644 index 000000000..2fcf59fa5 --- /dev/null +++ b/include/types/buffers.h @@ -0,0 +1,53 @@ +/* + include/types/buffers.h + Buffer management definitions, macros and inline functions. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _TYPES_BUFFERS_H +#define _TYPES_BUFFERS_H + +#include +#include + +/* describes a chunk of string */ +struct chunk { + char *str; /* beginning of the string itself. Might not be 0-terminated */ + int len; /* size of the string from first to last char. <0 = uninit. */ +}; + +struct buffer { + unsigned int l; /* data length */ + char *r, *w, *h, *lr; /* read ptr, write ptr, last header ptr, last read */ + char *rlim; /* read limit, used for header rewriting */ + unsigned long long total; /* total data read */ + char data[BUFSIZE]; +}; + +#define sizeof_buffer sizeof(struct buffer) +extern void **pool_buffer; + + +#endif /* _TYPES_BUFFERS_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/types/capture.h b/include/types/capture.h new file mode 100644 index 000000000..e531b546d --- /dev/null +++ b/include/types/capture.h @@ -0,0 +1,43 @@ +/* + include/types/capture.h + This file defines everything related to captures. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _TYPES_CAPTURE_H +#define _TYPES_CAPTURE_H + +struct cap_hdr { + struct cap_hdr *next; + char *name; /* header name, case insensitive */ + int namelen; /* length of the header name, to speed-up lookups */ + int len; /* capture length, not including terminal zero */ + int index; /* index in the output array */ + void *pool; /* pool of pre-allocated memory area of (len+1) bytes */ +}; + +extern void **pool_capture; + +#endif /* _TYPES_CAPTURE_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/types/client.h b/include/types/client.h new file mode 100644 index 000000000..d19e10065 --- /dev/null +++ b/include/types/client.h @@ -0,0 +1,45 @@ +/* + include/types/client.h + This file contains client-side definitions. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _TYPES_CLIENT_H +#define _TYPES_CLIENT_H + +/* + * FIXME: break this into HTTP state and TCP socket state. + * See server.h for the other end. + */ + +/* different possible states for the client side */ +#define CL_STHEADERS 0 +#define CL_STDATA 1 +#define CL_STSHUTR 2 +#define CL_STSHUTW 3 +#define CL_STCLOSE 4 + + +#endif /* _TYPES_CLIENT_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/types/fd.h b/include/types/fd.h new file mode 100644 index 000000000..6e350e56e --- /dev/null +++ b/include/types/fd.h @@ -0,0 +1,60 @@ +/* + include/fd.h + File descriptors states. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _TYPES_FD_H +#define _TYPES_FD_H + +#include +#include +#include + +#include +#include + +/* different possible states for the fd */ +#define FD_STCLOSE 0 +#define FD_STLISTEN 1 +#define FD_STCONN 2 +#define FD_STREADY 3 +#define FD_STERROR 4 + + +/* info about one given fd */ +struct fdtab { + int (*read)(int fd); /* read function */ + int (*write)(int fd); /* write function */ + struct task *owner; /* the session (or proxy) associated with this fd */ + int state; /* the state of this fd */ +}; + +extern struct fdtab *fdtab; /* array of all the file descriptors */ +extern int maxfd; /* # of the highest fd + 1 */ +extern int totalconn; /* total # of terminated sessions */ +extern int actconn; /* # of active sessions */ + +#endif /* _TYPES_FD_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/types/global.h b/include/types/global.h new file mode 100644 index 000000000..4a2e895e8 --- /dev/null +++ b/include/types/global.h @@ -0,0 +1,75 @@ +/* + include/types/global.h + Global variables. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _TYPES_GLOBAL_H +#define _TYPES_GLOBAL_H + +#include + +#include + +/* modes of operation (global.mode) */ +#define MODE_DEBUG 1 +#define MODE_STATS 2 +#define MODE_LOG 4 +#define MODE_DAEMON 8 +#define MODE_QUIET 16 +#define MODE_CHECK 32 +#define MODE_VERBOSE 64 +#define MODE_STARTING 128 +#define MODE_FOREGROUND 256 + + +/* FIXME : this will have to be redefined correctly */ +struct global { + int uid; + int gid; + int nbproc; + int maxconn; + int maxsock; /* max # of sockets */ + int rlimit_nofile; /* default ulimit-n value : 0=unset */ + int rlimit_memmax; /* default ulimit-d in megs value : 0=unset */ + int mode; + char *chroot; + char *pidfile; + int logfac1, logfac2; + int loglev1, loglev2; + struct sockaddr_in logsrv1, logsrv2; +}; + +extern struct global global; +extern char *progname; /* program name */ +extern int pid; /* current process id */ +extern int actconn; /* # of active sessions */ +extern int listeners; +extern char trash[BUFSIZE]; +extern const int zero; +extern const int one; +extern int stopping; /* non zero means stopping in progress */ + +#endif /* _TYPES_GLOBAL_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/types/httperr.h b/include/types/httperr.h new file mode 100644 index 000000000..3a40a239f --- /dev/null +++ b/include/types/httperr.h @@ -0,0 +1,42 @@ +/* + include/types/httperr.h + This file defines everything related to HTTP responses and errors. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _TYPES_HTTPERR_H +#define _TYPES_HTTPERR_H + +/* various data sources for the responses */ +#define DATA_SRC_NONE 0 +#define DATA_SRC_STATS 1 + +/* data transmission states for the responses */ +#define DATA_ST_INIT 0 +#define DATA_ST_DATA 1 + + + +#endif /* _TYPES_HTTPERR_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/types/log.h b/include/types/log.h new file mode 100644 index 000000000..9aeb11e7f --- /dev/null +++ b/include/types/log.h @@ -0,0 +1,56 @@ +/* + include/types/log.h + This file contains definitions of log-related structures and macros. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _TYPES_LOG_H +#define _TYPES_LOG_H + + +#define MAX_SYSLOG_LEN 1024 +#define NB_LOG_FACILITIES 24 +#define NB_LOG_LEVELS 8 +#define SYSLOG_PORT 514 + + +/* fields that need to be logged. They appear as flags in session->logs.logwait */ +#define LW_DATE 1 /* date */ +#define LW_CLIP 2 /* CLient IP */ +#define LW_SVIP 4 /* SerVer IP */ +#define LW_SVID 8 /* server ID */ +#define LW_REQ 16 /* http REQuest */ +#define LW_RESP 32 /* http RESPonse */ +#define LW_PXIP 64 /* proxy IP */ +#define LW_PXID 128 /* proxy ID */ +#define LW_BYTES 256 /* bytes read from server */ +#define LW_COOKIE 512 /* captured cookie */ +#define LW_REQHDR 1024 /* request header(s) */ +#define LW_RSPHDR 2048 /* response header(s) */ + +extern void **pool_requri; + + +#endif /* _TYPES_LOG_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/types/polling.h b/include/types/polling.h new file mode 100644 index 000000000..3c2a231a5 --- /dev/null +++ b/include/types/polling.h @@ -0,0 +1,74 @@ +/* + include/types/polling.h + File descriptors and polling definitions. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _TYPES_POLLING_H +#define _TYPES_POLLING_H + +/* for fd_set */ +#include +#include +#include + +#include + +/* for POLL_* */ +#if defined(ENABLE_POLL) +#include +#endif + +/* for EPOLL_* */ +#if defined(ENABLE_EPOLL) +#if !defined(USE_MY_EPOLL) +#include +#else +#include +#endif +#endif + +/* possible actions for the *poll() loops */ +#define POLL_LOOP_ACTION_INIT 0 +#define POLL_LOOP_ACTION_RUN 1 +#define POLL_LOOP_ACTION_CLEAN 2 + +/* poll mechanisms available */ +#define POLL_USE_SELECT (1<<0) +#define POLL_USE_POLL (1<<1) +#define POLL_USE_EPOLL (1<<2) + +/* result of an I/O event */ +#define RES_SILENT 0 /* didn't happen */ +#define RES_DATA 1 /* data were sent or received */ +#define RES_NULL 2 /* result is 0 (read == 0), or connect without need for writing */ +#define RES_ERROR 3 /* result -1 or error on the socket (eg: connect()) */ + +/* fd states */ +extern fd_set *StaticReadEvent, *StaticWriteEvent; +extern int cfg_polling_mechanism; /* POLL_USE_{SELECT|POLL|EPOLL} */ + + +#endif /* _TYPES_POLLING_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/types/proto_http.h b/include/types/proto_http.h new file mode 100644 index 000000000..2ccee0651 --- /dev/null +++ b/include/types/proto_http.h @@ -0,0 +1,60 @@ +/* + include/types/proto_http.h + This file contains HTTP protocol definitions. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _TYPES_PROTO_HTTP_H +#define _TYPES_PROTO_HTTP_H + + +/* + * FIXME: break this into HTTP state and TCP socket state. + * See server.h for the other end. + */ + +/* different possible states for the client side */ +#define CL_STHEADERS 0 +#define CL_STDATA 1 +#define CL_STSHUTR 2 +#define CL_STSHUTW 3 +#define CL_STCLOSE 4 + +/* + * FIXME: break this into HTTP state and TCP socket state. + * See client.h for the other end. + */ + +/* different possible states for the server side */ +#define SV_STIDLE 0 +#define SV_STCONN 1 +#define SV_STHEADERS 2 +#define SV_STDATA 3 +#define SV_STSHUTR 4 +#define SV_STSHUTW 5 +#define SV_STCLOSE 6 + + +#endif /* _TYPES_PROTO_HTTP_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/types/proxy.h b/include/types/proxy.h new file mode 100644 index 000000000..96858280b --- /dev/null +++ b/include/types/proxy.h @@ -0,0 +1,145 @@ +/* + include/types/proxy.h + This file defines everything related to proxies. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _TYPES_PROXY_H +#define _TYPES_PROXY_H + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +/* values for proxy->state */ +#define PR_STNEW 0 +#define PR_STIDLE 1 +#define PR_STRUN 2 +#define PR_STSTOPPED 3 +#define PR_STPAUSED 4 +#define PR_STERROR 5 + +/* values for proxy->mode */ +#define PR_MODE_TCP 0 +#define PR_MODE_HTTP 1 +#define PR_MODE_HEALTH 2 + +/* return codes for start_proxies */ +#define ERR_NONE 0 /* no error */ +#define ERR_RETRYABLE 1 /* retryable error, may be cumulated */ +#define ERR_FATAL 2 /* fatal error, may be cumulated */ + + +struct listener { + int fd; /* the listen socket */ + struct sockaddr_storage addr; /* the address we listen to */ + struct listener *next; /* next address or NULL */ +}; + +struct proxy { + struct listener *listen; /* the listen addresses and sockets */ + struct in_addr mon_net, mon_mask; /* don't forward connections from this net (network order) FIXME: should support IPv6 */ + int state; /* proxy state */ + struct sockaddr_in dispatch_addr; /* the default address to connect to */ + struct server *srv; /* known servers */ + int srv_act, srv_bck; /* # of running servers */ + int tot_wact, tot_wbck; /* total weights of active and backup servers */ + struct server **srv_map; /* the server map used to apply weights */ + int srv_map_sz; /* the size of the effective server map */ + int srv_rr_idx; /* next server to be elected in round robin mode */ + char *cookie_name; /* name of the cookie to look for */ + int cookie_len; /* strlen(cookie_name), computed only once */ + char *appsession_name; /* name of the cookie to look for */ + int appsession_name_len; /* strlen(appsession_name), computed only once */ + int appsession_len; /* length of the appsession cookie value to be used */ + int appsession_timeout; + CHTbl htbl_proxy; /* Per Proxy hashtable */ + char *capture_name; /* beginning of the name of the cookie to capture */ + int capture_namelen; /* length of the cookie name to match */ + int capture_len; /* length of the string to be captured */ + struct uri_auth *uri_auth; /* if non-NULL, the (list of) per-URI authentications */ + int clitimeout; /* client I/O timeout (in milliseconds) */ + int srvtimeout; /* server I/O timeout (in milliseconds) */ + int contimeout; /* connect timeout (in milliseconds) */ + char *id; /* proxy id */ + struct list pendconns; /* pending connections with no server assigned yet */ + int nbpend, nbpend_max; /* number of pending connections with no server assigned yet */ + int totpend; /* total number of pending connections on this instance (for stats) */ + unsigned int nbconn, nbconn_max; /* # of active sessions */ + unsigned int cum_conn; /* cumulated number of processed sessions */ + unsigned int maxconn; /* max # of active sessions */ + unsigned failed_conns, failed_resp; /* failed connect() and responses */ + unsigned failed_secu; /* blocked responses because of security concerns */ + int conn_retries; /* maximum number of connect retries */ + int options; /* PR_O_REDISP, PR_O_TRANSP, ... */ + int mode; /* mode = PR_MODE_TCP, PR_MODE_HTTP or PR_MODE_HEALTH */ + struct sockaddr_in source_addr; /* the address to which we want to bind for connect() */ + struct proxy *next; + struct sockaddr_in logsrv1, logsrv2; /* 2 syslog servers */ + signed char logfac1, logfac2; /* log facility for both servers. -1 = disabled */ + int loglev1, loglev2; /* log level for each server, 7 by default */ + int to_log; /* things to be logged (LW_*) */ + struct timeval stop_time; /* date to stop listening, when stopping != 0 */ + int nb_reqadd, nb_rspadd; + struct hdr_exp *req_exp; /* regular expressions for request headers */ + struct hdr_exp *rsp_exp; /* regular expressions for response headers */ + int nb_req_cap, nb_rsp_cap; /* # of headers to be captured */ + struct cap_hdr *req_cap; /* chained list of request headers to be captured */ + struct cap_hdr *rsp_cap; /* chained list of response headers to be captured */ + void *req_cap_pool, *rsp_cap_pool; /* pools of pre-allocated char ** used to build the sessions */ + char *req_add[MAX_NEWHDR], *rsp_add[MAX_NEWHDR]; /* headers to be added */ + int grace; /* grace time after stop request */ + char *check_req; /* HTTP request to use if PR_O_HTTP_CHK is set, else NULL */ + int check_len; /* Length of the HTTP request */ + struct { + char *msg400; /* message for error 400 */ + int len400; /* message length for error 400 */ + char *msg403; /* message for error 403 */ + int len403; /* message length for error 403 */ + char *msg408; /* message for error 408 */ + int len408; /* message length for error 408 */ + char *msg500; /* message for error 500 */ + int len500; /* message length for error 500 */ + char *msg502; /* message for error 502 */ + int len502; /* message length for error 502 */ + char *msg503; /* message for error 503 */ + int len503; /* message length for error 503 */ + char *msg504; /* message for error 504 */ + int len504; /* message length for error 504 */ + } errmsg; +}; + +extern struct proxy *proxy; + +#endif /* _TYPES_PROXY_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/types/queue.h b/include/types/queue.h new file mode 100644 index 000000000..d35fae9d4 --- /dev/null +++ b/include/types/queue.h @@ -0,0 +1,47 @@ +/* + include/types/queue.h + This file defines variables and structures needed for queues. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _TYPES_QUEUE_H +#define _TYPES_QUEUE_H + +#include + +#include +#include + +struct pendconn { + struct list list; /* chaining ... */ + struct session *sess; /* the session waiting for a connection */ + struct server *srv; /* the server we are waiting for */ +}; + +#define sizeof_pendconn sizeof(struct pendconn) +extern void **pool_pendconn; + + +#endif /* _TYPES_QUEUE_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/types/server.h b/include/types/server.h new file mode 100644 index 000000000..56a6b63f0 --- /dev/null +++ b/include/types/server.h @@ -0,0 +1,87 @@ +/* + include/types/server.h + This file defines everything related to servers. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _TYPES_SERVER_H +#define _TYPES_SERVER_H + +#include +#include + +#include + +#include +#include +#include +#include + + +/* server flags */ +#define SRV_RUNNING 1 /* the server is UP */ +#define SRV_BACKUP 2 /* this server is a backup server */ +#define SRV_MAPPORTS 4 /* this server uses mapped ports */ +#define SRV_BIND_SRC 8 /* this server uses a specific source address */ +#define SRV_CHECKED 16 /* this server needs to be checked */ + +/* function which act on servers need to return various errors */ +#define SRV_STATUS_OK 0 /* everything is OK. */ +#define SRV_STATUS_INTERNAL 1 /* other unrecoverable errors. */ +#define SRV_STATUS_NOSRV 2 /* no server is available */ +#define SRV_STATUS_FULL 3 /* the/all server(s) are saturated */ +#define SRV_STATUS_QUEUED 4 /* the/all server(s) are saturated but the connection was queued */ + + +struct server { + struct server *next; + int state; /* server state (SRV_*) */ + int cklen; /* the len of the cookie, to speed up checks */ + char *cookie; /* the id set in the cookie */ + char *id; /* just for identification */ + struct list pendconns; /* pending connections */ + int nbpend, nbpend_max; /* number of pending connections */ + struct task *queue_mgt; /* the task associated to the queue processing */ + struct sockaddr_in addr; /* the address to connect to */ + struct sockaddr_in source_addr; /* the address to which we want to bind for connect() */ + short check_port; /* the port to use for the health checks */ + int health; /* 0->rise-1 = bad; rise->rise+fall-1 = good */ + int rise, fall; /* time in iterations */ + int inter; /* time in milliseconds */ + int result; /* 0 = connect OK, -1 = connect KO */ + int curfd; /* file desc used for current test, or -1 if not in test */ + unsigned char uweight, eweight; /* user-specified weight-1, and effective weight-1 */ + unsigned int wscore; /* weight score, used during srv map computation */ + int cur_sess, cur_sess_max; /* number of currently active sessions (including syn_sent) */ + unsigned int cum_sess; /* cumulated number of sessions really sent to this server */ + unsigned int maxconn, minconn; /* max # of active sessions (0 = unlimited), min# for dynamic limit. */ + unsigned failed_checks, down_trans; /* failed checks and up-down transitions */ + unsigned failed_conns, failed_resp; /* failed connect() and responses */ + unsigned failed_secu; /* blocked responses because of security concerns */ + struct proxy *proxy; /* the proxy this server belongs to */ +}; + + +#endif /* _TYPES_SERVER_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/types/session.h b/include/types/session.h new file mode 100644 index 000000000..e2ae54d7d --- /dev/null +++ b/include/types/session.h @@ -0,0 +1,166 @@ +/* + include/types/session.h + This file defines everything related to sessions. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _TYPES_SESSION_H +#define _TYPES_SESSION_H + + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + + +/* various session flags, bits values 0x01 to 0x20 (shift 0) */ +#define SN_DIRECT 0x00000001 /* connection made on the server matching the client cookie */ +#define SN_CLDENY 0x00000002 /* a client header matches a deny regex */ +#define SN_CLALLOW 0x00000004 /* a client header matches an allow regex */ +#define SN_SVDENY 0x00000008 /* a server header matches a deny regex */ +#define SN_SVALLOW 0x00000010 /* a server header matches an allow regex */ +#define SN_POST 0x00000020 /* the request was an HTTP POST */ + +/* session flags dedicated to cookies : bits values 0x40, 0x80 (0-3 shift 6) */ +#define SN_CK_NONE 0x00000000 /* this session had no cookie */ +#define SN_CK_INVALID 0x00000040 /* this session had a cookie which matches no server */ +#define SN_CK_DOWN 0x00000080 /* this session had cookie matching a down server */ +#define SN_CK_VALID 0x000000C0 /* this session had cookie matching a valid server */ +#define SN_CK_MASK 0x000000C0 /* mask to get this session's cookie flags */ +#define SN_CK_SHIFT 6 /* bit shift */ + +/* session termination conditions, bits values 0x100 to 0x700 (0-7 shift 8) */ +#define SN_ERR_NONE 0x00000000 +#define SN_ERR_CLITO 0x00000100 /* client time-out */ +#define SN_ERR_CLICL 0x00000200 /* client closed (read/write error) */ +#define SN_ERR_SRVTO 0x00000300 /* server time-out, connect time-out */ +#define SN_ERR_SRVCL 0x00000400 /* server closed (connect/read/write error) */ +#define SN_ERR_PRXCOND 0x00000500 /* the proxy decided to close (deny...) */ +#define SN_ERR_RESOURCE 0x00000600 /* the proxy encountered a lack of a local resources (fd, mem, ...) */ +#define SN_ERR_INTERNAL 0x00000700 /* the proxy encountered an internal error */ +#define SN_ERR_MASK 0x00000700 /* mask to get only session error flags */ +#define SN_ERR_SHIFT 8 /* bit shift */ + +/* session state at termination, bits values 0x1000 to 0x7000 (0-7 shift 12) */ +#define SN_FINST_R 0x00001000 /* session ended during client request */ +#define SN_FINST_C 0x00002000 /* session ended during server connect */ +#define SN_FINST_H 0x00003000 /* session ended during server headers */ +#define SN_FINST_D 0x00004000 /* session ended during data phase */ +#define SN_FINST_L 0x00005000 /* session ended while pushing last data to client */ +#define SN_FINST_Q 0x00006000 /* session ended while waiting in queue for a server slot */ +#define SN_FINST_MASK 0x00007000 /* mask to get only final session state flags */ +#define SN_FINST_SHIFT 12 /* bit shift */ + +/* cookie information, bits values 0x10000 to 0x80000 (0-8 shift 16) */ +#define SN_SCK_NONE 0x00000000 /* no set-cookie seen for the server cookie */ +#define SN_SCK_DELETED 0x00010000 /* existing set-cookie deleted or changed */ +#define SN_SCK_INSERTED 0x00020000 /* new set-cookie inserted or changed existing one */ +#define SN_SCK_SEEN 0x00040000 /* set-cookie seen for the server cookie */ +#define SN_SCK_MASK 0x00070000 /* mask to get the set-cookie field */ +#define SN_SCK_ANY 0x00080000 /* at least one set-cookie seen (not to be counted) */ +#define SN_SCK_SHIFT 16 /* bit shift */ + +/* cacheability management, bits values 0x100000 to 0x300000 (0-3 shift 20) */ +#define SN_CACHEABLE 0x00100000 /* at least part of the response is cacheable */ +#define SN_CACHE_COOK 0x00200000 /* a cookie in the response is cacheable */ +#define SN_CACHE_SHIFT 20 /* bit shift */ + +/* various other session flags, bits values 0x400000 and above */ +#define SN_MONITOR 0x00400000 /* this session comes from a monitoring system */ +#define SN_ASSIGNED 0x00800000 /* no need to assign a server to this session */ +#define SN_ADDR_SET 0x01000000 /* this session's server address has been set */ +#define SN_SELF_GEN 0x02000000 /* the proxy generates data for the client (eg: stats) */ + + +/* WARNING: if new fields are added, they must be initialized in event_accept() */ +struct session { + struct task *task; /* the task associated with this session */ + /* application specific below */ + struct timeval crexpire; /* expiration date for a client read */ + struct timeval cwexpire; /* expiration date for a client write */ + struct timeval srexpire; /* expiration date for a server read */ + struct timeval swexpire; /* expiration date for a server write */ + struct timeval cnexpire; /* expiration date for a connect */ + char res_cr, res_cw, res_sr, res_sw; /* results of some events */ + struct proxy *proxy; /* the proxy this socket belongs to */ + int cli_fd; /* the client side fd */ + int srv_fd; /* the server side fd */ + int cli_state; /* state of the client side */ + int srv_state; /* state of the server side */ + int conn_retries; /* number of connect retries left */ + int flags; /* some flags describing the session */ + struct buffer *req; /* request buffer */ + struct buffer *rep; /* response buffer */ + struct sockaddr_storage cli_addr; /* the client address */ + struct sockaddr_in srv_addr; /* the address to connect to */ + struct server *srv; /* the server being used */ + struct pendconn *pend_pos; /* if not NULL, points to the position in the pending queue */ + char **req_cap; /* array of captured request headers (may be NULL) */ + char **rsp_cap; /* array of captured response headers (may be NULL) */ + struct chunk req_line; /* points to first line */ + struct chunk auth_hdr; /* points to 'Authorization:' header */ + struct { + int logwait; /* log fields waiting to be collected : LW_* */ + struct timeval tv_accept; /* date of the accept() (beginning of the session) */ + long t_request; /* delay before the end of the request arrives, -1 if never occurs */ + long t_queue; /* delay before the session gets out of the connect queue, -1 if never occurs */ + long t_connect; /* delay before the connect() to the server succeeds, -1 if never occurs */ + long t_data; /* delay before the first data byte from the server ... */ + unsigned long t_close; /* total session duration */ + unsigned long srv_queue_size; /* number of sessions waiting for a connect slot on this server at accept() time (in direct assignment) */ + unsigned long prx_queue_size; /* overall number of sessions waiting for a connect slot on this instance at accept() time */ + char *uri; /* first line if log needed, NULL otherwise */ + char *cli_cookie; /* cookie presented by the client, in capture mode */ + char *srv_cookie; /* cookie presented by the server, in capture mode */ + int status; /* HTTP status from the server, negative if from proxy */ + long long bytes; /* number of bytes transferred from the server */ + } logs; + short int data_source; /* where to get the data we generate ourselves */ + short int data_state; /* where to get the data we generate ourselves */ + union { + struct { + struct proxy *px; + struct server *sv; + short px_st, sv_st; /* DATA_ST_INIT or DATA_ST_DATA */ + } stats; + } data_ctx; + unsigned int uniq_id; /* unique ID used for the traces */ +}; + + +#define sizeof_session sizeof(struct session) +extern void **pool_session; + + +#endif /* _TYPES_SESSION_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/types/task.h b/include/types/task.h new file mode 100644 index 000000000..560a2fc68 --- /dev/null +++ b/include/types/task.h @@ -0,0 +1,58 @@ +/* + include/types/task.h + Macros, variables and structures for task management. + + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + exclusively. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _TYPES_TASK_H +#define _TYPES_TASK_H + + +#include + + +/* values for task->state */ +#define TASK_IDLE 0 +#define TASK_RUNNING 1 + +/* The base for all tasks */ +struct task { + struct task *next, *prev; /* chaining ... */ + struct task *rqnext; /* chaining in run queue ... */ + struct task *wq; /* the wait queue this task is in */ + int state; /* task state : IDLE or RUNNING */ + struct timeval expire; /* next expiration time for this task, use only for fast sorting */ + int (*process)(struct task *t); /* the function which processes the task */ + void *context; /* the task's context */ +}; + +#define sizeof_task sizeof(struct task) +extern void **pool_task; + +extern struct task wait_queue[2]; +extern struct task *rq; + + +#endif /* _TYPES_TASK_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/template.h b/include/types/template.h similarity index 80% rename from include/template.h rename to include/types/template.h index 9c8620234..8fa1ce52e 100644 --- a/include/template.h +++ b/include/types/template.h @@ -1,6 +1,7 @@ /* - include/template.h + include/types/template.h This file serves as a template for future include files. + Copyright (C) 2000-2006 Willy Tarreau - w@1wt.eu This library is free software; you can redistribute it and/or @@ -18,8 +19,15 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef _TEMPLATE_H -#define _TEMPLATE_H +#ifndef _TYPES_TEMPLATE_H +#define _TYPES_TEMPLATE_H -#endif +#endif /* _TYPES_TEMPLATE_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/appsession.c b/src/appsession.c new file mode 100644 index 000000000..da7315aa7 --- /dev/null +++ b/src/appsession.c @@ -0,0 +1,222 @@ +/* + * AppSession functions. + * + * Copyright 2004-2006 Alexander Lazic, Klaus Wagner + * + * 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 +#include +#include +#include + +#include +#include +#include +#include + +#include + + +void **pool_appsess = NULL; +struct app_pool apools; +int have_appsession; + +#if defined(DEBUG_HASH) +void print_table(const CHTbl *htbl) +{ + ListElmt *element; + int i; + appsess *asession; + + /********************************************************************* + * * + * Display the chained hash table. * + * * + *********************************************************************/ + + fprintf(stdout, "Table size is %d\n", chtbl_size(htbl)); + + for (i = 0; i < TBLSIZ; i++) { + fprintf(stdout, "Bucket[%03d]\n", i); + + for (element = list_head(&htbl->table[i]); + element != NULL; element = list_next(element)) { + //fprintf(stdout, "%c", *(char *)list_data(element)); + asession = (appsess *)list_data(element); + fprintf(stdout, "ELEM :%s:", asession->sessid); + fprintf(stdout, " Server :%s: \n", asession->serverid); + //fprintf(stdout, " Server request_count :%li:\n",asession->request_count); + } + + fprintf(stdout, "\n"); + } + return; +} /* end print_table */ +#endif + +int appsession_init(void) +{ + static int initialized = 0; + int idlen; + struct server *s; + struct proxy *p = proxy; + + if (!initialized) { + if (!appsession_task_init()) { + apools.sessid = NULL; + apools.serverid = NULL; + apools.ser_waste = 0; + apools.ser_use = 0; + apools.ser_msize = sizeof(void *); + apools.ses_waste = 0; + apools.ses_use = 0; + apools.ses_msize = sizeof(void *); + while (p) { + s = p->srv; + if (apools.ses_msize < p->appsession_len) + apools.ses_msize = p->appsession_len; + while (s) { + idlen = strlen(s->id); + if (apools.ser_msize < idlen) + apools.ser_msize = idlen; + s = s->next; + } + p = p->next; + } + /* we use strings, so reserve space for '\0' */ + apools.ser_msize ++; + apools.ses_msize ++; + } + else { + fprintf(stderr, "appsession_task_init failed\n"); + return -1; + } + initialized ++; + } + return 0; +} + +int appsession_task_init(void) +{ + static int initialized = 0; + struct task *t; + if (!initialized) { + if ((t = pool_alloc(task)) == NULL) + return -1; + t->next = t->prev = t->rqnext = NULL; + t->wq = LIST_HEAD(wait_queue[0]); + t->state = TASK_IDLE; + t->context = NULL; + tv_delayfrom(&t->expire, &now, TBLCHKINT); + task_queue(t); + t->process = appsession_refresh; + initialized ++; + } + return 0; +} + +int appsession_refresh(struct task *t) +{ + struct proxy *p = proxy; + CHTbl *htbl; + ListElmt *element, *last; + int i; + appsess *asession; + void *data; + + while (p) { + if (p->appsession_name != NULL) { + htbl = &p->htbl_proxy; + /* if we ever give up the use of TBLSIZ, we need to change this */ + for (i = 0; i < TBLSIZ; i++) { + last = NULL; + for (element = list_head(&htbl->table[i]); + element != NULL; element = list_next(element)) { + asession = (appsess *)list_data(element); + if (tv_cmp2_ms(&asession->expire, &now) <= 0) { + if ((global.mode & MODE_DEBUG) && + (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) { + int len; + /* + on Linux NULL pointers are catched by sprintf, on solaris -> segfault + */ + len = sprintf(trash, "appsession_refresh: cleaning up expired Session '%s' on Server %s\n", + asession->sessid, asession->serverid?asession->serverid:"(null)"); + write(1, trash, len); + } + /* delete the expired element from within the hash table */ + if ((list_rem_next(&htbl->table[i], last, (void **)&data) == 0) + && (htbl->table[i].destroy != NULL)) { + htbl->table[i].destroy(data); + } + if (last == NULL) {/* patient lost his head, get a new one */ + element = list_head(&htbl->table[i]); + if (element == NULL) break; /* no heads left, go to next patient */ + } + else + element = last; + }/* end if (tv_cmp2_ms(&asession->expire, &now) <= 0) */ + else + last = element; + }/* end for (element = list_head(&htbl->table[i]); element != NULL; element = list_next(element)) */ + } + } + p = p->next; + } + tv_delayfrom(&t->expire, &now, TBLCHKINT); /* check expiration every 5 seconds */ + return TBLCHKINT; +} /* end appsession_refresh */ + +int match_str(const void *key1, const void *key2) +{ + appsess *temp1,*temp2; + temp1 = (appsess *)key1; + temp2 = (appsess *)key2; + + //fprintf(stdout,">>>>>>>>>>>>>>temp1->sessid :%s:\n",temp1->sessid); + //fprintf(stdout,">>>>>>>>>>>>>>temp2->sessid :%s:\n",temp2->sessid); + + return (strcmp(temp1->sessid,temp2->sessid) == 0); +}/* end match_str */ + +void destroy(void *data) { + appsess *temp1; + + //printf("destroy called\n"); + temp1 = (appsess *)data; + + if (temp1->sessid) + pool_free_to(apools.sessid, temp1->sessid); + + if (temp1->serverid) + pool_free_to(apools.serverid, temp1->serverid); + + pool_free(appsess, temp1); +} /* end destroy */ + +void appsession_cleanup( void ) +{ + struct proxy *p = proxy; + + while(p) { + chtbl_destroy(&(p->htbl_proxy)); + p = p->next; + } +}/* end appsession_cleanup() */ + + + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/backend.c b/src/backend.c new file mode 100644 index 000000000..ec997901f --- /dev/null +++ b/src/backend.c @@ -0,0 +1,618 @@ +/* + * Backend variables and functions. + * + * Copyright 2000-2006 Willy Tarreau + * + * 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 +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + + +/* + * This function recounts the number of usable active and backup servers for + * proxy

. These numbers are returned into the p->srv_act and p->srv_bck. + * This function also recomputes the total active and backup weights. + */ +void recount_servers(struct proxy *px) +{ + struct server *srv; + + px->srv_act = 0; px->srv_bck = px->tot_wact = px->tot_wbck = 0; + for (srv = px->srv; srv != NULL; srv = srv->next) { + if (srv->state & SRV_RUNNING) { + if (srv->state & SRV_BACKUP) { + px->srv_bck++; + px->tot_wbck += srv->eweight + 1; + } else { + px->srv_act++; + px->tot_wact += srv->eweight + 1; + } + } + } +} + +/* This function recomputes the server map for proxy px. It + * relies on px->tot_wact and px->tot_wbck, so it must be + * called after recount_servers(). It also expects px->srv_map + * to be initialized to the largest value needed. + */ +void recalc_server_map(struct proxy *px) +{ + int o, tot, flag; + struct server *cur, *best; + + if (px->srv_act) { + flag = SRV_RUNNING; + tot = px->tot_wact; + } else if (px->srv_bck) { + flag = SRV_RUNNING | SRV_BACKUP; + if (px->options & PR_O_USE_ALL_BK) + tot = px->tot_wbck; + else + tot = 1; /* the first server is enough */ + } else { + px->srv_map_sz = 0; + return; + } + + /* this algorithm gives priority to the first server, which means that + * it will respect the declaration order for equivalent weights, and + * that whatever the weights, the first server called will always be + * the first declard. This is an important asumption for the backup + * case, where we want the first server only. + */ + for (cur = px->srv; cur; cur = cur->next) + cur->wscore = 0; + + for (o = 0; o < tot; o++) { + int max = 0; + best = NULL; + for (cur = px->srv; cur; cur = cur->next) { + if ((cur->state & (SRV_RUNNING | SRV_BACKUP)) == flag) { + int v; + + /* If we are forced to return only one server, we don't want to + * go further, because we would return the wrong one due to + * divide overflow. + */ + if (tot == 1) { + best = cur; + break; + } + + cur->wscore += cur->eweight + 1; + v = (cur->wscore + tot) / tot; /* result between 0 and 3 */ + if (best == NULL || v > max) { + max = v; + best = cur; + } + } + } + px->srv_map[o] = best; + best->wscore -= tot; + } + px->srv_map_sz = tot; +} + + +/* + * This function marks the session as 'assigned' in direct or dispatch modes, + * or tries to assign one in balance mode, according to the algorithm. It does + * nothing if the session had already been assigned a server. + * + * It may return : + * SRV_STATUS_OK if everything is OK. s->srv will be valid. + * SRV_STATUS_NOSRV if no server is available. s->srv = NULL. + * SRV_STATUS_FULL if all servers are saturated. s->srv = NULL. + * SRV_STATUS_INTERNAL for other unrecoverable errors. + * + * Upon successful return, the session flag SN_ASSIGNED to indicate that it does + * not need to be called anymore. This usually means that s->srv can be trusted + * in balance and direct modes. This flag is not cleared, so it's to the caller + * to clear it if required (eg: redispatch). + * + */ + +int assign_server(struct session *s) +{ +#ifdef DEBUG_FULL + fprintf(stderr,"assign_server : s=%p\n",s); +#endif + + if (s->pend_pos) + return SRV_STATUS_INTERNAL; + + if (!(s->flags & SN_ASSIGNED)) { + if ((s->proxy->options & PR_O_BALANCE) && !(s->flags & SN_DIRECT)) { + if (!s->proxy->srv_act && !s->proxy->srv_bck) + return SRV_STATUS_NOSRV; + + if (s->proxy->options & PR_O_BALANCE_RR) { + s->srv = get_server_rr_with_conns(s->proxy); + if (!s->srv) + return SRV_STATUS_FULL; + } + else if (s->proxy->options & PR_O_BALANCE_SH) { + int len; + + if (s->cli_addr.ss_family == AF_INET) + len = 4; + else if (s->cli_addr.ss_family == AF_INET6) + len = 16; + else /* unknown IP family */ + return SRV_STATUS_INTERNAL; + + s->srv = get_server_sh(s->proxy, + (void *)&((struct sockaddr_in *)&s->cli_addr)->sin_addr, + len); + } + else /* unknown balancing algorithm */ + return SRV_STATUS_INTERNAL; + } + s->flags |= SN_ASSIGNED; + } + return SRV_STATUS_OK; +} + + +/* + * This function assigns a server address to a session, and sets SN_ADDR_SET. + * The address is taken from the currently assigned server, or from the + * dispatch or transparent address. + * + * It may return : + * SRV_STATUS_OK if everything is OK. + * SRV_STATUS_INTERNAL for other unrecoverable errors. + * + * Upon successful return, the session flag SN_ADDR_SET is set. This flag is + * not cleared, so it's to the caller to clear it if required. + * + */ +int assign_server_address(struct session *s) +{ +#ifdef DEBUG_FULL + fprintf(stderr,"assign_server_address : s=%p\n",s); +#endif + + if (s->flags & SN_DIRECT || s->proxy->options & PR_O_BALANCE) { + /* A server is necessarily known for this session */ + if (!(s->flags & SN_ASSIGNED)) + return SRV_STATUS_INTERNAL; + + s->srv_addr = s->srv->addr; + + /* if this server remaps proxied ports, we'll use + * the port the client connected to with an offset. */ + if (s->srv->state & SRV_MAPPORTS) { + struct sockaddr_in sockname; + socklen_t namelen = sizeof(sockname); + + if (!(s->proxy->options & PR_O_TRANSP) || + get_original_dst(s->cli_fd, (struct sockaddr_in *)&sockname, &namelen) == -1) + getsockname(s->cli_fd, (struct sockaddr *)&sockname, &namelen); + s->srv_addr.sin_port = htons(ntohs(s->srv_addr.sin_port) + ntohs(sockname.sin_port)); + } + } + else if (*(int *)&s->proxy->dispatch_addr.sin_addr) { + /* connect to the defined dispatch addr */ + s->srv_addr = s->proxy->dispatch_addr; + } + else if (s->proxy->options & PR_O_TRANSP) { + /* in transparent mode, use the original dest addr if no dispatch specified */ + socklen_t salen = sizeof(s->srv_addr); + + if (get_original_dst(s->cli_fd, &s->srv_addr, &salen) == -1) { + qfprintf(stderr, "Cannot get original server address.\n"); + return SRV_STATUS_INTERNAL; + } + } + + s->flags |= SN_ADDR_SET; + return SRV_STATUS_OK; +} + + +/* This function assigns a server to session if required, and can add the + * connection to either the assigned server's queue or to the proxy's queue. + * + * Returns : + * + * SRV_STATUS_OK if everything is OK. + * SRV_STATUS_NOSRV if no server is available. s->srv = NULL. + * SRV_STATUS_QUEUED if the connection has been queued. + * SRV_STATUS_FULL if the server(s) is/are saturated and the + * connection could not be queued. + * SRV_STATUS_INTERNAL for other unrecoverable errors. + * + */ +int assign_server_and_queue(struct session *s) +{ + struct pendconn *p; + int err; + + if (s->pend_pos) + return SRV_STATUS_INTERNAL; + + if (s->flags & SN_ASSIGNED) { + /* a server does not need to be assigned, perhaps because we're in + * direct mode, or in dispatch or transparent modes where the server + * is not needed. + */ + if (s->srv && + s->srv->maxconn && s->srv->cur_sess >= srv_dynamic_maxconn(s->srv)) { + p = pendconn_add(s); + if (p) + return SRV_STATUS_QUEUED; + else + return SRV_STATUS_FULL; + } + return SRV_STATUS_OK; + } + + /* a server needs to be assigned */ + err = assign_server(s); + switch (err) { + case SRV_STATUS_OK: + /* in balance mode, we might have servers with connection limits */ + if (s->srv && + s->srv->maxconn && s->srv->cur_sess >= srv_dynamic_maxconn(s->srv)) { + p = pendconn_add(s); + if (p) + return SRV_STATUS_QUEUED; + else + return SRV_STATUS_FULL; + } + return SRV_STATUS_OK; + + case SRV_STATUS_FULL: + /* queue this session into the proxy's queue */ + p = pendconn_add(s); + if (p) + return SRV_STATUS_QUEUED; + else + return SRV_STATUS_FULL; + + case SRV_STATUS_NOSRV: + case SRV_STATUS_INTERNAL: + return err; + default: + return SRV_STATUS_INTERNAL; + } +} + + +/* + * This function initiates a connection to the server assigned to this session + * (s->srv, s->srv_addr). It will assign a server if none is assigned yet. + * It can return one of : + * - SN_ERR_NONE if everything's OK + * - SN_ERR_SRVTO if there are no more servers + * - SN_ERR_SRVCL if the connection was refused by the server + * - SN_ERR_PRXCOND if the connection has been limited by the proxy (maxconn) + * - SN_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...) + * - SN_ERR_INTERNAL for any other purely internal errors + * Additionnally, in the case of SN_ERR_RESOURCE, an emergency log will be emitted. + */ +int connect_server(struct session *s) +{ + int fd, err; + + if (!(s->flags & SN_ADDR_SET)) { + err = assign_server_address(s); + if (err != SRV_STATUS_OK) + return SN_ERR_INTERNAL; + } + + if ((fd = s->srv_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { + qfprintf(stderr, "Cannot get a server socket.\n"); + + if (errno == ENFILE) + send_log(s->proxy, LOG_EMERG, + "Proxy %s reached system FD limit at %d. Please check system tunables.\n", + s->proxy->id, maxfd); + else if (errno == EMFILE) + send_log(s->proxy, LOG_EMERG, + "Proxy %s reached process FD limit at %d. Please check 'ulimit-n' and restart.\n", + s->proxy->id, maxfd); + else if (errno == ENOBUFS || errno == ENOMEM) + send_log(s->proxy, LOG_EMERG, + "Proxy %s reached system memory limit at %d sockets. Please check system tunables.\n", + s->proxy->id, maxfd); + /* this is a resource error */ + return SN_ERR_RESOURCE; + } + + if (fd >= global.maxsock) { + /* do not log anything there, it's a normal condition when this option + * is used to serialize connections to a server ! + */ + Alert("socket(): not enough free sockets. Raise -n argument. Giving up.\n"); + close(fd); + return SN_ERR_PRXCOND; /* it is a configuration limit */ + } + + if ((fcntl(fd, F_SETFL, O_NONBLOCK)==-1) || + (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *) &one, sizeof(one)) == -1)) { + qfprintf(stderr,"Cannot set client socket to non blocking mode.\n"); + close(fd); + return SN_ERR_INTERNAL; + } + + if (s->proxy->options & PR_O_TCP_SRV_KA) + setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char *) &one, sizeof(one)); + + /* allow specific binding : + * - server-specific at first + * - proxy-specific next + */ + if (s->srv != NULL && s->srv->state & SRV_BIND_SRC) { + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(one)); + if (bind(fd, (struct sockaddr *)&s->srv->source_addr, sizeof(s->srv->source_addr)) == -1) { + Alert("Cannot bind to source address before connect() for server %s/%s. Aborting.\n", + s->proxy->id, s->srv->id); + close(fd); + send_log(s->proxy, LOG_EMERG, + "Cannot bind to source address before connect() for server %s/%s.\n", + s->proxy->id, s->srv->id); + return SN_ERR_RESOURCE; + } + } + else if (s->proxy->options & PR_O_BIND_SRC) { + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(one)); + if (bind(fd, (struct sockaddr *)&s->proxy->source_addr, sizeof(s->proxy->source_addr)) == -1) { + Alert("Cannot bind to source address before connect() for proxy %s. Aborting.\n", s->proxy->id); + close(fd); + send_log(s->proxy, LOG_EMERG, + "Cannot bind to source address before connect() for server %s/%s.\n", + s->proxy->id, s->srv->id); + return SN_ERR_RESOURCE; + } + } + + if ((connect(fd, (struct sockaddr *)&s->srv_addr, sizeof(s->srv_addr)) == -1) && + (errno != EINPROGRESS) && (errno != EALREADY) && (errno != EISCONN)) { + + if (errno == EAGAIN || errno == EADDRINUSE) { + char *msg; + if (errno == EAGAIN) /* no free ports left, try again later */ + msg = "no free ports"; + else + msg = "local address already in use"; + + qfprintf(stderr,"Cannot connect: %s.\n",msg); + close(fd); + send_log(s->proxy, LOG_EMERG, + "Connect() failed for server %s/%s: %s.\n", + s->proxy->id, s->srv->id, msg); + return SN_ERR_RESOURCE; + } else if (errno == ETIMEDOUT) { + //qfprintf(stderr,"Connect(): ETIMEDOUT"); + close(fd); + return SN_ERR_SRVTO; + } else { + // (errno == ECONNREFUSED || errno == ENETUNREACH || errno == EACCES || errno == EPERM) + //qfprintf(stderr,"Connect(): %d", errno); + close(fd); + return SN_ERR_SRVCL; + } + } + + fdtab[fd].owner = s->task; + fdtab[fd].read = &event_srv_read; + fdtab[fd].write = &event_srv_write; + fdtab[fd].state = FD_STCONN; /* connection in progress */ + + FD_SET(fd, StaticWriteEvent); /* for connect status */ +#if defined(DEBUG_FULL) && defined(ENABLE_EPOLL) + if (PrevReadEvent) { + assert(!(FD_ISSET(fd, PrevReadEvent))); + assert(!(FD_ISSET(fd, PrevWriteEvent))); + } +#endif + + fd_insert(fd); + if (s->srv) { + s->srv->cur_sess++; + if (s->srv->cur_sess > s->srv->cur_sess_max) + s->srv->cur_sess_max = s->srv->cur_sess; + } + + if (s->proxy->contimeout) + tv_delayfrom(&s->cnexpire, &now, s->proxy->contimeout); + else + tv_eternity(&s->cnexpire); + return SN_ERR_NONE; /* connection is OK */ +} + + +/* + * This function checks the retry count during the connect() job. + * It updates the session's srv_state and retries, so that the caller knows + * what it has to do. It uses the last connection error to set the log when + * it expires. It returns 1 when it has expired, and 0 otherwise. + */ +int srv_count_retry_down(struct session *t, int conn_err) +{ + /* we are in front of a retryable error */ + t->conn_retries--; + if (t->conn_retries < 0) { + /* if not retryable anymore, let's abort */ + tv_eternity(&t->cnexpire); + srv_close_with_err(t, conn_err, SN_FINST_C, + 503, t->proxy->errmsg.len503, t->proxy->errmsg.msg503); + if (t->srv) + t->srv->failed_conns++; + t->proxy->failed_conns++; + + /* We used to have a free connection slot. Since we'll never use it, + * we have to inform the server that it may be used by another session. + */ + if (may_dequeue_tasks(t->srv, t->proxy)) + task_wakeup(&rq, t->srv->queue_mgt); + return 1; + } + return 0; +} + + +/* + * This function performs the retryable part of the connect() job. + * It updates the session's srv_state and retries, so that the caller knows + * what it has to do. It returns 1 when it breaks out of the loop, or 0 if + * it needs to redispatch. + */ +int srv_retryable_connect(struct session *t) +{ + int conn_err; + + /* This loop ensures that we stop before the last retry in case of a + * redispatchable server. + */ + do { + /* initiate a connection to the server */ + conn_err = connect_server(t); + switch (conn_err) { + + case SN_ERR_NONE: + //fprintf(stderr,"0: c=%d, s=%d\n", c, s); + t->srv_state = SV_STCONN; + return 1; + + case SN_ERR_INTERNAL: + tv_eternity(&t->cnexpire); + srv_close_with_err(t, SN_ERR_INTERNAL, SN_FINST_C, + 500, t->proxy->errmsg.len500, t->proxy->errmsg.msg500); + if (t->srv) + t->srv->failed_conns++; + t->proxy->failed_conns++; + /* release other sessions waiting for this server */ + if (may_dequeue_tasks(t->srv, t->proxy)) + task_wakeup(&rq, t->srv->queue_mgt); + return 1; + } + /* ensure that we have enough retries left */ + if (srv_count_retry_down(t, conn_err)) { + /* let's try to offer this slot to anybody */ + if (may_dequeue_tasks(t->srv, t->proxy)) + task_wakeup(&rq, t->srv->queue_mgt); + return 1; + } + } while (t->srv == NULL || t->conn_retries > 0 || !(t->proxy->options & PR_O_REDISP)); + + /* We're on our last chance, and the REDISP option was specified. + * We will ignore cookie and force to balance or use the dispatcher. + */ + /* let's try to offer this slot to anybody */ + if (may_dequeue_tasks(t->srv, t->proxy)) + task_wakeup(&rq, t->srv->queue_mgt); + + if (t->srv) + t->srv->failed_conns++; + t->proxy->failed_conns++; + + t->flags &= ~(SN_DIRECT | SN_ASSIGNED | SN_ADDR_SET); + t->srv = NULL; /* it's left to the dispatcher to choose a server */ + if ((t->flags & SN_CK_MASK) == SN_CK_VALID) { + t->flags &= ~SN_CK_MASK; + t->flags |= SN_CK_DOWN; + } + return 0; +} + + +/* This function performs the "redispatch" part of a connection attempt. It + * will assign a server if required, queue the connection if required, and + * handle errors that might arise at this level. It can change the server + * state. It will return 1 if it encounters an error, switches the server + * state, or has to queue a connection. Otherwise, it will return 0 indicating + * that the connection is ready to use. + */ + +int srv_redispatch_connect(struct session *t) +{ + int conn_err; + + /* We know that we don't have any connection pending, so we will + * try to get a new one, and wait in this state if it's queued + */ + conn_err = assign_server_and_queue(t); + switch (conn_err) { + case SRV_STATUS_OK: + break; + + case SRV_STATUS_NOSRV: + /* note: it is guaranteed that t->srv == NULL here */ + tv_eternity(&t->cnexpire); + srv_close_with_err(t, SN_ERR_SRVTO, SN_FINST_C, + 503, t->proxy->errmsg.len503, t->proxy->errmsg.msg503); + if (t->srv) + t->srv->failed_conns++; + t->proxy->failed_conns++; + + return 1; + + case SRV_STATUS_QUEUED: + /* FIXME-20060503 : we should use the queue timeout instead */ + if (t->proxy->contimeout) + tv_delayfrom(&t->cnexpire, &now, t->proxy->contimeout); + else + tv_eternity(&t->cnexpire); + t->srv_state = SV_STIDLE; + /* do nothing else and do not wake any other session up */ + return 1; + + case SRV_STATUS_FULL: + case SRV_STATUS_INTERNAL: + default: + tv_eternity(&t->cnexpire); + srv_close_with_err(t, SN_ERR_INTERNAL, SN_FINST_C, + 500, t->proxy->errmsg.len500, t->proxy->errmsg.msg500); + if (t->srv) + t->srv->failed_conns++; + t->proxy->failed_conns++; + + /* release other sessions waiting for this server */ + if (may_dequeue_tasks(t->srv, t->proxy)) + task_wakeup(&rq, t->srv->queue_mgt); + return 1; + } + /* if we get here, it's because we got SRV_STATUS_OK, which also + * means that the connection has not been queued. + */ + return 0; +} + + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/base64.c b/src/base64.c index 79e86c200..a427d5d83 100644 --- a/src/base64.c +++ b/src/base64.c @@ -1,6 +1,7 @@ /* * Ascii to Base64 conversion as described in RFC1421. - * Copyright 2006 Willy Tarreau + * + * Copyright 2006 Willy Tarreau * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -9,8 +10,9 @@ * */ -#include +#include +const char base64tab[64]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; /* Encodes bytes from to for at most chars (including * the trailing zero). Returns the number of bytes written. No check is made @@ -19,7 +21,6 @@ */ int a2base64(char *in, int ilen, char *out, int olen) { - char base64[64]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; int convlen; convlen = ((ilen + 2) / 3) * 4; @@ -29,10 +30,10 @@ int a2base64(char *in, int ilen, char *out, int olen) /* we don't need to check olen anymore */ while (ilen >= 3) { - out[0] = base64[(((unsigned char)in[0]) >> 2)]; - out[1] = base64[(((unsigned char)in[0] & 0x03) << 4) | (((unsigned char)in[1]) >> 4)]; - out[2] = base64[(((unsigned char)in[1] & 0x0F) << 2) | (((unsigned char)in[2]) >> 6)]; - out[3] = base64[(((unsigned char)in[2] & 0x3F))]; + out[0] = base64tab[(((unsigned char)in[0]) >> 2)]; + out[1] = base64tab[(((unsigned char)in[0] & 0x03) << 4) | (((unsigned char)in[1]) >> 4)]; + out[2] = base64tab[(((unsigned char)in[1] & 0x0F) << 2) | (((unsigned char)in[2]) >> 6)]; + out[3] = base64tab[(((unsigned char)in[2] & 0x3F))]; out += 4; in += 3; ilen -= 3; } @@ -40,14 +41,14 @@ int a2base64(char *in, int ilen, char *out, int olen) if (!ilen) { out[0] = '\0'; } else { - out[0] = base64[((unsigned char)in[0]) >> 2]; + out[0] = base64tab[((unsigned char)in[0]) >> 2]; if (ilen == 1) { - out[1] = base64[((unsigned char)in[0] & 0x03) << 4]; + out[1] = base64tab[((unsigned char)in[0] & 0x03) << 4]; out[2] = '='; } else { - out[1] = base64[(((unsigned char)in[0] & 0x03) << 4) | + out[1] = base64tab[(((unsigned char)in[0] & 0x03) << 4) | (((unsigned char)in[1]) >> 4)]; - out[2] = base64[((unsigned char)in[1] & 0x0F) << 2]; + out[2] = base64tab[((unsigned char)in[1] & 0x0F) << 2]; } out[3] = '='; out[4] = '\0'; diff --git a/src/buffers.c b/src/buffers.c new file mode 100644 index 000000000..bbc1f2831 --- /dev/null +++ b/src/buffers.c @@ -0,0 +1,116 @@ +/* + * Buffer management functions. + * + * Copyright 2000-2006 Willy Tarreau + * + * 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 + +void **pool_buffer = NULL; + +/* writes bytes from message to buffer . Returns 0 in case of + * success, or the number of bytes available otherwise. + * FIXME-20060521: handle unaligned data. + */ +int buffer_write(struct buffer *buf, const char *msg, int len) +{ + int max; + + max = buffer_realign(buf); + + if (len > max) + return max; + + memcpy(buf->r, msg, len); + buf->l += len; + buf->r += len; + if (buf->r == buf->data + BUFSIZE) + buf->r = buf->data; + return 0; +} + +/* + * this function writes the string at position which must be in buffer , + * and moves just after the end of . + * 's parameters (l, r, w, h, lr) are recomputed to be valid after the shift. + * the shift value (positive or negative) is returned. + * If there's no space left, the move is not done. + * + */ +int buffer_replace(struct buffer *b, char *pos, char *end, char *str) +{ + int delta; + int len; + + len = strlen(str); + delta = len - (end - pos); + + if (delta + b->r >= b->data + BUFSIZE) + return 0; /* no space left */ + + /* first, protect the end of the buffer */ + memmove(end + delta, end, b->data + b->l - end); + + /* now, copy str over pos */ + memcpy(pos, str,len); + + /* we only move data after the displaced zone */ + if (b->r > pos) b->r += delta; + if (b->w > pos) b->w += delta; + if (b->h > pos) b->h += delta; + if (b->lr > pos) b->lr += delta; + b->l += delta; + + return delta; +} + +/* + * same except that the string length is given, which allows str to be NULL if + * len is 0. + */ +int buffer_replace2(struct buffer *b, char *pos, char *end, char *str, int len) +{ + int delta; + + delta = len - (end - pos); + + if (delta + b->r >= b->data + BUFSIZE) + return 0; /* no space left */ + + if (b->data + b->l < end) { + /* The data has been stolen, we could have crashed. + * Maybe we should abort() ? */ + return 0; + } + + /* first, protect the end of the buffer */ + memmove(end + delta, end, b->data + b->l - end); + + /* now, copy str over pos */ + if (len) + memcpy(pos, str, len); + + /* we only move data after the displaced zone */ + if (b->r > pos) b->r += delta; + if (b->w > pos) b->w += delta; + if (b->h > pos) b->h += delta; + if (b->lr > pos) b->lr += delta; + b->l += delta; + + return delta; +} + + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/capture.c b/src/capture.c new file mode 100644 index 000000000..3a7c2c6b1 --- /dev/null +++ b/src/capture.c @@ -0,0 +1,24 @@ +/* + * Capture variables and functions. + * + * Copyright 2000-2006 Willy Tarreau + * + * 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 + +void **pool_capture = NULL; + + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/cfgparse.c b/src/cfgparse.c new file mode 100644 index 000000000..52b6e0865 --- /dev/null +++ b/src/cfgparse.c @@ -0,0 +1,2058 @@ +/* + * Configuration parser + * + * Copyright 2000-2006 Willy Tarreau + * + * 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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +const char *HTTP_302 = + "HTTP/1.0 302 Found\r\n" + "Cache-Control: no-cache\r\n" + "Connection: close\r\n" + "Location: "; /* not terminated since it will be concatenated with the URL */ + +/* same as 302 except that the browser MUST retry with the GET method */ +const char *HTTP_303 = + "HTTP/1.0 303 See Other\r\n" + "Cache-Control: no-cache\r\n" + "Connection: close\r\n" + "Location: "; /* not terminated since it will be concatenated with the URL */ + +const char *HTTP_400 = + "HTTP/1.0 400 Bad request\r\n" + "Cache-Control: no-cache\r\n" + "Connection: close\r\n" + "\r\n" + "

400 Bad request

\nYour browser sent an invalid request.\n\n"; + +const char *HTTP_403 = + "HTTP/1.0 403 Forbidden\r\n" + "Cache-Control: no-cache\r\n" + "Connection: close\r\n" + "\r\n" + "

403 Forbidden

\nRequest forbidden by administrative rules.\n\n"; + +const char *HTTP_408 = + "HTTP/1.0 408 Request Time-out\r\n" + "Cache-Control: no-cache\r\n" + "Connection: close\r\n" + "\r\n" + "

408 Request Time-out

\nYour browser didn't send a complete request in time.\n\n"; + +const char *HTTP_500 = + "HTTP/1.0 500 Server Error\r\n" + "Cache-Control: no-cache\r\n" + "Connection: close\r\n" + "\r\n" + "

500 Server Error

\nAn internal server error occured.\n\n"; + +const char *HTTP_502 = + "HTTP/1.0 502 Bad Gateway\r\n" + "Cache-Control: no-cache\r\n" + "Connection: close\r\n" + "\r\n" + "

502 Bad Gateway

\nThe server returned an invalid or incomplete response.\n\n"; + +const char *HTTP_503 = + "HTTP/1.0 503 Service Unavailable\r\n" + "Cache-Control: no-cache\r\n" + "Connection: close\r\n" + "\r\n" + "

503 Service Unavailable

\nNo server is available to handle this request.\n\n"; + +const char *HTTP_504 = + "HTTP/1.0 504 Gateway Time-out\r\n" + "Cache-Control: no-cache\r\n" + "Connection: close\r\n" + "\r\n" + "

504 Gateway Time-out

\nThe server didn't respond in time.\n\n"; + + +static struct proxy defproxy; /* fake proxy used to assign default values on all instances */ +int cfg_maxpconn = DEFAULT_MAXCONN; /* # of simultaneous connections per proxy (-N) */ +int cfg_maxconn = 0; /* # of simultaneous connections, (-n) */ + +/* + * converts to a list of listeners which are dynamically allocated. + * The format is "{addr|'*'}:port[-end][,{addr|'*'}:port[-end]]*", where : + * - can be empty or "*" to indicate INADDR_ANY ; + * - is a numerical port from 1 to 65535 ; + * - indicates to use the range from to instead (inclusive). + * This can be repeated as many times as necessary, separated by a coma. + * The argument is a pointer to a current list which should be appended + * to the tail of the new list. The pointer to the new list is returned. + */ +static struct listener *str2listener(char *str, struct listener *tail) +{ + struct listener *l; + char *c, *next, *range, *dupstr; + int port, end; + + next = dupstr = strdup(str); + + while (next && *next) { + struct sockaddr_storage ss; + + str = next; + /* 1) look for the end of the first address */ + if ((next = strrchr(str, ',')) != NULL) { + *next++ = 0; + } + + /* 2) look for the addr/port delimiter, it's the last colon. */ + if ((range = strrchr(str, ':')) == NULL) { + Alert("Missing port number: '%s'\n", str); + goto fail; + } + + *range++ = 0; + + if (strrchr(str, ':') != NULL) { + /* IPv6 address contains ':' */ + memset(&ss, 0, sizeof(ss)); + ss.ss_family = AF_INET6; + + if (!inet_pton(ss.ss_family, str, &((struct sockaddr_in6 *)&ss)->sin6_addr)) { + Alert("Invalid server address: '%s'\n", str); + goto fail; + } + } + else { + memset(&ss, 0, sizeof(ss)); + ss.ss_family = AF_INET; + + if (*str == '*' || *str == '\0') { /* INADDR_ANY */ + ((struct sockaddr_in *)&ss)->sin_addr.s_addr = INADDR_ANY; + } + else if (!inet_pton(ss.ss_family, str, &((struct sockaddr_in *)&ss)->sin_addr)) { + struct hostent *he; + + if ((he = gethostbyname(str)) == NULL) { + Alert("Invalid server name: '%s'\n", str); + goto fail; + } + else + ((struct sockaddr_in *)&ss)->sin_addr = + *(struct in_addr *) *(he->h_addr_list); + } + } + + /* 3) look for the port-end delimiter */ + if ((c = strchr(range, '-')) != NULL) { + *c++ = 0; + end = atol(c); + } + else { + end = atol(range); + } + + port = atol(range); + + if (port < 1 || port > 65535) { + Alert("Invalid port '%d' specified for address '%s'.\n", port, str); + goto fail; + } + + if (end < 1 || end > 65535) { + Alert("Invalid port '%d' specified for address '%s'.\n", end, str); + goto fail; + } + + for (; port <= end; port++) { + l = (struct listener *)calloc(1, sizeof(struct listener)); + l->next = tail; + tail = l; + + l->fd = -1; + l->addr = ss; + if (ss.ss_family == AF_INET6) + ((struct sockaddr_in6 *)(&l->addr))->sin6_port = htons(port); + else + ((struct sockaddr_in *)(&l->addr))->sin_port = htons(port); + + } /* end for(port) */ + } /* end while(next) */ + free(dupstr); + return tail; + fail: + free(dupstr); + return NULL; +} + + +/* + * parse a line in a section. Returns 0 if OK, -1 if error. + */ +int cfg_parse_global(char *file, int linenum, char **args) +{ + + if (!strcmp(args[0], "global")) { /* new section */ + /* no option, nothing special to do */ + return 0; + } + else if (!strcmp(args[0], "daemon")) { + global.mode |= MODE_DAEMON; + } + else if (!strcmp(args[0], "debug")) { + global.mode |= MODE_DEBUG; + } + else if (!strcmp(args[0], "noepoll")) { + cfg_polling_mechanism &= ~POLL_USE_EPOLL; + } + else if (!strcmp(args[0], "nopoll")) { + cfg_polling_mechanism &= ~POLL_USE_POLL; + } + else if (!strcmp(args[0], "quiet")) { + global.mode |= MODE_QUIET; + } + else if (!strcmp(args[0], "stats")) { + global.mode |= MODE_STATS; + } + else if (!strcmp(args[0], "uid")) { + if (global.uid != 0) { + Alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); + return 0; + } + if (*(args[1]) == 0) { + Alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + return -1; + } + global.uid = atol(args[1]); + } + else if (!strcmp(args[0], "gid")) { + if (global.gid != 0) { + Alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); + return 0; + } + if (*(args[1]) == 0) { + Alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + return -1; + } + global.gid = atol(args[1]); + } + else if (!strcmp(args[0], "nbproc")) { + if (global.nbproc != 0) { + Alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); + return 0; + } + if (*(args[1]) == 0) { + Alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + return -1; + } + global.nbproc = atol(args[1]); + } + else if (!strcmp(args[0], "maxconn")) { + if (global.maxconn != 0) { + Alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); + return 0; + } + if (*(args[1]) == 0) { + Alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + return -1; + } + global.maxconn = atol(args[1]); +#ifdef SYSTEM_MAXCONN + if (global.maxconn > DEFAULT_MAXCONN && cfg_maxconn <= DEFAULT_MAXCONN) { + Alert("parsing [%s:%d] : maxconn value %d too high for this system.\nLimiting to %d. Please use '-n' to force the value.\n", file, linenum, global.maxconn, DEFAULT_MAXCONN); + global.maxconn = DEFAULT_MAXCONN; + } +#endif /* SYSTEM_MAXCONN */ + } + else if (!strcmp(args[0], "ulimit-n")) { + if (global.rlimit_nofile != 0) { + Alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); + return 0; + } + if (*(args[1]) == 0) { + Alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + return -1; + } + global.rlimit_nofile = atol(args[1]); + } + else if (!strcmp(args[0], "chroot")) { + if (global.chroot != NULL) { + Alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); + return 0; + } + if (*(args[1]) == 0) { + Alert("parsing [%s:%d] : '%s' expects a directory as an argument.\n", file, linenum, args[0]); + return -1; + } + global.chroot = strdup(args[1]); + } + else if (!strcmp(args[0], "pidfile")) { + if (global.pidfile != NULL) { + Alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); + return 0; + } + if (*(args[1]) == 0) { + Alert("parsing [%s:%d] : '%s' expects a file name as an argument.\n", file, linenum, args[0]); + return -1; + } + global.pidfile = strdup(args[1]); + } + else if (!strcmp(args[0], "log")) { /* syslog server address */ + struct sockaddr_in *sa; + int facility, level; + + if (*(args[1]) == 0 || *(args[2]) == 0) { + Alert("parsing [%s:%d] : '%s' expects
and as arguments.\n", file, linenum, args[0]); + return -1; + } + + facility = get_log_facility(args[2]); + if (facility < 0) { + Alert("parsing [%s:%d] : unknown log facility '%s'\n", file, linenum, args[2]); + exit(1); + } + + level = 7; /* max syslog level = debug */ + if (*(args[3])) { + level = get_log_level(args[3]); + if (level < 0) { + Alert("parsing [%s:%d] : unknown optional log level '%s'\n", file, linenum, args[3]); + exit(1); + } + } + + sa = str2sa(args[1]); + if (!sa->sin_port) + sa->sin_port = htons(SYSLOG_PORT); + + if (global.logfac1 == -1) { + global.logsrv1 = *sa; + global.logfac1 = facility; + global.loglev1 = level; + } + else if (global.logfac2 == -1) { + global.logsrv2 = *sa; + global.logfac2 = facility; + global.loglev2 = level; + } + else { + Alert("parsing [%s:%d] : too many syslog servers\n", file, linenum); + return -1; + } + + } + else { + Alert("parsing [%s:%d] : unknown keyword '%s' in '%s' section\n", file, linenum, args[0], "global"); + return -1; + } + return 0; +} + + +static void init_default_instance() +{ + memset(&defproxy, 0, sizeof(defproxy)); + defproxy.mode = PR_MODE_TCP; + defproxy.state = PR_STNEW; + defproxy.maxconn = cfg_maxpconn; + defproxy.conn_retries = CONN_RETRIES; + defproxy.logfac1 = defproxy.logfac2 = -1; /* log disabled */ +} + +/* + * parse a line in a section. Returns 0 if OK, -1 if error. + */ +int cfg_parse_listen(char *file, int linenum, char **args) +{ + static struct proxy *curproxy = NULL; + struct server *newsrv = NULL; + char *err; + int rc; + + if (!strcmp(args[0], "listen")) { /* new proxy */ + if (!*args[1]) { + Alert("parsing [%s:%d] : '%s' expects an argument and\n" + " optionnally supports [addr1]:port1[-end1]{,[addr]:port[-end]}...\n", + file, linenum, args[0]); + return -1; + } + + if ((curproxy = (struct proxy *)calloc(1, sizeof(struct proxy))) == NULL) { + Alert("parsing [%s:%d] : out of memory.\n", file, linenum); + return -1; + } + + curproxy->next = proxy; + proxy = curproxy; + LIST_INIT(&curproxy->pendconns); + + curproxy->id = strdup(args[1]); + + /* parse the listener address if any */ + if (*args[2]) { + curproxy->listen = str2listener(args[2], curproxy->listen); + if (!curproxy->listen) + return -1; + global.maxsock++; + } + + /* set default values */ + curproxy->state = defproxy.state; + curproxy->maxconn = defproxy.maxconn; + curproxy->conn_retries = defproxy.conn_retries; + curproxy->options = defproxy.options; + + if (defproxy.check_req) + curproxy->check_req = strdup(defproxy.check_req); + curproxy->check_len = defproxy.check_len; + + if (defproxy.cookie_name) + curproxy->cookie_name = strdup(defproxy.cookie_name); + curproxy->cookie_len = defproxy.cookie_len; + + if (defproxy.capture_name) + curproxy->capture_name = strdup(defproxy.capture_name); + curproxy->capture_namelen = defproxy.capture_namelen; + curproxy->capture_len = defproxy.capture_len; + + if (defproxy.errmsg.msg400) + curproxy->errmsg.msg400 = strdup(defproxy.errmsg.msg400); + curproxy->errmsg.len400 = defproxy.errmsg.len400; + + if (defproxy.errmsg.msg403) + curproxy->errmsg.msg403 = strdup(defproxy.errmsg.msg403); + curproxy->errmsg.len403 = defproxy.errmsg.len403; + + if (defproxy.errmsg.msg408) + curproxy->errmsg.msg408 = strdup(defproxy.errmsg.msg408); + curproxy->errmsg.len408 = defproxy.errmsg.len408; + + if (defproxy.errmsg.msg500) + curproxy->errmsg.msg500 = strdup(defproxy.errmsg.msg500); + curproxy->errmsg.len500 = defproxy.errmsg.len500; + + if (defproxy.errmsg.msg502) + curproxy->errmsg.msg502 = strdup(defproxy.errmsg.msg502); + curproxy->errmsg.len502 = defproxy.errmsg.len502; + + if (defproxy.errmsg.msg503) + curproxy->errmsg.msg503 = strdup(defproxy.errmsg.msg503); + curproxy->errmsg.len503 = defproxy.errmsg.len503; + + if (defproxy.errmsg.msg504) + curproxy->errmsg.msg504 = strdup(defproxy.errmsg.msg504); + curproxy->errmsg.len504 = defproxy.errmsg.len504; + + curproxy->clitimeout = defproxy.clitimeout; + curproxy->contimeout = defproxy.contimeout; + curproxy->srvtimeout = defproxy.srvtimeout; + curproxy->mode = defproxy.mode; + curproxy->logfac1 = defproxy.logfac1; + curproxy->logsrv1 = defproxy.logsrv1; + curproxy->loglev1 = defproxy.loglev1; + curproxy->logfac2 = defproxy.logfac2; + curproxy->logsrv2 = defproxy.logsrv2; + curproxy->loglev2 = defproxy.loglev2; + curproxy->to_log = defproxy.to_log & ~LW_COOKIE & ~LW_REQHDR & ~ LW_RSPHDR; + curproxy->grace = defproxy.grace; + curproxy->uri_auth = defproxy.uri_auth; + curproxy->source_addr = defproxy.source_addr; + curproxy->mon_net = defproxy.mon_net; + curproxy->mon_mask = defproxy.mon_mask; + return 0; + } + else if (!strcmp(args[0], "defaults")) { /* use this one to assign default values */ + /* some variables may have already been initialized earlier */ + if (defproxy.check_req) free(defproxy.check_req); + if (defproxy.cookie_name) free(defproxy.cookie_name); + if (defproxy.capture_name) free(defproxy.capture_name); + if (defproxy.errmsg.msg400) free(defproxy.errmsg.msg400); + if (defproxy.errmsg.msg403) free(defproxy.errmsg.msg403); + if (defproxy.errmsg.msg408) free(defproxy.errmsg.msg408); + if (defproxy.errmsg.msg500) free(defproxy.errmsg.msg500); + if (defproxy.errmsg.msg502) free(defproxy.errmsg.msg502); + if (defproxy.errmsg.msg503) free(defproxy.errmsg.msg503); + if (defproxy.errmsg.msg504) free(defproxy.errmsg.msg504); + /* we cannot free uri_auth because it might already be used */ + init_default_instance(); + curproxy = &defproxy; + return 0; + } + else if (curproxy == NULL) { + Alert("parsing [%s:%d] : 'listen' or 'defaults' expected.\n", file, linenum); + return -1; + } + + if (!strcmp(args[0], "bind")) { /* new listen addresses */ + if (curproxy == &defproxy) { + Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); + return -1; + } + + if (strchr(args[1], ':') == NULL) { + Alert("parsing [%s:%d] : '%s' expects [addr1]:port1[-end1]{,[addr]:port[-end]}... as arguments.\n", + file, linenum, args[0]); + return -1; + } + curproxy->listen = str2listener(args[1], curproxy->listen); + if (!curproxy->listen) + return -1; + global.maxsock++; + return 0; + } + else if (!strcmp(args[0], "monitor-net")) { /* set the range of IPs to ignore */ + if (!*args[1] || !str2net(args[1], &curproxy->mon_net, &curproxy->mon_mask)) { + Alert("parsing [%s:%d] : '%s' expects address[/mask].\n", + file, linenum, args[0]); + return -1; + } + /* flush useless bits */ + curproxy->mon_net.s_addr &= curproxy->mon_mask.s_addr; + return 0; + } + else if (!strcmp(args[0], "mode")) { /* sets the proxy mode */ + if (!strcmp(args[1], "http")) curproxy->mode = PR_MODE_HTTP; + else if (!strcmp(args[1], "tcp")) curproxy->mode = PR_MODE_TCP; + else if (!strcmp(args[1], "health")) curproxy->mode = PR_MODE_HEALTH; + else { + Alert("parsing [%s:%d] : unknown proxy mode '%s'.\n", file, linenum, args[1]); + return -1; + } + } + else if (!strcmp(args[0], "disabled")) { /* disables this proxy */ + curproxy->state = PR_STSTOPPED; + } + else if (!strcmp(args[0], "enabled")) { /* enables this proxy (used to revert a disabled default) */ + curproxy->state = PR_STNEW; + } + else if (!strcmp(args[0], "cookie")) { /* cookie name */ + int cur_arg; + // if (curproxy == &defproxy) { + // Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); + // return -1; + // } + + if (curproxy->cookie_name != NULL) { + // Alert("parsing [%s:%d] : cookie name already specified. Continuing.\n", + // file, linenum); + // return 0; + free(curproxy->cookie_name); + } + + if (*(args[1]) == 0) { + Alert("parsing [%s:%d] : '%s' expects as argument.\n", + file, linenum, args[0]); + return -1; + } + curproxy->cookie_name = strdup(args[1]); + curproxy->cookie_len = strlen(curproxy->cookie_name); + + cur_arg = 2; + while (*(args[cur_arg])) { + if (!strcmp(args[cur_arg], "rewrite")) { + curproxy->options |= PR_O_COOK_RW; + } + else if (!strcmp(args[cur_arg], "indirect")) { + curproxy->options |= PR_O_COOK_IND; + } + else if (!strcmp(args[cur_arg], "insert")) { + curproxy->options |= PR_O_COOK_INS; + } + else if (!strcmp(args[cur_arg], "nocache")) { + curproxy->options |= PR_O_COOK_NOC; + } + else if (!strcmp(args[cur_arg], "postonly")) { + curproxy->options |= PR_O_COOK_POST; + } + else if (!strcmp(args[cur_arg], "prefix")) { + curproxy->options |= PR_O_COOK_PFX; + } + else { + Alert("parsing [%s:%d] : '%s' supports 'rewrite', 'insert', 'prefix', 'indirect', 'nocache' and 'postonly' options.\n", + file, linenum, args[0]); + return -1; + } + cur_arg++; + } + if (!POWEROF2(curproxy->options & (PR_O_COOK_RW|PR_O_COOK_IND))) { + Alert("parsing [%s:%d] : cookie 'rewrite' and 'indirect' modes are incompatible.\n", + file, linenum); + return -1; + } + + if (!POWEROF2(curproxy->options & (PR_O_COOK_RW|PR_O_COOK_INS|PR_O_COOK_PFX))) { + Alert("parsing [%s:%d] : cookie 'rewrite', 'insert' and 'prefix' modes are incompatible.\n", + file, linenum); + return -1; + } + }/* end else if (!strcmp(args[0], "cookie")) */ + else if (!strcmp(args[0], "appsession")) { /* cookie name */ + // if (curproxy == &defproxy) { + // Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); + // return -1; + // } + + if (curproxy->appsession_name != NULL) { + // Alert("parsing [%s:%d] : cookie name already specified. Continuing.\n", + // file, linenum); + // return 0; + free(curproxy->appsession_name); + } + + if (*(args[5]) == 0) { + Alert("parsing [%s:%d] : '%s' expects 'appsession' 'len' 'timeout' .\n", + file, linenum, args[0]); + return -1; + } + have_appsession = 1; + curproxy->appsession_name = strdup(args[1]); + curproxy->appsession_name_len = strlen(curproxy->appsession_name); + curproxy->appsession_len = atoi(args[3]); + curproxy->appsession_timeout = atoi(args[5]); + rc = chtbl_init(&(curproxy->htbl_proxy), TBLSIZ, hashpjw, match_str, destroy); + if (rc) { + Alert("Error Init Appsession Hashtable.\n"); + return -1; + } + } /* Url App Session */ + else if (!strcmp(args[0], "capture")) { + if (!strcmp(args[1], "cookie")) { /* name of a cookie to capture */ + // if (curproxy == &defproxy) { + // Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); + // return -1; + // } + + if (curproxy->capture_name != NULL) { + // Alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", + // file, linenum, args[0]); + // return 0; + free(curproxy->capture_name); + } + + if (*(args[4]) == 0) { + Alert("parsing [%s:%d] : '%s' expects 'cookie' 'len' .\n", + file, linenum, args[0]); + return -1; + } + curproxy->capture_name = strdup(args[2]); + curproxy->capture_namelen = strlen(curproxy->capture_name); + curproxy->capture_len = atol(args[4]); + if (curproxy->capture_len >= CAPTURE_LEN) { + Warning("parsing [%s:%d] : truncating capture length to %d bytes.\n", + file, linenum, CAPTURE_LEN - 1); + curproxy->capture_len = CAPTURE_LEN - 1; + } + curproxy->to_log |= LW_COOKIE; + } + else if (!strcmp(args[1], "request") && !strcmp(args[2], "header")) { + struct cap_hdr *hdr; + + if (curproxy == &defproxy) { + Alert("parsing [%s:%d] : '%s %s' not allowed in 'defaults' section.\n", file, linenum, args[0], args[1]); + return -1; + } + + if (*(args[3]) == 0 || strcmp(args[4], "len") != 0 || *(args[5]) == 0) { + Alert("parsing [%s:%d] : '%s %s' expects 'header' 'len' .\n", + file, linenum, args[0], args[1]); + return -1; + } + + hdr = calloc(sizeof(struct cap_hdr), 1); + hdr->next = curproxy->req_cap; + hdr->name = strdup(args[3]); + hdr->namelen = strlen(args[3]); + hdr->len = atol(args[5]); + hdr->index = curproxy->nb_req_cap++; + curproxy->req_cap = hdr; + curproxy->to_log |= LW_REQHDR; + } + else if (!strcmp(args[1], "response") && !strcmp(args[2], "header")) { + struct cap_hdr *hdr; + + if (curproxy == &defproxy) { + Alert("parsing [%s:%d] : '%s %s' not allowed in 'defaults' section.\n", file, linenum, args[0], args[1]); + return -1; + } + + if (*(args[3]) == 0 || strcmp(args[4], "len") != 0 || *(args[5]) == 0) { + Alert("parsing [%s:%d] : '%s %s' expects 'header' 'len' .\n", + file, linenum, args[0], args[1]); + return -1; + } + hdr = calloc(sizeof(struct cap_hdr), 1); + hdr->next = curproxy->rsp_cap; + hdr->name = strdup(args[3]); + hdr->namelen = strlen(args[3]); + hdr->len = atol(args[5]); + hdr->index = curproxy->nb_rsp_cap++; + curproxy->rsp_cap = hdr; + curproxy->to_log |= LW_RSPHDR; + } + else { + Alert("parsing [%s:%d] : '%s' expects 'cookie' or 'request header' or 'response header'.\n", + file, linenum, args[0]); + return -1; + } + } + else if (!strcmp(args[0], "contimeout")) { /* connect timeout */ + if (curproxy->contimeout != defproxy.contimeout) { + Alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); + return 0; + } + if (*(args[1]) == 0) { + Alert("parsing [%s:%d] : '%s' expects an integer as argument.\n", + file, linenum, args[0]); + return -1; + } + curproxy->contimeout = atol(args[1]); + } + else if (!strcmp(args[0], "clitimeout")) { /* client timeout */ + if (curproxy->clitimeout != defproxy.clitimeout) { + Alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", + file, linenum, args[0]); + return 0; + } + if (*(args[1]) == 0) { + Alert("parsing [%s:%d] : '%s' expects an integer as argument.\n", + file, linenum, args[0]); + return -1; + } + curproxy->clitimeout = atol(args[1]); + } + else if (!strcmp(args[0], "srvtimeout")) { /* server timeout */ + if (curproxy->srvtimeout != defproxy.srvtimeout) { + Alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); + return 0; + } + if (*(args[1]) == 0) { + Alert("parsing [%s:%d] : '%s' expects an integer as argument.\n", + file, linenum, args[0]); + return -1; + } + curproxy->srvtimeout = atol(args[1]); + } + else if (!strcmp(args[0], "retries")) { /* connection retries */ + if (*(args[1]) == 0) { + Alert("parsing [%s:%d] : '%s' expects an integer argument (dispatch counts for one).\n", + file, linenum, args[0]); + return -1; + } + curproxy->conn_retries = atol(args[1]); + } + else if (!strcmp(args[0], "stats")) { + if (curproxy != &defproxy && curproxy->uri_auth == defproxy.uri_auth) + curproxy->uri_auth = NULL; /* we must detach from the default config */ + + if (*(args[1]) == 0) { + Alert("parsing [%s:%d] : '%s' expects 'uri', 'realm', 'auth', 'scope' or 'enable'.\n", file, linenum, args[0]); + return -1; + } else if (!strcmp(args[1], "uri")) { + if (*(args[2]) == 0) { + Alert("parsing [%s:%d] : 'uri' needs an URI prefix.\n", file, linenum); + return -1; + } else if (!stats_set_uri(&curproxy->uri_auth, args[2])) { + Alert("parsing [%s:%d] : out of memory.\n", file, linenum); + return -1; + } + } else if (!strcmp(args[1], "realm")) { + if (*(args[2]) == 0) { + Alert("parsing [%s:%d] : 'realm' needs an realm name.\n", file, linenum); + return -1; + } else if (!stats_set_realm(&curproxy->uri_auth, args[2])) { + Alert("parsing [%s:%d] : out of memory.\n", file, linenum); + return -1; + } + } else if (!strcmp(args[1], "auth")) { + if (*(args[2]) == 0) { + Alert("parsing [%s:%d] : 'auth' needs a user:password account.\n", file, linenum); + return -1; + } else if (!stats_add_auth(&curproxy->uri_auth, args[2])) { + Alert("parsing [%s:%d] : out of memory.\n", file, linenum); + return -1; + } + } else if (!strcmp(args[1], "scope")) { + if (*(args[2]) == 0) { + Alert("parsing [%s:%d] : 'scope' needs a proxy name.\n", file, linenum); + return -1; + } else if (!stats_add_scope(&curproxy->uri_auth, args[2])) { + Alert("parsing [%s:%d] : out of memory.\n", file, linenum); + return -1; + } + } else if (!strcmp(args[1], "enable")) { + if (!stats_check_init_uri_auth(&curproxy->uri_auth)) { + Alert("parsing [%s:%d] : out of memory.\n", file, linenum); + return -1; + } + } else { + Alert("parsing [%s:%d] : unknown stats parameter '%s' (expects 'uri', 'realm', 'auth' or 'enable').\n", + file, linenum, args[0]); + return -1; + } + } + else if (!strcmp(args[0], "option")) { + if (*(args[1]) == 0) { + Alert("parsing [%s:%d] : '%s' expects an option name.\n", file, linenum, args[0]); + return -1; + } + if (!strcmp(args[1], "redispatch")) + /* enable reconnections to dispatch */ + curproxy->options |= PR_O_REDISP; +#ifdef TPROXY + else if (!strcmp(args[1], "transparent")) + /* enable transparent proxy connections */ + curproxy->options |= PR_O_TRANSP; +#endif + else if (!strcmp(args[1], "keepalive")) + /* enable keep-alive */ + curproxy->options |= PR_O_KEEPALIVE; + else if (!strcmp(args[1], "forwardfor")) + /* insert x-forwarded-for field */ + curproxy->options |= PR_O_FWDFOR; + else if (!strcmp(args[1], "logasap")) + /* log as soon as possible, without waiting for the session to complete */ + curproxy->options |= PR_O_LOGASAP; + else if (!strcmp(args[1], "abortonclose")) + /* abort connection if client closes during queue or connect() */ + curproxy->options |= PR_O_ABRT_CLOSE; + else if (!strcmp(args[1], "httpclose")) + /* force connection: close in both directions in HTTP mode */ + curproxy->options |= PR_O_HTTP_CLOSE; + else if (!strcmp(args[1], "forceclose")) + /* force connection: close in both directions in HTTP mode and enforce end of session */ + curproxy->options |= PR_O_FORCE_CLO | PR_O_HTTP_CLOSE; + else if (!strcmp(args[1], "checkcache")) + /* require examination of cacheability of the 'set-cookie' field */ + curproxy->options |= PR_O_CHK_CACHE; + else if (!strcmp(args[1], "httplog")) + /* generate a complete HTTP log */ + curproxy->to_log |= LW_DATE | LW_CLIP | LW_SVID | LW_REQ | LW_PXID | LW_RESP | LW_BYTES; + else if (!strcmp(args[1], "tcplog")) + /* generate a detailed TCP log */ + curproxy->to_log |= LW_DATE | LW_CLIP | LW_SVID | LW_PXID | LW_BYTES; + else if (!strcmp(args[1], "dontlognull")) { + /* don't log empty requests */ + curproxy->options |= PR_O_NULLNOLOG; + } + else if (!strcmp(args[1], "tcpka")) { + /* enable TCP keep-alives on client and server sessions */ + curproxy->options |= PR_O_TCP_CLI_KA | PR_O_TCP_SRV_KA; + } + else if (!strcmp(args[1], "clitcpka")) { + /* enable TCP keep-alives on client sessions */ + curproxy->options |= PR_O_TCP_CLI_KA; + } + else if (!strcmp(args[1], "srvtcpka")) { + /* enable TCP keep-alives on server sessions */ + curproxy->options |= PR_O_TCP_SRV_KA; + } + else if (!strcmp(args[1], "allbackups")) { + /* Use all backup servers simultaneously */ + curproxy->options |= PR_O_USE_ALL_BK; + } + else if (!strcmp(args[1], "httpchk")) { + /* use HTTP request to check servers' health */ + if (curproxy->check_req != NULL) { + free(curproxy->check_req); + } + curproxy->options |= PR_O_HTTP_CHK; + if (!*args[2]) { /* no argument */ + curproxy->check_req = strdup(DEF_CHECK_REQ); /* default request */ + curproxy->check_len = strlen(DEF_CHECK_REQ); + } else if (!*args[3]) { /* one argument : URI */ + int reqlen = strlen(args[2]) + strlen("OPTIONS / HTTP/1.0\r\n\r\n"); + curproxy->check_req = (char *)malloc(reqlen); + curproxy->check_len = snprintf(curproxy->check_req, reqlen, + "OPTIONS %s HTTP/1.0\r\n\r\n", args[2]); /* URI to use */ + } else { /* more arguments : METHOD URI [HTTP_VER] */ + int reqlen = strlen(args[2]) + strlen(args[3]) + 3 + strlen("\r\n\r\n"); + if (*args[4]) + reqlen += strlen(args[4]); + else + reqlen += strlen("HTTP/1.0"); + + curproxy->check_req = (char *)malloc(reqlen); + curproxy->check_len = snprintf(curproxy->check_req, reqlen, + "%s %s %s\r\n\r\n", args[2], args[3], *args[4]?args[4]:"HTTP/1.0"); + } + } + else if (!strcmp(args[1], "persist")) { + /* persist on using the server specified by the cookie, even when it's down */ + curproxy->options |= PR_O_PERSIST; + } + else { + Alert("parsing [%s:%d] : unknown option '%s'.\n", file, linenum, args[1]); + return -1; + } + return 0; + } + else if (!strcmp(args[0], "redispatch") || !strcmp(args[0], "redisp")) { + /* enable reconnections to dispatch */ + curproxy->options |= PR_O_REDISP; + } +#ifdef TPROXY + else if (!strcmp(args[0], "transparent")) { + /* enable transparent proxy connections */ + curproxy->options |= PR_O_TRANSP; + } +#endif + else if (!strcmp(args[0], "maxconn")) { /* maxconn */ + if (*(args[1]) == 0) { + Alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + return -1; + } + curproxy->maxconn = atol(args[1]); + } + else if (!strcmp(args[0], "grace")) { /* grace time (ms) */ + if (*(args[1]) == 0) { + Alert("parsing [%s:%d] : '%s' expects a time in milliseconds.\n", file, linenum, args[0]); + return -1; + } + curproxy->grace = atol(args[1]); + } + else if (!strcmp(args[0], "dispatch")) { /* dispatch address */ + if (curproxy == &defproxy) { + Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); + return -1; + } + if (strchr(args[1], ':') == NULL) { + Alert("parsing [%s:%d] : '%s' expects as argument.\n", file, linenum, args[0]); + return -1; + } + curproxy->dispatch_addr = *str2sa(args[1]); + } + else if (!strcmp(args[0], "balance")) { /* set balancing with optional algorithm */ + if (*(args[1])) { + if (!strcmp(args[1], "roundrobin")) { + curproxy->options |= PR_O_BALANCE_RR; + } + else if (!strcmp(args[1], "source")) { + curproxy->options |= PR_O_BALANCE_SH; + } + else { + Alert("parsing [%s:%d] : '%s' only supports 'roundrobin' and 'source' options.\n", file, linenum, args[0]); + return -1; + } + } + else /* if no option is set, use round-robin by default */ + curproxy->options |= PR_O_BALANCE_RR; + } + else if (!strcmp(args[0], "server")) { /* server address */ + int cur_arg; + char *rport; + char *raddr; + short realport; + int do_check; + + if (curproxy == &defproxy) { + Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); + return -1; + } + + if (!*args[2]) { + Alert("parsing [%s:%d] : '%s' expects and [:] as arguments.\n", + file, linenum, args[0]); + return -1; + } + if ((newsrv = (struct server *)calloc(1, sizeof(struct server))) == NULL) { + Alert("parsing [%s:%d] : out of memory.\n", file, linenum); + return -1; + } + + /* the servers are linked backwards first */ + newsrv->next = curproxy->srv; + curproxy->srv = newsrv; + newsrv->proxy = curproxy; + + LIST_INIT(&newsrv->pendconns); + do_check = 0; + newsrv->state = SRV_RUNNING; /* early server setup */ + newsrv->id = strdup(args[1]); + + /* several ways to check the port component : + * - IP => port=+0, relative + * - IP: => port=+0, relative + * - IP:N => port=N, absolute + * - IP:+N => port=+N, relative + * - IP:-N => port=-N, relative + */ + raddr = strdup(args[2]); + rport = strchr(raddr, ':'); + if (rport) { + *rport++ = 0; + realport = atol(rport); + if (!isdigit((int)*rport)) + newsrv->state |= SRV_MAPPORTS; + } else { + realport = 0; + newsrv->state |= SRV_MAPPORTS; + } + + newsrv->addr = *str2sa(raddr); + newsrv->addr.sin_port = htons(realport); + free(raddr); + + newsrv->curfd = -1; /* no health-check in progress */ + newsrv->inter = DEF_CHKINTR; + newsrv->rise = DEF_RISETIME; + newsrv->fall = DEF_FALLTIME; + newsrv->health = newsrv->rise; /* up, but will fall down at first failure */ + cur_arg = 3; + while (*args[cur_arg]) { + if (!strcmp(args[cur_arg], "cookie")) { + newsrv->cookie = strdup(args[cur_arg + 1]); + newsrv->cklen = strlen(args[cur_arg + 1]); + cur_arg += 2; + } + else if (!strcmp(args[cur_arg], "rise")) { + newsrv->rise = atol(args[cur_arg + 1]); + newsrv->health = newsrv->rise; + cur_arg += 2; + } + else if (!strcmp(args[cur_arg], "fall")) { + newsrv->fall = atol(args[cur_arg + 1]); + cur_arg += 2; + } + else if (!strcmp(args[cur_arg], "inter")) { + newsrv->inter = atol(args[cur_arg + 1]); + cur_arg += 2; + } + else if (!strcmp(args[cur_arg], "port")) { + newsrv->check_port = atol(args[cur_arg + 1]); + cur_arg += 2; + } + else if (!strcmp(args[cur_arg], "backup")) { + newsrv->state |= SRV_BACKUP; + cur_arg ++; + } + else if (!strcmp(args[cur_arg], "weight")) { + int w; + w = atol(args[cur_arg + 1]); + if (w < 1 || w > 256) { + Alert("parsing [%s:%d] : weight of server %s is not within 1 and 256 (%d).\n", + file, linenum, newsrv->id, w); + return -1; + } + newsrv->uweight = w - 1; + cur_arg += 2; + } + else if (!strcmp(args[cur_arg], "minconn")) { + newsrv->minconn = atol(args[cur_arg + 1]); + cur_arg += 2; + } + else if (!strcmp(args[cur_arg], "maxconn")) { + newsrv->maxconn = atol(args[cur_arg + 1]); + cur_arg += 2; + } + else if (!strcmp(args[cur_arg], "check")) { + global.maxsock++; + do_check = 1; + cur_arg += 1; + } + else if (!strcmp(args[cur_arg], "source")) { /* address to which we bind when connecting */ + if (!*args[cur_arg + 1]) { + Alert("parsing [%s:%d] : '%s' expects [:] as argument.\n", + file, linenum, "source"); + return -1; + } + newsrv->state |= SRV_BIND_SRC; + newsrv->source_addr = *str2sa(args[cur_arg + 1]); + cur_arg += 2; + } + else { + Alert("parsing [%s:%d] : server %s only supports options 'backup', 'cookie', 'check', 'inter', 'rise', 'fall', 'port', 'source', 'minconn', 'maxconn' and 'weight'.\n", + file, linenum, newsrv->id); + return -1; + } + } + + if (do_check) { + if (!newsrv->check_port && !(newsrv->state & SRV_MAPPORTS)) + newsrv->check_port = realport; /* by default */ + if (!newsrv->check_port) { + Alert("parsing [%s:%d] : server %s has neither service port nor check port. Check has been disabled.\n", + file, linenum, newsrv->id); + return -1; + } + newsrv->state |= SRV_CHECKED; + } + + if (newsrv->state & SRV_BACKUP) + curproxy->srv_bck++; + else + curproxy->srv_act++; + } + else if (!strcmp(args[0], "log")) { /* syslog server address */ + struct sockaddr_in *sa; + int facility; + + if (*(args[1]) && *(args[2]) == 0 && !strcmp(args[1], "global")) { + curproxy->logfac1 = global.logfac1; + curproxy->logsrv1 = global.logsrv1; + curproxy->loglev1 = global.loglev1; + curproxy->logfac2 = global.logfac2; + curproxy->logsrv2 = global.logsrv2; + curproxy->loglev2 = global.loglev2; + } + else if (*(args[1]) && *(args[2])) { + int level; + + facility = get_log_facility(args[2]); + if (facility < 0) { + Alert("parsing [%s:%d] : unknown log facility '%s'\n", file, linenum, args[2]); + exit(1); + } + + level = 7; /* max syslog level = debug */ + if (*(args[3])) { + level = get_log_level(args[3]); + if (level < 0) { + Alert("parsing [%s:%d] : unknown optional log level '%s'\n", file, linenum, args[3]); + exit(1); + } + } + + sa = str2sa(args[1]); + if (!sa->sin_port) + sa->sin_port = htons(SYSLOG_PORT); + + if (curproxy->logfac1 == -1) { + curproxy->logsrv1 = *sa; + curproxy->logfac1 = facility; + curproxy->loglev1 = level; + } + else if (curproxy->logfac2 == -1) { + curproxy->logsrv2 = *sa; + curproxy->logfac2 = facility; + curproxy->loglev2 = level; + } + else { + Alert("parsing [%s:%d] : too many syslog servers\n", file, linenum); + return -1; + } + } + else { + Alert("parsing [%s:%d] : 'log' expects either and or 'global' as arguments.\n", + file, linenum); + return -1; + } + } + else if (!strcmp(args[0], "source")) { /* address to which we bind when connecting */ + if (!*args[1]) { + Alert("parsing [%s:%d] : '%s' expects [:] as argument.\n", + file, linenum, "source"); + return -1; + } + + curproxy->source_addr = *str2sa(args[1]); + curproxy->options |= PR_O_BIND_SRC; + } + else if (!strcmp(args[0], "cliexp") || !strcmp(args[0], "reqrep")) { /* replace request header from a regex */ + regex_t *preg; + if (curproxy == &defproxy) { + Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); + return -1; + } + + if (*(args[1]) == 0 || *(args[2]) == 0) { + Alert("parsing [%s:%d] : '%s' expects and as arguments.\n", + file, linenum, args[0]); + return -1; + } + + preg = calloc(1, sizeof(regex_t)); + if (regcomp(preg, args[1], REG_EXTENDED) != 0) { + Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]); + return -1; + } + + err = chain_regex(&curproxy->req_exp, preg, ACT_REPLACE, strdup(args[2])); + if (err) { + Alert("parsing [%s:%d] : invalid character or unterminated sequence in replacement string near '%c'.\n", + file, linenum, *err); + return -1; + } + } + else if (!strcmp(args[0], "reqdel")) { /* delete request header from a regex */ + regex_t *preg; + if (curproxy == &defproxy) { + Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); + return -1; + } + + if (*(args[1]) == 0) { + Alert("parsing [%s:%d] : '%s' expects as an argument.\n", file, linenum, args[0]); + return -1; + } + + preg = calloc(1, sizeof(regex_t)); + if (regcomp(preg, args[1], REG_EXTENDED) != 0) { + Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]); + return -1; + } + + chain_regex(&curproxy->req_exp, preg, ACT_REMOVE, NULL); + } + else if (!strcmp(args[0], "reqdeny")) { /* deny a request if a header matches this regex */ + regex_t *preg; + if (curproxy == &defproxy) { + Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); + return -1; + } + + if (*(args[1]) == 0) { + Alert("parsing [%s:%d] : '%s' expects as an argument.\n", file, linenum, args[0]); + return -1; + } + + preg = calloc(1, sizeof(regex_t)); + if (regcomp(preg, args[1], REG_EXTENDED) != 0) { + Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]); + return -1; + } + + chain_regex(&curproxy->req_exp, preg, ACT_DENY, NULL); + } + else if (!strcmp(args[0], "reqpass")) { /* pass this header without allowing or denying the request */ + regex_t *preg; + if (curproxy == &defproxy) { + Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); + return -1; + } + + if (*(args[1]) == 0) { + Alert("parsing [%s:%d] : '%s' expects as an argument.\n", file, linenum, args[0]); + return -1; + } + + preg = calloc(1, sizeof(regex_t)); + if (regcomp(preg, args[1], REG_EXTENDED) != 0) { + Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]); + return -1; + } + + chain_regex(&curproxy->req_exp, preg, ACT_PASS, NULL); + } + else if (!strcmp(args[0], "reqallow")) { /* allow a request if a header matches this regex */ + regex_t *preg; + if (curproxy == &defproxy) { + Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); + return -1; + } + + if (*(args[1]) == 0) { + Alert("parsing [%s:%d] : '%s' expects as an argument.\n", file, linenum, args[0]); + return -1; + } + + preg = calloc(1, sizeof(regex_t)); + if (regcomp(preg, args[1], REG_EXTENDED) != 0) { + Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]); + return -1; + } + + chain_regex(&curproxy->req_exp, preg, ACT_ALLOW, NULL); + } + else if (!strcmp(args[0], "reqirep")) { /* replace request header from a regex, ignoring case */ + regex_t *preg; + if (curproxy == &defproxy) { + Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); + return -1; + } + + if (*(args[1]) == 0 || *(args[2]) == 0) { + Alert("parsing [%s:%d] : '%s' expects and as arguments.\n", + file, linenum, args[0]); + return -1; + } + + preg = calloc(1, sizeof(regex_t)); + if (regcomp(preg, args[1], REG_EXTENDED | REG_ICASE) != 0) { + Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]); + return -1; + } + + err = chain_regex(&curproxy->req_exp, preg, ACT_REPLACE, strdup(args[2])); + if (err) { + Alert("parsing [%s:%d] : invalid character or unterminated sequence in replacement string near '%c'.\n", + file, linenum, *err); + return -1; + } + } + else if (!strcmp(args[0], "reqidel")) { /* delete request header from a regex ignoring case */ + regex_t *preg; + if (curproxy == &defproxy) { + Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); + return -1; + } + + if (*(args[1]) == 0) { + Alert("parsing [%s:%d] : '%s' expects as an argument.\n", file, linenum, args[0]); + return -1; + } + + preg = calloc(1, sizeof(regex_t)); + if (regcomp(preg, args[1], REG_EXTENDED | REG_ICASE) != 0) { + Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]); + return -1; + } + + chain_regex(&curproxy->req_exp, preg, ACT_REMOVE, NULL); + } + else if (!strcmp(args[0], "reqideny")) { /* deny a request if a header matches this regex ignoring case */ + regex_t *preg; + if (curproxy == &defproxy) { + Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); + return -1; + } + + if (*(args[1]) == 0) { + Alert("parsing [%s:%d] : '%s' expects as an argument.\n", file, linenum, args[0]); + return -1; + } + + preg = calloc(1, sizeof(regex_t)); + if (regcomp(preg, args[1], REG_EXTENDED | REG_ICASE) != 0) { + Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]); + return -1; + } + + chain_regex(&curproxy->req_exp, preg, ACT_DENY, NULL); + } + else if (!strcmp(args[0], "reqipass")) { /* pass this header without allowing or denying the request */ + regex_t *preg; + if (curproxy == &defproxy) { + Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); + return -1; + } + + if (*(args[1]) == 0) { + Alert("parsing [%s:%d] : '%s' expects as an argument.\n", file, linenum, args[0]); + return -1; + } + + preg = calloc(1, sizeof(regex_t)); + if (regcomp(preg, args[1], REG_EXTENDED | REG_ICASE) != 0) { + Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]); + return -1; + } + + chain_regex(&curproxy->req_exp, preg, ACT_PASS, NULL); + } + else if (!strcmp(args[0], "reqiallow")) { /* allow a request if a header matches this regex ignoring case */ + regex_t *preg; + if (curproxy == &defproxy) { + Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); + return -1; + } + + if (*(args[1]) == 0) { + Alert("parsing [%s:%d] : '%s' expects as an argument.\n", file, linenum, args[0]); + return -1; + } + + preg = calloc(1, sizeof(regex_t)); + if (regcomp(preg, args[1], REG_EXTENDED | REG_ICASE) != 0) { + Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]); + return -1; + } + + chain_regex(&curproxy->req_exp, preg, ACT_ALLOW, NULL); + } + else if (!strcmp(args[0], "reqadd")) { /* add request header */ + if (curproxy == &defproxy) { + Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); + return -1; + } + + if (curproxy->nb_reqadd >= MAX_NEWHDR) { + Alert("parsing [%s:%d] : too many '%s'. Continuing.\n", file, linenum, args[0]); + return 0; + } + + if (*(args[1]) == 0) { + Alert("parsing [%s:%d] : '%s' expects
as an argument.\n", file, linenum, args[0]); + return -1; + } + + curproxy->req_add[curproxy->nb_reqadd++] = strdup(args[1]); + } + else if (!strcmp(args[0], "srvexp") || !strcmp(args[0], "rsprep")) { /* replace response header from a regex */ + regex_t *preg; + + if (*(args[1]) == 0 || *(args[2]) == 0) { + Alert("parsing [%s:%d] : '%s' expects and as arguments.\n", + file, linenum, args[0]); + return -1; + } + + preg = calloc(1, sizeof(regex_t)); + if (regcomp(preg, args[1], REG_EXTENDED) != 0) { + Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]); + return -1; + } + + err = chain_regex(&curproxy->rsp_exp, preg, ACT_REPLACE, strdup(args[2])); + if (err) { + Alert("parsing [%s:%d] : invalid character or unterminated sequence in replacement string near '%c'.\n", + file, linenum, *err); + return -1; + } + } + else if (!strcmp(args[0], "rspdel")) { /* delete response header from a regex */ + regex_t *preg; + if (curproxy == &defproxy) { + Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); + return -1; + } + + if (*(args[1]) == 0) { + Alert("parsing [%s:%d] : '%s' expects as an argument.\n", file, linenum, args[0]); + return -1; + } + + preg = calloc(1, sizeof(regex_t)); + if (regcomp(preg, args[1], REG_EXTENDED) != 0) { + Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]); + return -1; + } + + err = chain_regex(&curproxy->rsp_exp, preg, ACT_REMOVE, strdup(args[2])); + if (err) { + Alert("parsing [%s:%d] : invalid character or unterminated sequence in replacement string near '%c'.\n", + file, linenum, *err); + return -1; + } + } + else if (!strcmp(args[0], "rspdeny")) { /* block response header from a regex */ + regex_t *preg; + if (curproxy == &defproxy) { + Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); + return -1; + } + + if (*(args[1]) == 0) { + Alert("parsing [%s:%d] : '%s' expects as an argument.\n", file, linenum, args[0]); + return -1; + } + + preg = calloc(1, sizeof(regex_t)); + if (regcomp(preg, args[1], REG_EXTENDED) != 0) { + Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]); + return -1; + } + + err = chain_regex(&curproxy->rsp_exp, preg, ACT_DENY, strdup(args[2])); + if (err) { + Alert("parsing [%s:%d] : invalid character or unterminated sequence in replacement string near '%c'.\n", + file, linenum, *err); + return -1; + } + } + else if (!strcmp(args[0], "rspirep")) { /* replace response header from a regex ignoring case */ + regex_t *preg; + if (curproxy == &defproxy) { + Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); + return -1; + } + + if (*(args[1]) == 0 || *(args[2]) == 0) { + Alert("parsing [%s:%d] : '%s' expects and as arguments.\n", + file, linenum, args[0]); + return -1; + } + + preg = calloc(1, sizeof(regex_t)); + if (regcomp(preg, args[1], REG_EXTENDED | REG_ICASE) != 0) { + Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]); + return -1; + } + + err = chain_regex(&curproxy->rsp_exp, preg, ACT_REPLACE, strdup(args[2])); + if (err) { + Alert("parsing [%s:%d] : invalid character or unterminated sequence in replacement string near '%c'.\n", + file, linenum, *err); + return -1; + } + } + else if (!strcmp(args[0], "rspidel")) { /* delete response header from a regex ignoring case */ + regex_t *preg; + if (curproxy == &defproxy) { + Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); + return -1; + } + + if (*(args[1]) == 0) { + Alert("parsing [%s:%d] : '%s' expects as an argument.\n", file, linenum, args[0]); + return -1; + } + + preg = calloc(1, sizeof(regex_t)); + if (regcomp(preg, args[1], REG_EXTENDED | REG_ICASE) != 0) { + Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]); + return -1; + } + + err = chain_regex(&curproxy->rsp_exp, preg, ACT_REMOVE, strdup(args[2])); + if (err) { + Alert("parsing [%s:%d] : invalid character or unterminated sequence in replacement string near '%c'.\n", + file, linenum, *err); + return -1; + } + } + else if (!strcmp(args[0], "rspideny")) { /* block response header from a regex ignoring case */ + regex_t *preg; + if (curproxy == &defproxy) { + Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); + return -1; + } + + if (*(args[1]) == 0) { + Alert("parsing [%s:%d] : '%s' expects as an argument.\n", file, linenum, args[0]); + return -1; + } + + preg = calloc(1, sizeof(regex_t)); + if (regcomp(preg, args[1], REG_EXTENDED | REG_ICASE) != 0) { + Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[1]); + return -1; + } + + err = chain_regex(&curproxy->rsp_exp, preg, ACT_DENY, strdup(args[2])); + if (err) { + Alert("parsing [%s:%d] : invalid character or unterminated sequence in replacement string near '%c'.\n", + file, linenum, *err); + return -1; + } + } + else if (!strcmp(args[0], "rspadd")) { /* add response header */ + if (curproxy == &defproxy) { + Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); + return -1; + } + + if (curproxy->nb_rspadd >= MAX_NEWHDR) { + Alert("parsing [%s:%d] : too many '%s'. Continuing.\n", file, linenum, args[0]); + return 0; + } + + if (*(args[1]) == 0) { + Alert("parsing [%s:%d] : '%s' expects
as an argument.\n", file, linenum, args[0]); + return -1; + } + + curproxy->rsp_add[curproxy->nb_rspadd++] = strdup(args[1]); + } + else if (!strcmp(args[0], "errorloc") || + !strcmp(args[0], "errorloc302") || + !strcmp(args[0], "errorloc303")) { /* error location */ + int errnum, errlen; + char *err; + + // if (curproxy == &defproxy) { + // Alert("parsing [%s:%d] : '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]); + // return -1; + // } + + if (*(args[2]) == 0) { + Alert("parsing [%s:%d] : expects and as arguments.\n", file, linenum); + return -1; + } + + errnum = atol(args[1]); + if (!strcmp(args[0], "errorloc303")) { + err = malloc(strlen(HTTP_303) + strlen(args[2]) + 5); + errlen = sprintf(err, "%s%s\r\n\r\n", HTTP_303, args[2]); + } else { + err = malloc(strlen(HTTP_302) + strlen(args[2]) + 5); + errlen = sprintf(err, "%s%s\r\n\r\n", HTTP_302, args[2]); + } + + if (errnum == 400) { + if (curproxy->errmsg.msg400) { + //Warning("parsing [%s:%d] : error %d already defined.\n", file, linenum, errnum); + free(curproxy->errmsg.msg400); + } + curproxy->errmsg.msg400 = err; + curproxy->errmsg.len400 = errlen; + } + else if (errnum == 403) { + if (curproxy->errmsg.msg403) { + //Warning("parsing [%s:%d] : error %d already defined.\n", file, linenum, errnum); + free(curproxy->errmsg.msg403); + } + curproxy->errmsg.msg403 = err; + curproxy->errmsg.len403 = errlen; + } + else if (errnum == 408) { + if (curproxy->errmsg.msg408) { + //Warning("parsing [%s:%d] : error %d already defined.\n", file, linenum, errnum); + free(curproxy->errmsg.msg408); + } + curproxy->errmsg.msg408 = err; + curproxy->errmsg.len408 = errlen; + } + else if (errnum == 500) { + if (curproxy->errmsg.msg500) { + //Warning("parsing [%s:%d] : error %d already defined.\n", file, linenum, errnum); + free(curproxy->errmsg.msg500); + } + curproxy->errmsg.msg500 = err; + curproxy->errmsg.len500 = errlen; + } + else if (errnum == 502) { + if (curproxy->errmsg.msg502) { + //Warning("parsing [%s:%d] : error %d already defined.\n", file, linenum, errnum); + free(curproxy->errmsg.msg502); + } + curproxy->errmsg.msg502 = err; + curproxy->errmsg.len502 = errlen; + } + else if (errnum == 503) { + if (curproxy->errmsg.msg503) { + //Warning("parsing [%s:%d] : error %d already defined.\n", file, linenum, errnum); + free(curproxy->errmsg.msg503); + } + curproxy->errmsg.msg503 = err; + curproxy->errmsg.len503 = errlen; + } + else if (errnum == 504) { + if (curproxy->errmsg.msg504) { + //Warning("parsing [%s:%d] : error %d already defined.\n", file, linenum, errnum); + free(curproxy->errmsg.msg504); + } + curproxy->errmsg.msg504 = err; + curproxy->errmsg.len504 = errlen; + } + else { + Warning("parsing [%s:%d] : error %d relocation will be ignored.\n", file, linenum, errnum); + free(err); + } + } + else { + Alert("parsing [%s:%d] : unknown keyword '%s' in '%s' section\n", file, linenum, args[0], "listen"); + return -1; + } + return 0; +} + + +/* + * This function reads and parses the configuration file given in the argument. + * returns 0 if OK, -1 if error. + */ +int readcfgfile(char *file) +{ + char thisline[256]; + char *line; + FILE *f; + int linenum = 0; + char *end; + char *args[MAX_LINE_ARGS]; + int arg; + int cfgerr = 0; + int nbchk, mininter; + int confsect = CFG_NONE; + + struct proxy *curproxy = NULL; + struct server *newsrv = NULL; + + if ((f=fopen(file,"r")) == NULL) + return -1; + + init_default_instance(); + + while (fgets(line = thisline, sizeof(thisline), f) != NULL) { + linenum++; + + end = line + strlen(line); + + /* skip leading spaces */ + while (isspace((int)*line)) + line++; + + arg = 0; + args[arg] = line; + + while (*line && arg < MAX_LINE_ARGS) { + /* first, we'll replace \\, \, \#, \r, \n, \t, \xXX with their + * C equivalent value. Other combinations left unchanged (eg: \1). + */ + if (*line == '\\') { + int skip = 0; + if (line[1] == ' ' || line[1] == '\\' || line[1] == '#') { + *line = line[1]; + skip = 1; + } + else if (line[1] == 'r') { + *line = '\r'; + skip = 1; + } + else if (line[1] == 'n') { + *line = '\n'; + skip = 1; + } + else if (line[1] == 't') { + *line = '\t'; + skip = 1; + } + else if (line[1] == 'x') { + if ((line + 3 < end ) && ishex(line[2]) && ishex(line[3])) { + unsigned char hex1, hex2; + hex1 = toupper(line[2]) - '0'; + hex2 = toupper(line[3]) - '0'; + if (hex1 > 9) hex1 -= 'A' - '9' - 1; + if (hex2 > 9) hex2 -= 'A' - '9' - 1; + *line = (hex1<<4) + hex2; + skip = 3; + } + else { + Alert("parsing [%s:%d] : invalid or incomplete '\\x' sequence in '%s'.\n", file, linenum, args[0]); + return -1; + } + } + if (skip) { + memmove(line + 1, line + 1 + skip, end - (line + skip + 1)); + end -= skip; + } + line++; + } + else if (*line == '#' || *line == '\n' || *line == '\r') { + /* end of string, end of loop */ + *line = 0; + break; + } + else if (isspace((int)*line)) { + /* a non-escaped space is an argument separator */ + *line++ = 0; + while (isspace((int)*line)) + line++; + args[++arg] = line; + } + else { + line++; + } + } + + /* empty line */ + if (!**args) + continue; + + /* zero out remaining args */ + while (++arg < MAX_LINE_ARGS) { + args[arg] = line; + } + + if (!strcmp(args[0], "listen") || !strcmp(args[0], "defaults")) /* new proxy */ + confsect = CFG_LISTEN; + else if (!strcmp(args[0], "global")) /* global config */ + confsect = CFG_GLOBAL; + /* else it's a section keyword */ + + switch (confsect) { + case CFG_LISTEN: + if (cfg_parse_listen(file, linenum, args) < 0) + return -1; + break; + case CFG_GLOBAL: + if (cfg_parse_global(file, linenum, args) < 0) + return -1; + break; + default: + Alert("parsing [%s:%d] : unknown keyword '%s' out of section.\n", file, linenum, args[0]); + return -1; + } + + + } + fclose(f); + + /* + * Now, check for the integrity of all that we have collected. + */ + + /* will be needed further to delay some tasks */ + tv_now(&now); + + if ((curproxy = proxy) == NULL) { + Alert("parsing %s : no line. Nothing to do !\n", + file); + return -1; + } + + while (curproxy != NULL) { + if (curproxy->state == PR_STSTOPPED) { + curproxy = curproxy->next; + continue; + } + + if (curproxy->listen == NULL) { + Alert("parsing %s : listener %s has no listen address. Please either specify a valid address on the line, or use the keyword.\n", file, curproxy->id); + cfgerr++; + } + else if ((curproxy->mode != PR_MODE_HEALTH) && + !(curproxy->options & (PR_O_TRANSP | PR_O_BALANCE)) && + (*(int *)&curproxy->dispatch_addr.sin_addr == 0)) { + Alert("parsing %s : listener %s has no dispatch address and is not in transparent or balance mode.\n", + file, curproxy->id); + cfgerr++; + } + else if ((curproxy->mode != PR_MODE_HEALTH) && (curproxy->options & PR_O_BALANCE)) { + if (curproxy->options & PR_O_TRANSP) { + Alert("parsing %s : listener %s cannot use both transparent and balance mode.\n", + file, curproxy->id); + cfgerr++; + } +#ifdef WE_DONT_SUPPORT_SERVERLESS_LISTENERS + else if (curproxy->srv == NULL) { + Alert("parsing %s : listener %s needs at least 1 server in balance mode.\n", + file, curproxy->id); + cfgerr++; + } +#endif + else if (*(int *)&curproxy->dispatch_addr.sin_addr != 0) { + Warning("parsing %s : dispatch address of listener %s will be ignored in balance mode.\n", + file, curproxy->id); + } + } + else if (curproxy->mode == PR_MODE_TCP || curproxy->mode == PR_MODE_HEALTH) { /* TCP PROXY or HEALTH CHECK */ + if (curproxy->cookie_name != NULL) { + Warning("parsing %s : cookie will be ignored for listener %s.\n", + file, curproxy->id); + } + if ((newsrv = curproxy->srv) != NULL) { + Warning("parsing %s : servers will be ignored for listener %s.\n", + file, curproxy->id); + } + if (curproxy->rsp_exp != NULL) { + Warning("parsing %s : server regular expressions will be ignored for listener %s.\n", + file, curproxy->id); + } + if (curproxy->req_exp != NULL) { + Warning("parsing %s : client regular expressions will be ignored for listener %s.\n", + file, curproxy->id); + } + } + else if (curproxy->mode == PR_MODE_HTTP) { /* HTTP PROXY */ + if ((curproxy->cookie_name != NULL) && ((newsrv = curproxy->srv) == NULL)) { + Alert("parsing %s : HTTP proxy %s has a cookie but no server list !\n", + file, curproxy->id); + cfgerr++; + } + } + + /* first, we will invert the servers list order */ + newsrv = NULL; + while (curproxy->srv) { + struct server *next; + + next = curproxy->srv->next; + curproxy->srv->next = newsrv; + newsrv = curproxy->srv; + if (!next) + break; + curproxy->srv = next; + } + + /* now, newsrv == curproxy->srv */ + if (newsrv) { + struct server *srv; + int pgcd; + int act, bck; + + /* We will factor the weights to reduce the table, + * using Euclide's largest common divisor algorithm + */ + pgcd = newsrv->uweight + 1; + for (srv = newsrv->next; srv && pgcd > 1; srv = srv->next) { + int t, w; + + w = srv->uweight + 1; + while (w) { + t = pgcd % w; + pgcd = w; + w = t; + } + } + + act = bck = 0; + for (srv = newsrv; srv; srv = srv->next) { + srv->eweight = ((srv->uweight + 1) / pgcd) - 1; + if (srv->state & SRV_BACKUP) + bck += srv->eweight + 1; + else + act += srv->eweight + 1; + } + + /* this is the largest map we will ever need for this servers list */ + if (act < bck) + act = bck; + + curproxy->srv_map = (struct server **)calloc(act, sizeof(struct server *)); + /* recounts servers and their weights */ + recount_servers(curproxy); + recalc_server_map(curproxy); + } + + if (curproxy->options & PR_O_LOGASAP) + curproxy->to_log &= ~LW_BYTES; + + if (curproxy->errmsg.msg400 == NULL) { + curproxy->errmsg.msg400 = (char *)HTTP_400; + curproxy->errmsg.len400 = strlen(HTTP_400); + } + if (curproxy->errmsg.msg403 == NULL) { + curproxy->errmsg.msg403 = (char *)HTTP_403; + curproxy->errmsg.len403 = strlen(HTTP_403); + } + if (curproxy->errmsg.msg408 == NULL) { + curproxy->errmsg.msg408 = (char *)HTTP_408; + curproxy->errmsg.len408 = strlen(HTTP_408); + } + if (curproxy->errmsg.msg500 == NULL) { + curproxy->errmsg.msg500 = (char *)HTTP_500; + curproxy->errmsg.len500 = strlen(HTTP_500); + } + if (curproxy->errmsg.msg502 == NULL) { + curproxy->errmsg.msg502 = (char *)HTTP_502; + curproxy->errmsg.len502 = strlen(HTTP_502); + } + if (curproxy->errmsg.msg503 == NULL) { + curproxy->errmsg.msg503 = (char *)HTTP_503; + curproxy->errmsg.len503 = strlen(HTTP_503); + } + if (curproxy->errmsg.msg504 == NULL) { + curproxy->errmsg.msg504 = (char *)HTTP_504; + curproxy->errmsg.len504 = strlen(HTTP_504); + } + + /* + * If this server supports a maxconn parameter, it needs a dedicated + * tasks to fill the emptied slots when a connection leaves. + */ + newsrv = curproxy->srv; + while (newsrv != NULL) { + if (newsrv->minconn >= newsrv->maxconn) { + /* Only 'minconn' was specified, or it was higher than or equal + * to 'maxconn'. Let's turn this into maxconn and clean it, as + * this will avoid further useless expensive computations. + */ + newsrv->maxconn = newsrv->minconn; + newsrv->minconn = 0; + } + + if (newsrv->maxconn > 0) { + struct task *t; + + if ((t = pool_alloc(task)) == NULL) { + Alert("parsing [%s:%d] : out of memory.\n", file, linenum); + return -1; + } + + t->next = t->prev = t->rqnext = NULL; /* task not in run queue yet */ + t->wq = LIST_HEAD(wait_queue[1]); /* already assigned to the eternity queue */ + t->state = TASK_IDLE; + t->process = process_srv_queue; + t->context = newsrv; + newsrv->queue_mgt = t; + + /* never run it unless specifically woken up */ + tv_eternity(&t->expire); + task_queue(t); + } + newsrv = newsrv->next; + } + + /* now we'll start this proxy's health checks if any */ + /* 1- count the checkers to run simultaneously */ + nbchk = 0; + mininter = 0; + newsrv = curproxy->srv; + while (newsrv != NULL) { + if (newsrv->state & SRV_CHECKED) { + if (!mininter || mininter > newsrv->inter) + mininter = newsrv->inter; + nbchk++; + } + newsrv = newsrv->next; + } + + /* 2- start them as far as possible from each others while respecting + * their own intervals. For this, we will start them after their own + * interval added to the min interval divided by the number of servers, + * weighted by the server's position in the list. + */ + if (nbchk > 0) { + struct task *t; + int srvpos; + + newsrv = curproxy->srv; + srvpos = 0; + while (newsrv != NULL) { + /* should this server be checked ? */ + if (newsrv->state & SRV_CHECKED) { + if ((t = pool_alloc(task)) == NULL) { + Alert("parsing [%s:%d] : out of memory.\n", file, linenum); + return -1; + } + + t->next = t->prev = t->rqnext = NULL; /* task not in run queue yet */ + t->wq = LIST_HEAD(wait_queue[0]); /* but already has a wait queue assigned */ + t->state = TASK_IDLE; + t->process = process_chk; + t->context = newsrv; + + /* check this every ms */ + tv_delayfrom(&t->expire, &now, + newsrv->inter + mininter * srvpos / nbchk); + task_queue(t); + //task_wakeup(&rq, t); + srvpos++; + } + newsrv = newsrv->next; + } + } + + curproxy = curproxy->next; + } + if (cfgerr > 0) { + Alert("Errors found in configuration file, aborting.\n"); + return -1; + } + else + return 0; +} + + + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/checks.c b/src/checks.c new file mode 100644 index 000000000..8fd3e8e7c --- /dev/null +++ b/src/checks.c @@ -0,0 +1,393 @@ +/* + * Health-checks functions. + * + * Copyright 2000-2006 Willy Tarreau + * + * 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +/* Sets server down, notifies by all available means, recounts the + * remaining servers on the proxy and transfers queued sessions whenever + * possible to other servers. + */ +void set_server_down(struct server *s) +{ + struct pendconn *pc, *pc_bck, *pc_end; + struct session *sess; + int xferred; + + s->state &= ~SRV_RUNNING; + + if (s->health == s->rise) { + recount_servers(s->proxy); + recalc_server_map(s->proxy); + + /* we might have sessions queued on this server and waiting for + * a connection. Those which are redispatchable will be queued + * to another server or to the proxy itself. + */ + xferred = 0; + FOREACH_ITEM_SAFE(pc, pc_bck, &s->pendconns, pc_end, struct pendconn *, list) { + sess = pc->sess; + if ((sess->proxy->options & PR_O_REDISP)) { + /* The REDISP option was specified. We will ignore + * cookie and force to balance or use the dispatcher. + */ + sess->flags &= ~(SN_DIRECT | SN_ASSIGNED | SN_ADDR_SET); + sess->srv = NULL; /* it's left to the dispatcher to choose a server */ + if ((sess->flags & SN_CK_MASK) == SN_CK_VALID) { + sess->flags &= ~SN_CK_MASK; + sess->flags |= SN_CK_DOWN; + } + pendconn_free(pc); + task_wakeup(&rq, sess->task); + xferred++; + } + } + + sprintf(trash, "%sServer %s/%s is DOWN. %d active and %d backup servers left.%s" + " %d sessions active, %d requeued, %d remaining in queue.\n", + s->state & SRV_BACKUP ? "Backup " : "", + s->proxy->id, s->id, s->proxy->srv_act, s->proxy->srv_bck, + (s->proxy->srv_bck && !s->proxy->srv_act) ? " Running on backup." : "", + s->cur_sess, xferred, s->nbpend); + + Warning("%s", trash); + send_log(s->proxy, LOG_ALERT, "%s", trash); + + if (s->proxy->srv_bck == 0 && s->proxy->srv_act == 0) { + Alert("Proxy %s has no server available !\n", s->proxy->id); + send_log(s->proxy, LOG_EMERG, "Proxy %s has no server available !\n", s->proxy->id); + } + s->down_trans++; + } + s->health = 0; /* failure */ +} + + +/* + * This function is used only for server health-checks. It handles + * the connection acknowledgement. If the proxy requires HTTP health-checks, + * it sends the request. In other cases, it returns 1 if the socket is OK, + * or -1 if an error occured. + */ +int event_srv_chk_w(int fd) +{ + struct task *t = fdtab[fd].owner; + struct server *s = t->context; + int skerr; + socklen_t lskerr = sizeof(skerr); + + skerr = 1; + if ((getsockopt(fd, SOL_SOCKET, SO_ERROR, &skerr, &lskerr) == -1) + || (skerr != 0)) { + /* in case of TCP only, this tells us if the connection failed */ + s->result = -1; + fdtab[fd].state = FD_STERROR; + FD_CLR(fd, StaticWriteEvent); + } + else if (s->result != -1) { + /* we don't want to mark 'UP' a server on which we detected an error earlier */ + if (s->proxy->options & PR_O_HTTP_CHK) { + int ret; + /* we want to check if this host replies to "OPTIONS / HTTP/1.0" + * so we'll send the request, and won't wake the checker up now. + */ +#ifndef MSG_NOSIGNAL + ret = send(fd, s->proxy->check_req, s->proxy->check_len, MSG_DONTWAIT); +#else + ret = send(fd, s->proxy->check_req, s->proxy->check_len, MSG_DONTWAIT | MSG_NOSIGNAL); +#endif + if (ret == s->proxy->check_len) { + FD_SET(fd, StaticReadEvent); /* prepare for reading reply */ + FD_CLR(fd, StaticWriteEvent); /* nothing more to write */ + return 0; + } + else { + s->result = -1; + FD_CLR(fd, StaticWriteEvent); + } + } + else { + /* good TCP connection is enough */ + s->result = 1; + } + } + + task_wakeup(&rq, t); + return 0; +} + + +/* + * This function is used only for server health-checks. It handles + * the server's reply to an HTTP request. It returns 1 if the server replies + * 2xx or 3xx (valid responses), or -1 in other cases. + */ +int event_srv_chk_r(int fd) +{ + char reply[64]; + int len, result; + struct task *t = fdtab[fd].owner; + struct server *s = t->context; + int skerr; + socklen_t lskerr = sizeof(skerr); + + result = len = -1; + + getsockopt(fd, SOL_SOCKET, SO_ERROR, &skerr, &lskerr); + if (!skerr) { +#ifndef MSG_NOSIGNAL + len = recv(fd, reply, sizeof(reply), 0); +#else + /* Warning! Linux returns EAGAIN on SO_ERROR if data are still available + * but the connection was closed on the remote end. Fortunately, recv still + * works correctly and we don't need to do the getsockopt() on linux. + */ + len = recv(fd, reply, sizeof(reply), MSG_NOSIGNAL); +#endif + + if ((len >= sizeof("HTTP/1.0 000")) && + !memcmp(reply, "HTTP/1.", 7) && + (reply[9] == '2' || reply[9] == '3')) /* 2xx or 3xx */ + result = 1; + } + + if (result == -1) + fdtab[fd].state = FD_STERROR; + + if (s->result != -1) + s->result = result; + + FD_CLR(fd, StaticReadEvent); + task_wakeup(&rq, t); + return 0; +} + +/* + * manages a server health-check. Returns + * the time the task accepts to wait, or TIME_ETERNITY for infinity. + */ +int process_chk(struct task *t) +{ + struct server *s = t->context; + struct sockaddr_in sa; + int fd; + + //fprintf(stderr, "process_chk: task=%p\n", t); + + new_chk: + fd = s->curfd; + if (fd < 0) { /* no check currently running */ + //fprintf(stderr, "process_chk: 2\n"); + if (tv_cmp2_ms(&t->expire, &now) > 0) { /* not good time yet */ + task_queue(t); /* restore t to its place in the task list */ + return tv_remain2(&now, &t->expire); + } + + /* we don't send any health-checks when the proxy is stopped or when + * the server should not be checked. + */ + if (!(s->state & SRV_CHECKED) || s->proxy->state == PR_STSTOPPED) { + while (tv_cmp2_ms(&t->expire, &now) <= 0) + tv_delayfrom(&t->expire, &t->expire, s->inter); + task_queue(t); /* restore t to its place in the task list */ + return tv_remain2(&now, &t->expire); + } + + /* we'll initiate a new check */ + s->result = 0; /* no result yet */ + if ((fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) != -1) { + if ((fd < global.maxsock) && + (fcntl(fd, F_SETFL, O_NONBLOCK) != -1) && + (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *) &one, sizeof(one)) != -1)) { + //fprintf(stderr, "process_chk: 3\n"); + + /* we'll connect to the check port on the server */ + sa = s->addr; + sa.sin_port = htons(s->check_port); + + /* allow specific binding : + * - server-specific at first + * - proxy-specific next + */ + if (s->state & SRV_BIND_SRC) { + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(one)); + if (bind(fd, (struct sockaddr *)&s->source_addr, sizeof(s->source_addr)) == -1) { + Alert("Cannot bind to source address before connect() for server %s/%s. Aborting.\n", + s->proxy->id, s->id); + s->result = -1; + } + } + else if (s->proxy->options & PR_O_BIND_SRC) { + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(one)); + if (bind(fd, (struct sockaddr *)&s->proxy->source_addr, sizeof(s->proxy->source_addr)) == -1) { + Alert("Cannot bind to source address before connect() for proxy %s. Aborting.\n", + s->proxy->id); + s->result = -1; + } + } + + if (!s->result) { + if ((connect(fd, (struct sockaddr *)&sa, sizeof(sa)) != -1) || (errno == EINPROGRESS)) { + /* OK, connection in progress or established */ + + //fprintf(stderr, "process_chk: 4\n"); + + s->curfd = fd; /* that's how we know a test is in progress ;-) */ + fdtab[fd].owner = t; + fdtab[fd].read = &event_srv_chk_r; + fdtab[fd].write = &event_srv_chk_w; + fdtab[fd].state = FD_STCONN; /* connection in progress */ + FD_SET(fd, StaticWriteEvent); /* for connect status */ +#ifdef DEBUG_FULL + assert (!FD_ISSET(fd, StaticReadEvent)); +#endif + fd_insert(fd); + /* FIXME: we allow up to for a connection to establish, but we should use another parameter */ + tv_delayfrom(&t->expire, &now, s->inter); + task_queue(t); /* restore t to its place in the task list */ + return tv_remain(&now, &t->expire); + } + else if (errno != EALREADY && errno != EISCONN && errno != EAGAIN) { + s->result = -1; /* a real error */ + } + } + } + close(fd); /* socket creation error */ + } + + if (!s->result) { /* nothing done */ + //fprintf(stderr, "process_chk: 6\n"); + while (tv_cmp2_ms(&t->expire, &now) <= 0) + tv_delayfrom(&t->expire, &t->expire, s->inter); + goto new_chk; /* may be we should initialize a new check */ + } + + /* here, we have seen a failure */ + if (s->health > s->rise) { + s->health--; /* still good */ + s->failed_checks++; + } + else + set_server_down(s); + + //fprintf(stderr, "process_chk: 7\n"); + /* FIXME: we allow up to for a connection to establish, but we should use another parameter */ + while (tv_cmp2_ms(&t->expire, &now) <= 0) + tv_delayfrom(&t->expire, &t->expire, s->inter); + goto new_chk; + } + else { + //fprintf(stderr, "process_chk: 8\n"); + /* there was a test running */ + if (s->result > 0) { /* good server detected */ + //fprintf(stderr, "process_chk: 9\n"); + s->health++; /* was bad, stays for a while */ + if (s->health >= s->rise) { + s->state |= SRV_RUNNING; + + if (s->health == s->rise) { + int xferred; + + recount_servers(s->proxy); + recalc_server_map(s->proxy); + + /* check if we can handle some connections queued at the proxy. We + * will take as many as we can handle. + */ + for (xferred = 0; !s->maxconn || xferred < srv_dynamic_maxconn(s); xferred++) { + struct session *sess; + struct pendconn *p; + + p = pendconn_from_px(s->proxy); + if (!p) + break; + p->sess->srv = s; + sess = p->sess; + pendconn_free(p); + task_wakeup(&rq, sess->task); + } + + sprintf(trash, + "%sServer %s/%s is UP. %d active and %d backup servers online.%s" + " %d sessions requeued, %d total in queue.\n", + s->state & SRV_BACKUP ? "Backup " : "", + s->proxy->id, s->id, s->proxy->srv_act, s->proxy->srv_bck, + (s->proxy->srv_bck && !s->proxy->srv_act) ? " Running on backup." : "", + xferred, s->nbpend); + + Warning("%s", trash); + send_log(s->proxy, LOG_NOTICE, "%s", trash); + } + + s->health = s->rise + s->fall - 1; /* OK now */ + } + s->curfd = -1; /* no check running anymore */ + //FD_CLR(fd, StaticWriteEvent); + fd_delete(fd); + while (tv_cmp2_ms(&t->expire, &now) <= 0) + tv_delayfrom(&t->expire, &t->expire, s->inter); + goto new_chk; + } + else if (s->result < 0 || tv_cmp2_ms(&t->expire, &now) <= 0) { + //fprintf(stderr, "process_chk: 10\n"); + /* failure or timeout detected */ + if (s->health > s->rise) { + s->health--; /* still good */ + s->failed_checks++; + } + else + set_server_down(s); + s->curfd = -1; + //FD_CLR(fd, StaticWriteEvent); + fd_delete(fd); + while (tv_cmp2_ms(&t->expire, &now) <= 0) + tv_delayfrom(&t->expire, &t->expire, s->inter); + goto new_chk; + } + /* if result is 0 and there's no timeout, we have to wait again */ + } + //fprintf(stderr, "process_chk: 11\n"); + s->result = 0; + task_queue(t); /* restore t to its place in the task list */ + return tv_remain2(&now, &t->expire); +} + + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/chtbl.c b/src/chtbl.c index 481b3ac63..c8e794fd3 100644 --- a/src/chtbl.c +++ b/src/chtbl.c @@ -18,8 +18,8 @@ #include #include -#include -#include +#include +#include /***************************************************************************** * * @@ -30,47 +30,47 @@ int chtbl_init(CHTbl *htbl, int buckets, int (*h)(const void *key), int (*match)(const void *key1, const void *key2), void (*destroy)(void*data)) { - int i; + int i; - /***************************************************************************** - * * - * Allocate space for the hash table. * - * * - *****************************************************************************/ + /***************************************************************************** + * * + * Allocate space for the hash table. * + * * + *****************************************************************************/ - if ((htbl->table = (List *)malloc(buckets * sizeof(List))) == NULL) - return -1; + if ((htbl->table = (List *)malloc(buckets * sizeof(List))) == NULL) + return -1; - /***************************************************************************** - * * - * Initialize the buckets. * - * * - *****************************************************************************/ + /***************************************************************************** + * * + * Initialize the buckets. * + * * + *****************************************************************************/ - htbl->buckets = buckets; + htbl->buckets = buckets; - for (i = 0; i < htbl->buckets; i++) - list_init(&htbl->table[i], destroy); + for (i = 0; i < htbl->buckets; i++) + list_init(&htbl->table[i], destroy); - /***************************************************************************** - * * - * Encapsulate the functions. * - * * - *****************************************************************************/ + /***************************************************************************** + * * + * Encapsulate the functions. * + * * + *****************************************************************************/ - htbl->h = h; - htbl->match = match; - htbl->destroy = destroy; + htbl->h = h; + htbl->match = match; + htbl->destroy = destroy; - /***************************************************************************** - * * - * Initialize the number of elements in the table. * - * * - *****************************************************************************/ + /***************************************************************************** + * * + * Initialize the number of elements in the table. * + * * + *****************************************************************************/ - htbl->size = 0; + htbl->size = 0; - return 0; + return 0; } /* end chtbl_init () */ /***************************************************************************** @@ -81,35 +81,35 @@ int chtbl_init(CHTbl *htbl, int buckets, int (*h)(const void *key), int void chtbl_destroy(CHTbl *htbl) { - int i; + int i; - /***************************************************************************** - * * - * Destroy each bucket. * - * * - *****************************************************************************/ + /***************************************************************************** + * * + * Destroy each bucket. * + * * + *****************************************************************************/ - for (i = 0; i < htbl->buckets; i++) { - list_destroy(&htbl->table[i]); - } /* end for () */ + for (i = 0; i < htbl->buckets; i++) { + list_destroy(&htbl->table[i]); + } /* end for () */ - /***************************************************************************** - * * - * Free the storage allocated for the hash table. * - * * - *****************************************************************************/ + /***************************************************************************** + * * + * Free the storage allocated for the hash table. * + * * + *****************************************************************************/ - free(htbl->table); + free(htbl->table); - /***************************************************************************** - * * - * No operations are allowed now, but clear the structure as a precaution. * - * * - *****************************************************************************/ + /***************************************************************************** + * * + * No operations are allowed now, but clear the structure as a precaution. * + * * + *****************************************************************************/ - memset(htbl, 0, sizeof(CHTbl)); + memset(htbl, 0, sizeof(CHTbl)); - return; + return; } /* end chtbl_destroy() */ /***************************************************************************** @@ -120,38 +120,38 @@ void chtbl_destroy(CHTbl *htbl) { int chtbl_insert(CHTbl *htbl, const void *data) { - void *temp; - int bucket,retval; + void *temp; + int bucket,retval; - /***************************************************************************** - * * - * Do nothing if the data is already in the table. * - * * - *****************************************************************************/ + /***************************************************************************** + * * + * Do nothing if the data is already in the table. * + * * + *****************************************************************************/ - temp = (void *)data; + temp = (void *)data; - if (chtbl_lookup(htbl, &temp) == 0) - return 1; + if (chtbl_lookup(htbl, &temp) == 0) + return 1; - /***************************************************************************** - * * - * Hash the key. * - * * - *****************************************************************************/ + /***************************************************************************** + * * + * Hash the key. * + * * + *****************************************************************************/ - bucket = htbl->h(data) % htbl->buckets; + bucket = htbl->h(data) % htbl->buckets; - /***************************************************************************** - * * - * Insert the data into the bucket. * - * * - *****************************************************************************/ + /***************************************************************************** + * * + * Insert the data into the bucket. * + * * + *****************************************************************************/ - if ((retval = list_ins_next(&htbl->table[bucket], NULL, data)) == 0) - htbl->size++; + if ((retval = list_ins_next(&htbl->table[bucket], NULL, data)) == 0) + htbl->size++; - return retval; + return retval; } /* end chtbl_insert() */ /***************************************************************************** @@ -162,53 +162,54 @@ int chtbl_insert(CHTbl *htbl, const void *data) { int chtbl_remove(CHTbl *htbl, void **data) { - ListElmt *element, *prev; - int bucket; + ListElmt *element, *prev; + int bucket; - /***************************************************************************** - * * - * Hash the key. * - * * - *****************************************************************************/ + /********************************************************************* + * * + * Hash the key. * + * * + *********************************************************************/ - bucket = htbl->h(*data) % htbl->buckets; + bucket = htbl->h(*data) % htbl->buckets; - /***************************************************************************** - * * - * Search for the data in the bucket. * - * * - *****************************************************************************/ + /********************************************************************* + * * + * Search for the data in the bucket. * + * * + *********************************************************************/ - prev = NULL; + prev = NULL; - for (element = list_head(&htbl->table[bucket]); element != NULL; element = list_next(element)) { - if (htbl->match(*data, list_data(element))) { + for (element = list_head(&htbl->table[bucket]); + element != NULL; element = list_next(element)) { + if (htbl->match(*data, list_data(element))) { - /*********************************************************************** - * * - * Remove the data from the bucket. * - * * - ***********************************************************************/ + /***************************************************** + * * + * Remove the data from the bucket. * + * * + *****************************************************/ - if (list_rem_next(&htbl->table[bucket], prev, data) == 0) { - htbl->size--; - return 0; - } /* end if() */ - else { - return -1; - }/* end else */ - }/* end if (htbl->match(*data, list_data(element))) */ + if (list_rem_next(&htbl->table[bucket], prev, data) == 0) { + htbl->size--; + return 0; + } /* end if() */ + else { + return -1; + }/* end else */ + }/* end if (htbl->match(*data, list_data(element))) */ - prev = element; - }/* end for() */ + prev = element; + }/* end for() */ - /***************************************************************************** - * * - * Return that the data was not found. * - * * - *****************************************************************************/ + /********************************************************************* + * * + * Return that the data was not found. * + * * + *********************************************************************/ - return -1; + return -1; } /* end int chtbl_remove(CHTbl *htbl, void **data) */ /***************************************************************************** @@ -219,42 +220,50 @@ int chtbl_remove(CHTbl *htbl, void **data) { int chtbl_lookup(const CHTbl *htbl, void **data) { - ListElmt *element; - int bucket; + ListElmt *element; + int bucket; - /***************************************************************************** - * * - * Hash the key. * - * * - *****************************************************************************/ + /********************************************************************* + * * + * Hash the key. * + * * + *********************************************************************/ - bucket = htbl->h(*data) % htbl->buckets; + bucket = htbl->h(*data) % htbl->buckets; - /***************************************************************************** - * * - * Search for the data in the bucket. * - * * - *****************************************************************************/ + /********************************************************************* + * * + * Search for the data in the bucket. * + * * + *********************************************************************/ - for (element = list_head(&htbl->table[bucket]); element != NULL; element = list_next(element)) { - if (htbl->match(*data, list_data(element))) { + for (element = list_head(&htbl->table[bucket]); + element != NULL; element = list_next(element)) { + if (htbl->match(*data, list_data(element))) { - /*********************************************************************** - * * - * Pass back the data from the table. * - * * - ***********************************************************************/ + /***************************************************** + * * + * Pass back the data from the table. * + * * + *****************************************************/ - *data = list_data(element); - return 0; - }/* end if() */ - }/* end for() */ + *data = list_data(element); + return 0; + }/* end if() */ + }/* end for() */ - /***************************************************************************** - * * - * Return that the data was not found. * - * * - *****************************************************************************/ + /********************************************************************* + * * + * Return that the data was not found. * + * * + *********************************************************************/ - return -1; + return -1; } /* end chtbl_lookup() */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/client.c b/src/client.c new file mode 100644 index 000000000..d42e8aadd --- /dev/null +++ b/src/client.c @@ -0,0 +1,397 @@ +/* + * Client-side variables and functions. + * + * Copyright 2000-2006 Willy Tarreau + * + * 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 +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + + +/* + * FIXME: This should move to the STREAM_SOCK code then split into TCP and HTTP. + */ + +/* + * this function is called on a read event from a listen socket, corresponding + * to an accept. It tries to accept as many connections as possible. + * It returns 0. + */ +int event_accept(int fd) { + struct proxy *p = (struct proxy *)fdtab[fd].owner; + struct session *s; + struct task *t; + int cfd; + int max_accept; + + if (global.nbproc > 1) + max_accept = 8; /* let other processes catch some connections too */ + else + max_accept = -1; + + while (p->nbconn < p->maxconn && max_accept--) { + struct sockaddr_storage addr; + socklen_t laddr = sizeof(addr); + + if ((cfd = accept(fd, (struct sockaddr *)&addr, &laddr)) == -1) { + switch (errno) { + case EAGAIN: + case EINTR: + case ECONNABORTED: + return 0; /* nothing more to accept */ + case ENFILE: + send_log(p, LOG_EMERG, + "Proxy %s reached system FD limit at %d. Please check system tunables.\n", + p->id, maxfd); + return 0; + case EMFILE: + send_log(p, LOG_EMERG, + "Proxy %s reached process FD limit at %d. Please check 'ulimit-n' and restart.\n", + p->id, maxfd); + return 0; + case ENOBUFS: + case ENOMEM: + send_log(p, LOG_EMERG, + "Proxy %s reached system memory limit at %d sockets. Please check system tunables.\n", + p->id, maxfd); + return 0; + default: + return 0; + } + } + + if ((s = pool_alloc(session)) == NULL) { /* disable this proxy for a while */ + Alert("out of memory in event_accept().\n"); + FD_CLR(fd, StaticReadEvent); + p->state = PR_STIDLE; + close(cfd); + return 0; + } + + /* if this session comes from a known monitoring system, we want to ignore + * it as soon as possible, which means closing it immediately for TCP. + */ + s->flags = 0; + if (addr.ss_family == AF_INET && + p->mon_mask.s_addr && + (((struct sockaddr_in *)&addr)->sin_addr.s_addr & p->mon_mask.s_addr) == p->mon_net.s_addr) { + if (p->mode == PR_MODE_TCP) { + close(cfd); + pool_free(session, s); + continue; + } + s->flags |= SN_MONITOR; + } + + if ((t = pool_alloc(task)) == NULL) { /* disable this proxy for a while */ + Alert("out of memory in event_accept().\n"); + FD_CLR(fd, StaticReadEvent); + p->state = PR_STIDLE; + close(cfd); + pool_free(session, s); + return 0; + } + + s->cli_addr = addr; + if (cfd >= global.maxsock) { + Alert("accept(): not enough free sockets. Raise -n argument. Giving up.\n"); + close(cfd); + pool_free(task, t); + pool_free(session, s); + return 0; + } + + if ((fcntl(cfd, F_SETFL, O_NONBLOCK) == -1) || + (setsockopt(cfd, IPPROTO_TCP, TCP_NODELAY, + (char *) &one, sizeof(one)) == -1)) { + Alert("accept(): cannot set the socket in non blocking mode. Giving up\n"); + close(cfd); + pool_free(task, t); + pool_free(session, s); + return 0; + } + + if (p->options & PR_O_TCP_CLI_KA) + setsockopt(cfd, SOL_SOCKET, SO_KEEPALIVE, (char *) &one, sizeof(one)); + + t->next = t->prev = t->rqnext = NULL; /* task not in run queue yet */ + t->wq = LIST_HEAD(wait_queue[0]); /* but already has a wait queue assigned */ + t->state = TASK_IDLE; + t->process = process_session; + t->context = s; + + s->task = t; + s->proxy = p; + s->cli_state = (p->mode == PR_MODE_HTTP) ? CL_STHEADERS : CL_STDATA; /* no HTTP headers for non-HTTP proxies */ + s->srv_state = SV_STIDLE; + s->req = s->rep = NULL; /* will be allocated later */ + + s->res_cr = s->res_cw = s->res_sr = s->res_sw = RES_SILENT; + s->cli_fd = cfd; + s->srv_fd = -1; + s->req_line.len = -1; + s->auth_hdr.len = -1; + s->srv = NULL; + s->pend_pos = NULL; + s->conn_retries = p->conn_retries; + + if (s->flags & SN_MONITOR) + s->logs.logwait = 0; + else + s->logs.logwait = p->to_log; + + s->logs.tv_accept = now; + s->logs.t_request = -1; + s->logs.t_queue = -1; + s->logs.t_connect = -1; + s->logs.t_data = -1; + s->logs.t_close = 0; + s->logs.uri = NULL; + s->logs.cli_cookie = NULL; + s->logs.srv_cookie = NULL; + s->logs.status = -1; + s->logs.bytes = 0; + s->logs.prx_queue_size = 0; /* we get the number of pending conns before us */ + s->logs.srv_queue_size = 0; /* we will get this number soon */ + + s->data_source = DATA_SRC_NONE; + + s->uniq_id = totalconn; + p->cum_conn++; + + if (p->nb_req_cap > 0) { + if ((s->req_cap = + pool_alloc_from(p->req_cap_pool, p->nb_req_cap*sizeof(char *))) + == NULL) { /* no memory */ + close(cfd); /* nothing can be done for this fd without memory */ + pool_free(task, t); + pool_free(session, s); + return 0; + } + memset(s->req_cap, 0, p->nb_req_cap*sizeof(char *)); + } + else + s->req_cap = NULL; + + if (p->nb_rsp_cap > 0) { + if ((s->rsp_cap = + pool_alloc_from(p->rsp_cap_pool, p->nb_rsp_cap*sizeof(char *))) + == NULL) { /* no memory */ + if (s->req_cap != NULL) + pool_free_to(p->req_cap_pool, s->req_cap); + close(cfd); /* nothing can be done for this fd without memory */ + pool_free(task, t); + pool_free(session, s); + return 0; + } + memset(s->rsp_cap, 0, p->nb_rsp_cap*sizeof(char *)); + } + else + s->rsp_cap = NULL; + + if ((p->mode == PR_MODE_TCP || p->mode == PR_MODE_HTTP) + && (p->logfac1 >= 0 || p->logfac2 >= 0)) { + struct sockaddr_storage sockname; + socklen_t namelen = sizeof(sockname); + + if (addr.ss_family != AF_INET || + !(s->proxy->options & PR_O_TRANSP) || + get_original_dst(cfd, (struct sockaddr_in *)&sockname, &namelen) == -1) + getsockname(cfd, (struct sockaddr *)&sockname, &namelen); + + if (p->to_log) { + /* we have the client ip */ + if (s->logs.logwait & LW_CLIP) + if (!(s->logs.logwait &= ~LW_CLIP)) + sess_log(s); + } + else if (s->cli_addr.ss_family == AF_INET) { + char pn[INET_ADDRSTRLEN], sn[INET_ADDRSTRLEN]; + if (inet_ntop(AF_INET, (const void *)&((struct sockaddr_in *)&sockname)->sin_addr, + sn, sizeof(sn)) && + inet_ntop(AF_INET, (const void *)&((struct sockaddr_in *)&s->cli_addr)->sin_addr, + pn, sizeof(pn))) { + send_log(p, LOG_INFO, "Connect from %s:%d to %s:%d (%s/%s)\n", + pn, ntohs(((struct sockaddr_in *)&s->cli_addr)->sin_port), + sn, ntohs(((struct sockaddr_in *)&sockname)->sin_port), + p->id, (p->mode == PR_MODE_HTTP) ? "HTTP" : "TCP"); + } + } + else { + char pn[INET6_ADDRSTRLEN], sn[INET6_ADDRSTRLEN]; + if (inet_ntop(AF_INET6, (const void *)&((struct sockaddr_in6 *)&sockname)->sin6_addr, + sn, sizeof(sn)) && + inet_ntop(AF_INET6, (const void *)&((struct sockaddr_in6 *)&s->cli_addr)->sin6_addr, + pn, sizeof(pn))) { + send_log(p, LOG_INFO, "Connect from %s:%d to %s:%d (%s/%s)\n", + pn, ntohs(((struct sockaddr_in6 *)&s->cli_addr)->sin6_port), + sn, ntohs(((struct sockaddr_in6 *)&sockname)->sin6_port), + p->id, (p->mode == PR_MODE_HTTP) ? "HTTP" : "TCP"); + } + } + } + + if ((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) { + struct sockaddr_in sockname; + socklen_t namelen = sizeof(sockname); + int len; + if (addr.ss_family != AF_INET || + !(s->proxy->options & PR_O_TRANSP) || + get_original_dst(cfd, (struct sockaddr_in *)&sockname, &namelen) == -1) + getsockname(cfd, (struct sockaddr *)&sockname, &namelen); + + if (s->cli_addr.ss_family == AF_INET) { + char pn[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, + (const void *)&((struct sockaddr_in *)&s->cli_addr)->sin_addr, + pn, sizeof(pn)); + + len = sprintf(trash, "%08x:%s.accept(%04x)=%04x from [%s:%d]\n", + s->uniq_id, p->id, (unsigned short)fd, (unsigned short)cfd, + pn, ntohs(((struct sockaddr_in *)&s->cli_addr)->sin_port)); + } + else { + char pn[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, + (const void *)&((struct sockaddr_in6 *)(&s->cli_addr))->sin6_addr, + pn, sizeof(pn)); + + len = sprintf(trash, "%08x:%s.accept(%04x)=%04x from [%s:%d]\n", + s->uniq_id, p->id, (unsigned short)fd, (unsigned short)cfd, + pn, ntohs(((struct sockaddr_in6 *)(&s->cli_addr))->sin6_port)); + } + + write(1, trash, len); + } + + if ((s->req = pool_alloc(buffer)) == NULL) { /* no memory */ + if (s->rsp_cap != NULL) + pool_free_to(p->rsp_cap_pool, s->rsp_cap); + if (s->req_cap != NULL) + pool_free_to(p->req_cap_pool, s->req_cap); + close(cfd); /* nothing can be done for this fd without memory */ + pool_free(task, t); + pool_free(session, s); + return 0; + } + + s->req->l = 0; + s->req->total = 0; + s->req->h = s->req->r = s->req->lr = s->req->w = s->req->data; /* r and w will be reset further */ + s->req->rlim = s->req->data + BUFSIZE; + if (s->cli_state == CL_STHEADERS) /* reserve some space for header rewriting */ + s->req->rlim -= MAXREWRITE; + + if ((s->rep = pool_alloc(buffer)) == NULL) { /* no memory */ + pool_free(buffer, s->req); + if (s->rsp_cap != NULL) + pool_free_to(p->rsp_cap_pool, s->rsp_cap); + if (s->req_cap != NULL) + pool_free_to(p->req_cap_pool, s->req_cap); + close(cfd); /* nothing can be done for this fd without memory */ + pool_free(task, t); + pool_free(session, s); + return 0; + } + s->rep->l = 0; + s->rep->total = 0; + s->rep->h = s->rep->r = s->rep->lr = s->rep->w = s->rep->rlim = s->rep->data; + + fdtab[cfd].read = &event_cli_read; + fdtab[cfd].write = &event_cli_write; + fdtab[cfd].owner = t; + fdtab[cfd].state = FD_STREADY; + + if ((p->mode == PR_MODE_HTTP && (s->flags & SN_MONITOR)) || + (p->mode == PR_MODE_HEALTH && (p->options & PR_O_HTTP_CHK))) + /* Either we got a request from a monitoring system on an HTTP instance, + * or we're in health check mode with the 'httpchk' option enabled. In + * both cases, we return a fake "HTTP/1.0 200 OK" response and we exit. + */ + client_retnclose(s, 19, "HTTP/1.0 200 OK\r\n\r\n"); /* forge a 200 response */ + else if (p->mode == PR_MODE_HEALTH) { /* health check mode, no client reading */ + client_retnclose(s, 3, "OK\n"); /* forge an "OK" response */ + } + else { + FD_SET(cfd, StaticReadEvent); + } + +#if defined(DEBUG_FULL) && defined(ENABLE_EPOLL) + if (PrevReadEvent) { + assert(!(FD_ISSET(cfd, PrevReadEvent))); + assert(!(FD_ISSET(cfd, PrevWriteEvent))); + } +#endif + fd_insert(cfd); + + tv_eternity(&s->cnexpire); + tv_eternity(&s->srexpire); + tv_eternity(&s->swexpire); + tv_eternity(&s->crexpire); + tv_eternity(&s->cwexpire); + + if (s->proxy->clitimeout) { + if (FD_ISSET(cfd, StaticReadEvent)) + tv_delayfrom(&s->crexpire, &now, s->proxy->clitimeout); + if (FD_ISSET(cfd, StaticWriteEvent)) + tv_delayfrom(&s->cwexpire, &now, s->proxy->clitimeout); + } + + tv_min(&t->expire, &s->crexpire, &s->cwexpire); + + task_queue(t); + + if (p->mode != PR_MODE_HEALTH) + task_wakeup(&rq, t); + + p->nbconn++; + if (p->nbconn > p->nbconn_max) + p->nbconn_max = p->nbconn; + actconn++; + totalconn++; + + // fprintf(stderr, "accepting from %p => %d conn, %d total, task=%p\n", p, actconn, totalconn, t); + } /* end of while (p->nbconn < p->maxconn) */ + return 0; +} + + + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/fd.c b/src/fd.c new file mode 100644 index 000000000..b7ff8add7 --- /dev/null +++ b/src/fd.c @@ -0,0 +1,503 @@ +/* + * File descriptors management functions. + * + * Copyright 2000-2006 Willy Tarreau + * + * 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. + * + */ + +/* + * FIXME: + * - we still use 'listeners' to check whether we want to stop or not. + * - the various pollers should be moved to other external files, possibly + * dynamic libs. + * - merge event_cli_read() and event_srv_read(). The difference is res_*, + * buffer (at the beginning) and timeouts (at the end). + * => event_tcp_read(). It may be called from event_accept(). + * - extract the connect code from event_srv_write() + * => event_tcp_connect(). It must then call event_write(). + * - merge the remaining event_cli_write() and event_srv_write() + * => single event_tcp_write(). Check buffer, fd_state, res*, and timeouts. + * + */ + +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include + +struct fdtab *fdtab = NULL; /* array of all the file descriptors */ +int maxfd; /* # of the highest fd + 1 */ +int totalconn; /* total # of terminated sessions */ +int actconn; /* # of active sessions */ + +fd_set *StaticReadEvent, *StaticWriteEvent; +int cfg_polling_mechanism = 0; /* POLL_USE_{SELECT|POLL|EPOLL} */ + + +/****************************** + * pollers + ******************************/ + + +/* + * FIXME: this is dirty, but at the moment, there's no other solution to remove + * the old FDs from outside the loop. Perhaps we should export a global 'poll' + * structure with pointers to functions such as init_fd() and close_fd(), plus + * a private structure with several pointers to places such as below. + */ + +#if defined(ENABLE_EPOLL) +fd_set *PrevReadEvent = NULL, *PrevWriteEvent = NULL; + +#if defined(USE_MY_EPOLL) +_syscall1 (int, epoll_create, int, size); +_syscall4 (int, epoll_ctl, int, epfd, int, op, int, fd, struct epoll_event *, event); +_syscall4 (int, epoll_wait, int, epfd, struct epoll_event *, events, int, maxevents, int, timeout); +#endif + +/* + * Main epoll() loop. + * does 3 actions : + * 0 (POLL_LOOP_ACTION_INIT) : initializes necessary private structures + * 1 (POLL_LOOP_ACTION_RUN) : runs the loop + * 2 (POLL_LOOP_ACTION_CLEAN) : cleans up + * + * returns 0 if initialization failed, !0 otherwise. + */ + +int epoll_loop(int action) +{ + int next_time; + int status; + int fd; + + int fds, count; + int pr, pw, sr, sw; + unsigned rn, ro, wn, wo; /* read new, read old, write new, write old */ + struct epoll_event ev; + + /* private data */ + static struct epoll_event *epoll_events = NULL; + static int epoll_fd; + + if (action == POLL_LOOP_ACTION_INIT) { + epoll_fd = epoll_create(global.maxsock + 1); + if (epoll_fd < 0) + return 0; + else { + epoll_events = (struct epoll_event*) + calloc(1, sizeof(struct epoll_event) * global.maxsock); + PrevReadEvent = (fd_set *) + calloc(1, sizeof(fd_set) * (global.maxsock + FD_SETSIZE - 1) / FD_SETSIZE); + PrevWriteEvent = (fd_set *) + calloc(1, sizeof(fd_set) * (global.maxsock + FD_SETSIZE - 1) / FD_SETSIZE); + } + return 1; + } + else if (action == POLL_LOOP_ACTION_CLEAN) { + if (PrevWriteEvent) free(PrevWriteEvent); + if (PrevReadEvent) free(PrevReadEvent); + if (epoll_events) free(epoll_events); + close(epoll_fd); + epoll_fd = 0; + return 1; + } + + /* OK, it's POLL_LOOP_ACTION_RUN */ + + tv_now(&now); + + while (1) { + next_time = process_runnable_tasks(); + + /* stop when there's no connection left and we don't allow them anymore */ + if (!actconn && listeners == 0) + break; + + for (fds = 0; (fds << INTBITS) < maxfd; fds++) { + + rn = ((int*)StaticReadEvent)[fds]; ro = ((int*)PrevReadEvent)[fds]; + wn = ((int*)StaticWriteEvent)[fds]; wo = ((int*)PrevWriteEvent)[fds]; + + if ((ro^rn) | (wo^wn)) { + for (count = 0, fd = fds << INTBITS; count < (1<> count) & 1; + pw = (wo >> count) & 1; + sr = (rn >> count) & 1; + sw = (wn >> count) & 1; +#else + pr = FD_ISSET(fd&((1<> count) & 1; + sw = (wn >> count) & 1; +#else + sr = FD_ISSET(fd&((1< 0 && count < nbfd; count++) { + fd = poll_events[count].fd; + + if (!(poll_events[count].revents & ( POLLOUT | POLLIN | POLLERR | POLLHUP ))) + continue; + + /* ok, we found one active fd */ + status--; + + if (FD_ISSET(fd, StaticReadEvent)) { + if (fdtab[fd].state == FD_STCLOSE) + continue; + if (poll_events[count].revents & ( POLLIN | POLLERR | POLLHUP )) + fdtab[fd].read(fd); + } + + if (FD_ISSET(fd, StaticWriteEvent)) { + if (fdtab[fd].state == FD_STCLOSE) + continue; + if (poll_events[count].revents & ( POLLOUT | POLLERR | POLLHUP )) + fdtab[fd].write(fd); + } + } + } + return 1; +} +#endif + + + +/* + * Main select() loop. + * does 3 actions : + * 0 (POLL_LOOP_ACTION_INIT) : initializes necessary private structures + * 1 (POLL_LOOP_ACTION_RUN) : runs the loop + * 2 (POLL_LOOP_ACTION_CLEAN) : cleans up + * + * returns 0 if initialization failed, !0 otherwise. + */ + + +int select_loop(int action) +{ + int next_time; + int status; + int fd,i; + struct timeval delta; + int readnotnull, writenotnull; + static fd_set *ReadEvent = NULL, *WriteEvent = NULL; + + if (action == POLL_LOOP_ACTION_INIT) { + ReadEvent = (fd_set *) + calloc(1, sizeof(fd_set) * (global.maxsock + FD_SETSIZE - 1) / FD_SETSIZE); + WriteEvent = (fd_set *) + calloc(1, sizeof(fd_set) * (global.maxsock + FD_SETSIZE - 1) / FD_SETSIZE); + return 1; + } + else if (action == POLL_LOOP_ACTION_CLEAN) { + if (WriteEvent) free(WriteEvent); + if (ReadEvent) free(ReadEvent); + return 1; + } + + /* OK, it's POLL_LOOP_ACTION_RUN */ + + tv_now(&now); + + while (1) { + next_time = process_runnable_tasks(); + + /* stop when there's no connection left and we don't allow them anymore */ + if (!actconn && listeners == 0) + break; + + if (next_time > 0) { /* FIXME */ + /* Convert to timeval */ + /* to avoid eventual select loops due to timer precision */ + next_time += SCHEDULER_RESOLUTION; + delta.tv_sec = next_time / 1000; + delta.tv_usec = (next_time % 1000) * 1000; + } + else if (next_time == 0) { /* allow select to return immediately when needed */ + delta.tv_sec = delta.tv_usec = 0; + } + + + /* let's restore fdset state */ + + readnotnull = 0; writenotnull = 0; + for (i = 0; i < (maxfd + FD_SETSIZE - 1)/(8*sizeof(int)); i++) { + readnotnull |= (*(((int*)ReadEvent)+i) = *(((int*)StaticReadEvent)+i)) != 0; + writenotnull |= (*(((int*)WriteEvent)+i) = *(((int*)StaticWriteEvent)+i)) != 0; + } + + // /* just a verification code, needs to be removed for performance */ + // for (i=0; i= 0) ? &delta : NULL); + + /* this is an experiment on the separation of the select work */ + // status = (readnotnull ? select(maxfd, ReadEvent, NULL, NULL, (next_time >= 0) ? &delta : NULL) : 0); + // status |= (writenotnull ? select(maxfd, NULL, WriteEvent, NULL, (next_time >= 0) ? &delta : NULL) : 0); + + tv_now(&now); + + if (status > 0) { /* must proceed with events */ + + int fds; + char count; + + for (fds = 0; (fds << INTBITS) < maxfd; fds++) + if ((((int *)(ReadEvent))[fds] | ((int *)(WriteEvent))[fds]) != 0) + for (count = 1<= 0) && (fdtab[maxfd-1].state == FD_STCLOSE)) + maxfd--; +} + + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/haproxy.c b/src/haproxy.c new file mode 100644 index 000000000..e339a7b04 --- /dev/null +++ b/src/haproxy.c @@ -0,0 +1,869 @@ +/* + * HA-Proxy : High Availability-enabled HTTP/TCP proxy + * Copyright 2000-2006 Willy Tarreau . + * + * 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. + * + * Please refer to RFC2068 or RFC2616 for informations about HTTP protocol, and + * RFC2965 for informations about cookies usage. More generally, the IETF HTTP + * Working Group's web site should be consulted for protocol related changes : + * + * http://ftp.ics.uci.edu/pub/ietf/http/ + * + * Pending bugs (may be not fixed because never reproduced) : + * - solaris only : sometimes, an HTTP proxy with only a dispatch address causes + * the proxy to terminate (no core) if the client breaks the connection during + * the response. Seen on 1.1.8pre4, but never reproduced. May not be related to + * the snprintf() bug since requests were simple (GET / HTTP/1.0), but may be + * related to missing setsid() (fixed in 1.1.15) + * - a proxy with an invalid config will prevent the startup even if disabled. + * + * ChangeLog has moved to the CHANGELOG file. + * + * TODO: + * - handle properly intermediate incomplete server headers. Done ? + * - handle hot-reconfiguration + * - fix client/server state transition when server is in connect or headers state + * and client suddenly disconnects. The server *should* switch to SHUT_WR, but + * still handle HTTP headers. + * - remove MAX_NEWHDR + * - cut this huge file into several ones + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef DEBUG_FULL +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*********************************************************************/ + +/*********************************************************************/ + +char *cfg_cfgfile = NULL; /* configuration file */ +char *progname = NULL; /* program name */ +int pid; /* current process id */ + +/* global options */ +struct global global = { + logfac1 : -1, + logfac2 : -1, + loglev1 : 7, /* max syslog level : debug */ + loglev2 : 7, + /* others NULL OK */ +}; + +/*********************************************************************/ + +int stopping; /* non zero means stopping in progress */ + +/* Here we store informations about the pids of the processes we may pause + * or kill. We will send them a signal every 10 ms until we can bind to all + * our ports. With 200 retries, that's about 2 seconds. + */ +#define MAX_START_RETRIES 200 +static int nb_oldpids = 0; +static int *oldpids = NULL; +static int oldpids_sig; /* use USR1 or TERM */ + +/* this is used to drain data, and as a temporary buffer for sprintf()... */ +char trash[BUFSIZE]; + +const int zero = 0; +const int one = 1; + +/* + * Syslog facilities and levels. Conforming to RFC3164. + */ + +#define MAX_HOSTNAME_LEN 32 +static char hostname[MAX_HOSTNAME_LEN] = ""; + + +/*********************************************************************/ +/* general purpose functions ***************************************/ +/*********************************************************************/ + +void display_version() +{ + printf("HA-Proxy version " HAPROXY_VERSION " " HAPROXY_DATE"\n"); + printf("Copyright 2000-2006 Willy Tarreau \n\n"); +} + +/* + * This function prints the command line usage and exits + */ +void usage(char *name) +{ + display_version(); + fprintf(stderr, + "Usage : %s -f [ -vdV" + "D ] [ -n ] [ -N ]\n" + " [ -p ] [ -m ]\n" + " -v displays version\n" + " -d enters debug mode ; -db only disables background mode.\n" + " -V enters verbose mode (disables quiet mode)\n" + " -D goes daemon ; implies -q\n" + " -q quiet mode : don't display messages\n" + " -c check mode : only check config file and exit\n" + " -n sets the maximum total # of connections (%d)\n" + " -m limits the usable amount of memory (in MB)\n" + " -N sets the default, per-proxy maximum # of connections (%d)\n" + " -p writes pids of all children to this file\n" +#if defined(ENABLE_EPOLL) + " -de disables epoll() usage even when available\n" +#endif +#if defined(ENABLE_POLL) + " -dp disables poll() usage even when available\n" +#endif + " -sf/-st [pid ]* finishes/terminates old pids. Must be last arguments.\n" + "\n", + name, DEFAULT_MAXCONN, cfg_maxpconn); + exit(1); +} + + + +/*********************************************************************/ +/* more specific functions ***************************************/ +/*********************************************************************/ + +/* + * upon SIGUSR1, let's have a soft stop. + */ +void sig_soft_stop(int sig) +{ + soft_stop(); + signal(sig, SIG_IGN); +} + +/* + * upon SIGTTOU, we pause everything + */ +void sig_pause(int sig) +{ + pause_proxies(); + signal(sig, sig_pause); +} + +/* + * upon SIGTTIN, let's have a soft stop. + */ +void sig_listen(int sig) +{ + listen_proxies(); + signal(sig, sig_listen); +} + +/* + * this function dumps every server's state when the process receives SIGHUP. + */ +void sig_dump_state(int sig) +{ + struct proxy *p = proxy; + + Warning("SIGHUP received, dumping servers states.\n"); + while (p) { + struct server *s = p->srv; + + send_log(p, LOG_NOTICE, "SIGHUP received, dumping servers states for proxy %s.\n", p->id); + while (s) { + snprintf(trash, sizeof(trash), + "SIGHUP: Server %s/%s is %s. Conn: %d act, %d pend, %d tot.", + p->id, s->id, + (s->state & SRV_RUNNING) ? "UP" : "DOWN", + s->cur_sess, s->nbpend, s->cum_sess); + Warning("%s\n", trash); + send_log(p, LOG_NOTICE, "%s\n", trash); + s = s->next; + } + + if (p->srv_act == 0) { + snprintf(trash, sizeof(trash), + "SIGHUP: Proxy %s %s ! Conn: %d act, %d pend (%d unass), %d tot.", + p->id, + (p->srv_bck) ? "is running on backup servers" : "has no server available", + p->nbconn, p->totpend, p->nbpend, p->cum_conn); + } else { + snprintf(trash, sizeof(trash), + "SIGHUP: Proxy %s has %d active servers and %d backup servers available." + " Conn: %d act, %d pend (%d unass), %d tot.", + p->id, p->srv_act, p->srv_bck, + p->nbconn, p->totpend, p->nbpend, p->cum_conn); + } + Warning("%s\n", trash); + send_log(p, LOG_NOTICE, "%s\n", trash); + + p = p->next; + } + signal(sig, sig_dump_state); +} + +void dump(int sig) +{ + struct task *t, *tnext; + struct session *s; + + tnext = ((struct task *)LIST_HEAD(wait_queue[0]))->next; + while ((t = tnext) != LIST_HEAD(wait_queue[0])) { /* we haven't looped ? */ + tnext = t->next; + s = t->context; + qfprintf(stderr,"[dump] wq: task %p, still %ld ms, " + "cli=%d, srv=%d, cr=%d, cw=%d, sr=%d, sw=%d, " + "req=%d, rep=%d, clifd=%d\n", + s, tv_remain(&now, &t->expire), + s->cli_state, + s->srv_state, + FD_ISSET(s->cli_fd, StaticReadEvent), + FD_ISSET(s->cli_fd, StaticWriteEvent), + FD_ISSET(s->srv_fd, StaticReadEvent), + FD_ISSET(s->srv_fd, StaticWriteEvent), + s->req->l, s->rep?s->rep->l:0, s->cli_fd + ); + } +} + +#ifdef DEBUG_MEMORY +static void fast_stop(void) +{ + struct proxy *p; + p = proxy; + while (p) { + p->grace = 0; + p = p->next; + } + soft_stop(); +} + +void sig_int(int sig) +{ + /* This would normally be a hard stop, + but we want to be sure about deallocation, + and so on, so we do a soft stop with + 0 GRACE time + */ + fast_stop(); + /* If we are killed twice, we decide to die*/ + signal(sig, SIG_DFL); +} + +void sig_term(int sig) +{ + /* This would normally be a hard stop, + but we want to be sure about deallocation, + and so on, so we do a soft stop with + 0 GRACE time + */ + fast_stop(); + /* If we are killed twice, we decide to die*/ + signal(sig, SIG_DFL); +} +#endif + + +/* + * This function initializes all the necessary variables. It only returns + * if everything is OK. If something fails, it exits. + */ +void init(int argc, char **argv) +{ + int i; + int arg_mode = 0; /* MODE_DEBUG, ... */ + char *old_argv = *argv; + char *tmp; + char *cfg_pidfile = NULL; + + if (1< 0) { + char *flag; + + if (**argv == '-') { + flag = *argv+1; + + /* 1 arg */ + if (*flag == 'v') { + display_version(); + exit(0); + } +#if defined(ENABLE_EPOLL) + else if (*flag == 'd' && flag[1] == 'e') + cfg_polling_mechanism &= ~POLL_USE_EPOLL; +#endif +#if defined(ENABLE_POLL) + else if (*flag == 'd' && flag[1] == 'p') + cfg_polling_mechanism &= ~POLL_USE_POLL; +#endif + else if (*flag == 'V') + arg_mode |= MODE_VERBOSE; + else if (*flag == 'd' && flag[1] == 'b') + arg_mode |= MODE_FOREGROUND; + else if (*flag == 'd') + arg_mode |= MODE_DEBUG; + else if (*flag == 'c') + arg_mode |= MODE_CHECK; + else if (*flag == 'D') + arg_mode |= MODE_DAEMON | MODE_QUIET; + else if (*flag == 'q') + arg_mode |= MODE_QUIET; + else if (*flag == 's' && (flag[1] == 'f' || flag[1] == 't')) { + /* list of pids to finish ('f') or terminate ('t') */ + + if (flag[1] == 'f') + oldpids_sig = SIGUSR1; /* finish then exit */ + else + oldpids_sig = SIGTERM; /* terminate immediately */ + argv++; argc--; + + if (argc > 0) { + oldpids = calloc(argc, sizeof(int)); + while (argc > 0) { + oldpids[nb_oldpids] = atol(*argv); + if (oldpids[nb_oldpids] <= 0) + usage(old_argv); + argc--; argv++; + nb_oldpids++; + } + } + } + else { /* >=2 args */ + argv++; argc--; + if (argc == 0) + usage(old_argv); + + switch (*flag) { + case 'n' : cfg_maxconn = atol(*argv); break; + case 'm' : global.rlimit_memmax = atol(*argv); break; + case 'N' : cfg_maxpconn = atol(*argv); break; + case 'f' : cfg_cfgfile = *argv; break; + case 'p' : cfg_pidfile = *argv; break; + default: usage(old_argv); + } + } + } + else + usage(old_argv); + argv++; argc--; + } + + global.mode = MODE_STARTING | /* during startup, we want most of the alerts */ + (arg_mode & (MODE_DAEMON | MODE_FOREGROUND | MODE_VERBOSE + | MODE_QUIET | MODE_CHECK | MODE_DEBUG)); + + if (!cfg_cfgfile) + usage(old_argv); + + gethostname(hostname, MAX_HOSTNAME_LEN); + + have_appsession = 0; + global.maxsock = 10; /* reserve 10 fds ; will be incremented by socket eaters */ + if (readcfgfile(cfg_cfgfile) < 0) { + Alert("Error reading configuration file : %s\n", cfg_cfgfile); + exit(1); + } + if (have_appsession) + appsession_init(); + + if (global.mode & MODE_CHECK) { + qfprintf(stdout, "Configuration file is valid : %s\n", cfg_cfgfile); + exit(0); + } + + if (cfg_maxconn > 0) + global.maxconn = cfg_maxconn; + + if (cfg_pidfile) { + if (global.pidfile) + free(global.pidfile); + global.pidfile = strdup(cfg_pidfile); + } + + if (global.maxconn == 0) + global.maxconn = DEFAULT_MAXCONN; + + global.maxsock += global.maxconn * 2; /* each connection needs two sockets */ + + if (arg_mode & (MODE_DEBUG | MODE_FOREGROUND)) { + /* command line debug mode inhibits configuration mode */ + global.mode &= ~(MODE_DAEMON | MODE_QUIET); + } + global.mode |= (arg_mode & (MODE_DAEMON | MODE_FOREGROUND | MODE_QUIET | + MODE_VERBOSE | MODE_DEBUG | MODE_STATS | MODE_LOG)); + + if ((global.mode & MODE_DEBUG) && (global.mode & (MODE_DAEMON | MODE_QUIET))) { + Warning(" mode incompatible with and . Keeping only.\n"); + global.mode &= ~(MODE_DAEMON | MODE_QUIET); + } + + if ((global.nbproc > 1) && !(global.mode & MODE_DAEMON)) { + if (!(global.mode & (MODE_FOREGROUND | MODE_DEBUG))) + Warning(" is only meaningful in daemon mode. Setting limit to 1 process.\n"); + global.nbproc = 1; + } + + if (global.nbproc < 1) + global.nbproc = 1; + + StaticReadEvent = (fd_set *)calloc(1, + sizeof(fd_set) * + (global.maxsock + FD_SETSIZE - 1) / FD_SETSIZE); + StaticWriteEvent = (fd_set *)calloc(1, + sizeof(fd_set) * + (global.maxsock + FD_SETSIZE - 1) / FD_SETSIZE); + + fdtab = (struct fdtab *)calloc(1, + sizeof(struct fdtab) * (global.maxsock)); + for (i = 0; i < global.maxsock; i++) { + fdtab[i].state = FD_STCLOSE; + } +} + +void deinit(void) +{ + struct proxy *p = proxy; + struct cap_hdr *h,*h_next; + struct server *s,*s_next; + struct listener *l,*l_next; + + while (p) { + if (p->id) + free(p->id); + + if (p->check_req) + free(p->check_req); + + if (p->cookie_name) + free(p->cookie_name); + + if (p->capture_name) + free(p->capture_name); + + /* only strup if the user have set in config. + When should we free it?! + if (p->errmsg.msg400) free(p->errmsg.msg400); + if (p->errmsg.msg403) free(p->errmsg.msg403); + if (p->errmsg.msg408) free(p->errmsg.msg408); + if (p->errmsg.msg500) free(p->errmsg.msg500); + if (p->errmsg.msg502) free(p->errmsg.msg502); + if (p->errmsg.msg503) free(p->errmsg.msg503); + if (p->errmsg.msg504) free(p->errmsg.msg504); + */ + if (p->appsession_name) + free(p->appsession_name); + + h = p->req_cap; + while (h) { + h_next = h->next; + if (h->name) + free(h->name); + pool_destroy(h->pool); + free(h); + h = h_next; + }/* end while(h) */ + + h = p->rsp_cap; + while (h) { + h_next = h->next; + if (h->name) + free(h->name); + + pool_destroy(h->pool); + free(h); + h = h_next; + }/* end while(h) */ + + s = p->srv; + while (s) { + s_next = s->next; + if (s->id) + free(s->id); + + if (s->cookie) + free(s->cookie); + + free(s); + s = s_next; + }/* end while(s) */ + + l = p->listen; + while (l) { + l_next = l->next; + free(l); + l = l_next; + }/* end while(l) */ + + pool_destroy((void **) p->req_cap_pool); + pool_destroy((void **) p->rsp_cap_pool); + p = p->next; + }/* end while(p) */ + + if (global.chroot) free(global.chroot); + if (global.pidfile) free(global.pidfile); + + if (StaticReadEvent) free(StaticReadEvent); + if (StaticWriteEvent) free(StaticWriteEvent); + if (fdtab) free(fdtab); + + pool_destroy(pool_session); + pool_destroy(pool_buffer); + pool_destroy(pool_requri); + pool_destroy(pool_task); + pool_destroy(pool_capture); + pool_destroy(pool_appsess); + + if (have_appsession) { + pool_destroy(apools.serverid); + pool_destroy(apools.sessid); + } +} /* end deinit() */ + +/* sends the signal to all pids found in */ +static void tell_old_pids(int sig) +{ + int p; + for (p = 0; p < nb_oldpids; p++) + kill(oldpids[p], sig); +} + +int main(int argc, char **argv) +{ + int err, retry; + struct rlimit limit; + FILE *pidfile = NULL; + init(argc, argv); + + signal(SIGQUIT, dump); + signal(SIGUSR1, sig_soft_stop); + signal(SIGHUP, sig_dump_state); +#ifdef DEBUG_MEMORY + signal(SIGINT, sig_int); + signal(SIGTERM, sig_term); +#endif + + /* on very high loads, a sigpipe sometimes happen just between the + * getsockopt() which tells "it's OK to write", and the following write :-( + */ +#ifndef MSG_NOSIGNAL + signal(SIGPIPE, SIG_IGN); +#endif + + /* We will loop at most 100 times with 10 ms delay each time. + * That's at most 1 second. We only send a signal to old pids + * if we cannot grab at least one port. + */ + retry = MAX_START_RETRIES; + err = ERR_NONE; + while (retry >= 0) { + struct timeval w; + err = start_proxies(retry == 0 || nb_oldpids == 0); + if (err != ERR_RETRYABLE) + break; + if (nb_oldpids == 0) + break; + + /* FIXME-20060514: Solaris and OpenBSD do not support shutdown() on + * listening sockets. So on those platforms, it would be wiser to + * simply send SIGUSR1, which will not be undoable. + */ + tell_old_pids(SIGTTOU); + /* give some time to old processes to stop listening */ + w.tv_sec = 0; + w.tv_usec = 10*1000; + select(0, NULL, NULL, NULL, &w); + retry--; + } + + /* Note: start_proxies() sends an alert when it fails. */ + if (err != ERR_NONE) { + if (retry != MAX_START_RETRIES && nb_oldpids) + tell_old_pids(SIGTTIN); + exit(1); + } + + if (listeners == 0) { + Alert("[%s.main()] No enabled listener found (check the keywords) ! Exiting.\n", argv[0]); + /* Note: we don't have to send anything to the old pids because we + * never stopped them. */ + exit(1); + } + + /* prepare pause/play signals */ + signal(SIGTTOU, sig_pause); + signal(SIGTTIN, sig_listen); + + if (global.mode & MODE_DAEMON) { + global.mode &= ~MODE_VERBOSE; + global.mode |= MODE_QUIET; + } + + /* MODE_QUIET can inhibit alerts and warnings below this line */ + + global.mode &= ~MODE_STARTING; + if ((global.mode & MODE_QUIET) && !(global.mode & MODE_VERBOSE)) { + /* detach from the tty */ + fclose(stdin); fclose(stdout); fclose(stderr); + close(0); close(1); close(2); + } + + /* open log & pid files before the chroot */ + if (global.mode & MODE_DAEMON && global.pidfile != NULL) { + int pidfd; + unlink(global.pidfile); + pidfd = open(global.pidfile, O_CREAT | O_WRONLY | O_TRUNC, 0644); + if (pidfd < 0) { + Alert("[%s.main()] Cannot create pidfile %s\n", argv[0], global.pidfile); + if (nb_oldpids) + tell_old_pids(SIGTTIN); + exit(1); + } + pidfile = fdopen(pidfd, "w"); + } + + /* chroot if needed */ + if (global.chroot != NULL) { + if (chroot(global.chroot) == -1) { + Alert("[%s.main()] Cannot chroot(%s).\n", argv[0], global.chroot); + if (nb_oldpids) + tell_old_pids(SIGTTIN); + } + chdir("/"); + } + + /* ulimits */ + if (!global.rlimit_nofile) + global.rlimit_nofile = global.maxsock; + + if (global.rlimit_nofile) { + limit.rlim_cur = limit.rlim_max = global.rlimit_nofile; + if (setrlimit(RLIMIT_NOFILE, &limit) == -1) { + Warning("[%s.main()] Cannot raise FD limit to %d.\n", argv[0], global.rlimit_nofile); + } + } + + if (global.rlimit_memmax) { + limit.rlim_cur = limit.rlim_max = + global.rlimit_memmax * 1048576 / global.nbproc; +#ifdef RLIMIT_AS + if (setrlimit(RLIMIT_AS, &limit) == -1) { + Warning("[%s.main()] Cannot fix MEM limit to %d megs.\n", + argv[0], global.rlimit_memmax); + } +#else + if (setrlimit(RLIMIT_DATA, &limit) == -1) { + Warning("[%s.main()] Cannot fix MEM limit to %d megs.\n", + argv[0], global.rlimit_memmax); + } +#endif + } + + if (nb_oldpids) + tell_old_pids(oldpids_sig); + + /* Note that any error at this stage will be fatal because we will not + * be able to restart the old pids. + */ + + /* setgid / setuid */ + if (global.gid && setgid(global.gid) == -1) { + Alert("[%s.main()] Cannot set gid %d.\n", argv[0], global.gid); + exit(1); + } + + if (global.uid && setuid(global.uid) == -1) { + Alert("[%s.main()] Cannot set uid %d.\n", argv[0], global.uid); + exit(1); + } + + /* check ulimits */ + limit.rlim_cur = limit.rlim_max = 0; + getrlimit(RLIMIT_NOFILE, &limit); + if (limit.rlim_cur < global.maxsock) { + Warning("[%s.main()] FD limit (%d) too low for maxconn=%d/maxsock=%d. Please raise 'ulimit-n' to %d or more to avoid any trouble.\n", + argv[0], limit.rlim_cur, global.maxconn, global.maxsock, global.maxsock); + } + + if (global.mode & MODE_DAEMON) { + int ret = 0; + int proc; + + /* the father launches the required number of processes */ + for (proc = 0; proc < global.nbproc; proc++) { + ret = fork(); + if (ret < 0) { + Alert("[%s.main()] Cannot fork.\n", argv[0]); + if (nb_oldpids) + exit(1); /* there has been an error */ + } + else if (ret == 0) /* child breaks here */ + break; + if (pidfile != NULL) { + fprintf(pidfile, "%d\n", ret); + fflush(pidfile); + } + } + /* close the pidfile both in children and father */ + if (pidfile != NULL) + fclose(pidfile); + free(global.pidfile); + + if (proc == global.nbproc) + exit(0); /* parent must leave */ + + /* if we're NOT in QUIET mode, we should now close the 3 first FDs to ensure + * that we can detach from the TTY. We MUST NOT do it in other cases since + * it would have already be done, and 0-2 would have been affected to listening + * sockets + */ + if (!(global.mode & MODE_QUIET)) { + /* detach from the tty */ + fclose(stdin); fclose(stdout); fclose(stderr); + close(0); close(1); close(2); /* close all fd's */ + global.mode |= MODE_QUIET; /* ensure that we won't say anything from now */ + } + pid = getpid(); /* update child's pid */ + setsid(); + } + +#if defined(ENABLE_EPOLL) + if (cfg_polling_mechanism & POLL_USE_EPOLL) { + if (epoll_loop(POLL_LOOP_ACTION_INIT)) { + epoll_loop(POLL_LOOP_ACTION_RUN); + epoll_loop(POLL_LOOP_ACTION_CLEAN); + cfg_polling_mechanism &= POLL_USE_EPOLL; + } + else { + Warning("epoll() is not available. Using poll()/select() instead.\n"); + cfg_polling_mechanism &= ~POLL_USE_EPOLL; + } + } +#endif + +#if defined(ENABLE_POLL) + if (cfg_polling_mechanism & POLL_USE_POLL) { + if (poll_loop(POLL_LOOP_ACTION_INIT)) { + poll_loop(POLL_LOOP_ACTION_RUN); + poll_loop(POLL_LOOP_ACTION_CLEAN); + cfg_polling_mechanism &= POLL_USE_POLL; + } + else { + Warning("poll() is not available. Using select() instead.\n"); + cfg_polling_mechanism &= ~POLL_USE_POLL; + } + } +#endif + if (cfg_polling_mechanism & POLL_USE_SELECT) { + if (select_loop(POLL_LOOP_ACTION_INIT)) { + select_loop(POLL_LOOP_ACTION_RUN); + select_loop(POLL_LOOP_ACTION_CLEAN); + cfg_polling_mechanism &= POLL_USE_SELECT; + } + } + + + /* Free all Hash Keys and all Hash elements */ + appsession_cleanup(); + /* Do some cleanup */ + deinit(); + + exit(0); +} + + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/hashpjw.c b/src/hashpjw.c index a207eb91d..78c0a5be7 100644 --- a/src/hashpjw.c +++ b/src/hashpjw.c @@ -16,7 +16,8 @@ * * *****************************************************************************/ -#include +#include +#include /***************************************************************************** * * @@ -26,37 +27,44 @@ int hashpjw(const void *key) { - const char *ptr; - unsigned int val; - appsess *appsession_temp; + const char *ptr; + unsigned int val; + appsess *appsession_temp; - /***************************************************************************** - * * - * Hash the key by performing a number of bit operations on it. * - * * - *****************************************************************************/ + /********************************************************************* + * * + * Hash the key by performing a number of bit operations on it. * + * * + *********************************************************************/ - val = 0; - appsession_temp = (appsess *)key; - ptr = appsession_temp->sessid; + val = 0; + appsession_temp = (appsess *)key; + ptr = appsession_temp->sessid; - while (*ptr != '\0') { + while (*ptr != '\0') { - int tmp; + int tmp; - val = (val << 4) + (*ptr); + val = (val << 4) + (*ptr); - if((tmp = (val & 0xf0000000))) { - val = val ^ (tmp >> 24); - val = val ^ tmp; - } - ptr++; - }/* end while */ + if((tmp = (val & 0xf0000000))) { + val = val ^ (tmp >> 24); + val = val ^ tmp; + } + ptr++; + }/* end while */ - /***************************************************************************** - * * - * In practice, replace PRIME_TBLSIZ with the actual table size. * - * * - *****************************************************************************/ - return val % PRIME_TBLSIZ; + /********************************************************************* + * * + * In practice, replace PRIME_TBLSIZ with the actual table size. * + * * + *********************************************************************/ + return val % PRIME_TBLSIZ; }/* end hashpjw */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/list.c b/src/list.c index 364ed141f..0eaf6ce32 100644 --- a/src/list.c +++ b/src/list.c @@ -18,7 +18,7 @@ #include #include -#include +#include /***************************************************************************** * * @@ -28,17 +28,17 @@ void list_init(List *list, void (*destroy)(void *data)) { - /***************************************************************************** - * * - * Initialize the list. * - * * - *****************************************************************************/ + /********************************************************************* + * * + * Initialize the list. * + * * + *********************************************************************/ - list->size = 0; - list->destroy = destroy; - list->head = NULL; - list->tail = NULL; - return; + list->size = 0; + list->destroy = destroy; + list->head = NULL; + list->tail = NULL; + return; } /* end list_init() */ /***************************************************************************** @@ -49,39 +49,39 @@ void list_init(List *list, void (*destroy)(void *data)) { void list_destroy(List *list) { - void *data; - int rc; + void *data; + int rc; - /***************************************************************************** - * * - * Remove each element. * - * * - *****************************************************************************/ + /********************************************************************* + * * + * Remove each element. * + * * + *********************************************************************/ - while (list_size(list) > 0) { + while (list_size(list) > 0) { - rc = list_rem_next(list, NULL, (void **)&data); + rc = list_rem_next(list, NULL, (void **)&data); - if (( rc == 0) && (list->destroy != NULL)) { + if (( rc == 0) && (list->destroy != NULL)) { - /*********************************************************************** - * * - * Call a user-defined function to free dynamically allocated data. * - * * - ***********************************************************************/ + /******************************************************************* + * * + * Call a user-defined function to free dynamically allocated data.* + * * + *******************************************************************/ - list->destroy(data); - }/* end if() */ - }/* end while() */ + list->destroy(data); + }/* end if() */ + }/* end while() */ - /***************************************************************************** - * * - * No operations are allowed now, but clear the structure as a precaution. * - * * - *****************************************************************************/ + /************************************************************************** + * * + * No operations are allowed now, but clear the structure as a precaution.* + * * + **************************************************************************/ - memset(list, 0, sizeof(List)); - return; + memset(list, 0, sizeof(List)); + return; } /* void list_destroy(List *list) */ /***************************************************************************** @@ -92,62 +92,62 @@ void list_destroy(List *list) { int list_ins_next(List *list, ListElmt *element, const void *data) { - ListElmt *new_element; + ListElmt *new_element; - /***************************************************************************** - * * - * Allocate storage for the element. * - * * - *****************************************************************************/ + /********************************************************************* + * * + * Allocate storage for the element. * + * * + *********************************************************************/ - if ((new_element = (ListElmt *)malloc(sizeof(ListElmt))) == NULL) - return -1; + if ((new_element = (ListElmt *)malloc(sizeof(ListElmt))) == NULL) + return -1; - /***************************************************************************** - * * - * Insert the element into the list. * - * * - *****************************************************************************/ + /********************************************************************* + * * + * Insert the element into the list. * + * * + *********************************************************************/ - new_element->data = (void *)data; + new_element->data = (void *)data; - if (element == NULL) { + if (element == NULL) { - /************************************************************************** - * * - * Handle insertion at the head of the list. * - * * - **************************************************************************/ + /************************************************************* + * * + * Handle insertion at the head of the list. * + * * + *************************************************************/ - if (list_size(list) == 0) - list->tail = new_element; + if (list_size(list) == 0) + list->tail = new_element; - new_element->next = list->head; - list->head = new_element; - }/* end if (element == NULL) */ - else { + new_element->next = list->head; + list->head = new_element; + }/* end if (element == NULL) */ + else { - /************************************************************************** - * * - * Handle insertion somewhere other than at the head. * - * * - **************************************************************************/ + /************************************************************* + * * + * Handle insertion somewhere other than at the head. * + * * + *************************************************************/ - if (element->next == NULL) - list->tail = new_element; + if (element->next == NULL) + list->tail = new_element; - new_element->next = element->next; - element->next = new_element; - }/* end else */ + new_element->next = element->next; + element->next = new_element; + }/* end else */ - /***************************************************************************** - * * - * Adjust the size of the list to account for the inserted element. * - * * - *****************************************************************************/ + /********************************************************************* + * * + * Adjust the size of the list to account for the inserted element. * + * * + *********************************************************************/ - list->size++; - return 0; + list->size++; + return 0; } /* end list_ins_next() */ /***************************************************************************** @@ -158,71 +158,78 @@ int list_ins_next(List *list, ListElmt *element, const void *data) { int list_rem_next(List *list, ListElmt *element, void **data) { - ListElmt *old_element; + ListElmt *old_element; - /***************************************************************************** - * * - * Do not allow removal from an empty list. * - * * - *****************************************************************************/ + /********************************************************************* + * * + * Do not allow removal from an empty list. * + * * + *********************************************************************/ - if (list_size(list) == 0) - return -1; + if (list_size(list) == 0) + return -1; - /***************************************************************************** - * * - * Remove the element from the list. * - * * - *****************************************************************************/ + /********************************************************************* + * * + * Remove the element from the list. * + * * + *********************************************************************/ - if (element == NULL) { + if (element == NULL) { - /************************************************************************** - * * - * Handle removal from the head of the list. * - * * - **************************************************************************/ + /************************************************************* + * * + * Handle removal from the head of the list. * + * * + *************************************************************/ - *data = list->head->data; - old_element = list->head; - list->head = list->head->next; + *data = list->head->data; + old_element = list->head; + list->head = list->head->next; - if (list_size(list) == 1) - list->tail = NULL; - }/* end if (element == NULL) */ - else { + if (list_size(list) == 1) + list->tail = NULL; + }/* end if (element == NULL) */ + else { - /************************************************************************** - * * - * Handle removal from somewhere other than the head. * - * * - **************************************************************************/ + /************************************************************* + * * + * Handle removal from somewhere other than the head. * + * * + *************************************************************/ - if (element->next == NULL) - return -1; + if (element->next == NULL) + return -1; - *data = element->next->data; - old_element = element->next; - element->next = element->next->next; + *data = element->next->data; + old_element = element->next; + element->next = element->next->next; - if (element->next == NULL) - list->tail = element; - }/* end else */ + if (element->next == NULL) + list->tail = element; + }/* end else */ - /***************************************************************************** - * * - * Free the storage allocated by the abstract data type. * - * * - *****************************************************************************/ + /********************************************************************* + * * + * Free the storage allocated by the abstract data type. * + * * + *********************************************************************/ - free(old_element); + free(old_element); - /***************************************************************************** - * * - * Adjust the size of the list to account for the removed element. * - * * - *****************************************************************************/ + /********************************************************************* + * * + * Adjust the size of the list to account for the removed element. * + * * + *********************************************************************/ - list->size--; - return 0; + list->size--; + return 0; } + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/log.c b/src/log.c new file mode 100644 index 000000000..d311ac888 --- /dev/null +++ b/src/log.c @@ -0,0 +1,459 @@ +/* + * General logging functions. + * + * Copyright 2000-2006 Willy Tarreau + * + * 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 +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include + + +const char *log_facilities[NB_LOG_FACILITIES] = { + "kern", "user", "mail", "daemon", + "auth", "syslog", "lpr", "news", + "uucp", "cron", "auth2", "ftp", + "ntp", "audit", "alert", "cron2", + "local0", "local1", "local2", "local3", + "local4", "local5", "local6", "local7" +}; + + +const char *log_levels[NB_LOG_LEVELS] = { + "emerg", "alert", "crit", "err", + "warning", "notice", "info", "debug" +}; + +const char *monthname[12] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +const char sess_term_cond[8] = "-cCsSPRI"; /* normal, CliTo, CliErr, SrvTo, SrvErr, PxErr, Resource, Internal */ +const char sess_fin_state[8] = "-RCHDLQ7"; /* cliRequest, srvConnect, srvHeader, Data, Last, Queue, unknown */ +const char sess_cookie[4] = "NIDV"; /* No cookie, Invalid cookie, cookie for a Down server, Valid cookie */ +const char sess_set_cookie[8] = "N1I3PD5R"; /* No set-cookie, unknown, Set-Cookie Inserted, unknown, + Set-cookie seen and left unchanged (passive), Set-cookie Deleted, + unknown, Set-cookie Rewritten */ +void **pool_requri = NULL; + + +/* + * Displays the message on stderr with the date and pid. Overrides the quiet + * mode during startup. + */ +void Alert(char *fmt, ...) +{ + va_list argp; + struct timeval tv; + struct tm *tm; + + if (!(global.mode & MODE_QUIET) || (global.mode & (MODE_VERBOSE | MODE_STARTING))) { + va_start(argp, fmt); + + gettimeofday(&tv, NULL); + tm = localtime(&tv.tv_sec); + fprintf(stderr, "[ALERT] %03d/%02d%02d%02d (%d) : ", + tm->tm_yday, tm->tm_hour, tm->tm_min, tm->tm_sec, (int)getpid()); + vfprintf(stderr, fmt, argp); + fflush(stderr); + va_end(argp); + } +} + + +/* + * Displays the message on stderr with the date and pid. + */ +void Warning(char *fmt, ...) +{ + va_list argp; + struct timeval tv; + struct tm *tm; + + if (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE)) { + va_start(argp, fmt); + + gettimeofday(&tv, NULL); + tm = localtime(&tv.tv_sec); + fprintf(stderr, "[WARNING] %03d/%02d%02d%02d (%d) : ", + tm->tm_yday, tm->tm_hour, tm->tm_min, tm->tm_sec, (int)getpid()); + vfprintf(stderr, fmt, argp); + fflush(stderr); + va_end(argp); + } +} + +/* + * Displays the message on only if quiet mode is not set. + */ +void qfprintf(FILE *out, char *fmt, ...) +{ + va_list argp; + + if (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE)) { + va_start(argp, fmt); + vfprintf(out, fmt, argp); + fflush(out); + va_end(argp); + } +} + +/* + * returns log level for or -1 if not found. + */ +int get_log_level(const char *lev) +{ + int level; + + level = NB_LOG_LEVELS - 1; + while (level >= 0 && strcmp(log_levels[level], lev)) + level--; + + return level; +} + + +/* + * returns log facility for or -1 if not found. + */ +int get_log_facility(const char *fac) +{ + int facility; + + facility = NB_LOG_FACILITIES - 1; + while (facility >= 0 && strcmp(log_facilities[facility], fac)) + facility--; + + return facility; +} + + +#define FD_SETS_ARE_BITFIELDS +#ifdef FD_SETS_ARE_BITFIELDS +/* + * This map is used with all the FD_* macros to check whether a particular bit + * is set or not. Each bit represents an ACSII code. FD_SET() sets those bytes + * which should be encoded. When FD_ISSET() returns non-zero, it means that the + * byte should be encoded. Be careful to always pass bytes from 0 to 255 + * exclusively to the macros. + */ +fd_set hdr_encode_map[(sizeof(fd_set) > (256/8)) ? 1 : ((256/8) / sizeof(fd_set))]; +fd_set url_encode_map[(sizeof(fd_set) > (256/8)) ? 1 : ((256/8) / sizeof(fd_set))]; + +#else +#error "Check if your OS uses bitfields for fd_sets" +#endif + +/* + * This function sends a syslog message to both log servers of a proxy, + * or to global log servers if the proxy is NULL. + * It also tries not to waste too much time computing the message header. + * It doesn't care about errors nor does it report them. + */ +void send_log(struct proxy *p, int level, char *message, ...) +{ + static int logfd = -1; /* syslog UDP socket */ + static long tvsec = -1; /* to force the string to be initialized */ + struct timeval tv; + va_list argp; + static char logmsg[MAX_SYSLOG_LEN]; + static char *dataptr = NULL; + int fac_level; + int hdr_len, data_len; + struct sockaddr_in *sa[2]; + int facilities[2], loglevel[2]; + int nbloggers = 0; + char *log_ptr; + + if (logfd < 0) { + if ((logfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) + return; + } + + if (level < 0 || progname == NULL || message == NULL) + return; + + gettimeofday(&tv, NULL); + if (tv.tv_sec != tvsec || dataptr == NULL) { + /* this string is rebuild only once a second */ + struct tm *tm = localtime(&tv.tv_sec); + tvsec = tv.tv_sec; + + hdr_len = snprintf(logmsg, sizeof(logmsg), + "<<<<>%s %2d %02d:%02d:%02d %s[%d]: ", + monthname[tm->tm_mon], + tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, + progname, pid); + /* WARNING: depending upon implementations, snprintf may return + * either -1 or the number of bytes that would be needed to store + * the total message. In both cases, we must adjust it. + */ + if (hdr_len < 0 || hdr_len > sizeof(logmsg)) + hdr_len = sizeof(logmsg); + + dataptr = logmsg + hdr_len; + } + + va_start(argp, message); + data_len = vsnprintf(dataptr, logmsg + sizeof(logmsg) - dataptr, message, argp); + if (data_len < 0 || data_len > (logmsg + sizeof(logmsg) - dataptr)) + data_len = logmsg + sizeof(logmsg) - dataptr; + va_end(argp); + dataptr[data_len - 1] = '\n'; /* force a break on ultra-long lines */ + + if (p == NULL) { + if (global.logfac1 >= 0) { + sa[nbloggers] = &global.logsrv1; + facilities[nbloggers] = global.logfac1; + loglevel[nbloggers] = global.loglev1; + nbloggers++; + } + if (global.logfac2 >= 0) { + sa[nbloggers] = &global.logsrv2; + facilities[nbloggers] = global.logfac2; + loglevel[nbloggers] = global.loglev2; + nbloggers++; + } + } else { + if (p->logfac1 >= 0) { + sa[nbloggers] = &p->logsrv1; + facilities[nbloggers] = p->logfac1; + loglevel[nbloggers] = p->loglev1; + nbloggers++; + } + if (p->logfac2 >= 0) { + sa[nbloggers] = &p->logsrv2; + facilities[nbloggers] = p->logfac2; + loglevel[nbloggers] = p->loglev2; + nbloggers++; + } + } + + while (nbloggers-- > 0) { + /* we can filter the level of the messages that are sent to each logger */ + if (level > loglevel[nbloggers]) + continue; + + /* For each target, we may have a different facility. + * We can also have a different log level for each message. + * This induces variations in the message header length. + * Since we don't want to recompute it each time, nor copy it every + * time, we only change the facility in the pre-computed header, + * and we change the pointer to the header accordingly. + */ + fac_level = (facilities[nbloggers] << 3) + level; + log_ptr = logmsg + 3; /* last digit of the log level */ + do { + *log_ptr = '0' + fac_level % 10; + fac_level /= 10; + log_ptr--; + } while (fac_level && log_ptr > logmsg); + *log_ptr = '<'; + + /* the total syslog message now starts at logptr, for dataptr+data_len-logptr */ + +#ifndef MSG_NOSIGNAL + sendto(logfd, log_ptr, dataptr + data_len - log_ptr, MSG_DONTWAIT, + (struct sockaddr *)sa[nbloggers], sizeof(**sa)); +#else + sendto(logfd, log_ptr, dataptr + data_len - log_ptr, MSG_DONTWAIT | MSG_NOSIGNAL, + (struct sockaddr *)sa[nbloggers], sizeof(**sa)); +#endif + } +} + + +/* + * send a log for the session when we have enough info about it + */ +void sess_log(struct session *s) +{ + char pn[INET6_ADDRSTRLEN + strlen(":65535")]; + struct proxy *p = s->proxy; + int log; + char *uri; + char *pxid; + char *srv; + struct tm *tm; + + /* This is a first attempt at a better logging system. + * For now, we rely on send_log() to provide the date, although it obviously + * is the date of the log and not of the request, and most fields are not + * computed. + */ + + log = p->to_log & ~s->logs.logwait; + + if (s->cli_addr.ss_family == AF_INET) + inet_ntop(AF_INET, + (const void *)&((struct sockaddr_in *)&s->cli_addr)->sin_addr, + pn, sizeof(pn)); + else + inet_ntop(AF_INET6, + (const void *)&((struct sockaddr_in6 *)(&s->cli_addr))->sin6_addr, + pn, sizeof(pn)); + + uri = (log & LW_REQ) ? s->logs.uri ? s->logs.uri : "" : ""; + pxid = p->id; + srv = (p->to_log & LW_SVID) ? + (s->data_source != DATA_SRC_STATS) ? + (s->srv != NULL) ? s->srv->id : "" : "" : "-"; + + tm = localtime(&s->logs.tv_accept.tv_sec); + if (p->to_log & LW_REQ) { + char tmpline[MAX_SYSLOG_LEN], *h; + int hdr; + + h = tmpline; + if (p->to_log & LW_REQHDR && (h < tmpline + sizeof(tmpline) - 10)) { + *(h++) = ' '; + *(h++) = '{'; + for (hdr = 0; hdr < p->nb_req_cap; hdr++) { + if (hdr) + *(h++) = '|'; + if (s->req_cap[hdr] != NULL) + h = encode_string(h, tmpline + sizeof(tmpline) - 7, + '#', hdr_encode_map, s->req_cap[hdr]); + } + *(h++) = '}'; + } + + if (p->to_log & LW_RSPHDR && (h < tmpline + sizeof(tmpline) - 7)) { + *(h++) = ' '; + *(h++) = '{'; + for (hdr = 0; hdr < p->nb_rsp_cap; hdr++) { + if (hdr) + *(h++) = '|'; + if (s->rsp_cap[hdr] != NULL) + h = encode_string(h, tmpline + sizeof(tmpline) - 4, + '#', hdr_encode_map, s->rsp_cap[hdr]); + } + *(h++) = '}'; + } + + if (h < tmpline + sizeof(tmpline) - 4) { + *(h++) = ' '; + *(h++) = '"'; + h = encode_string(h, tmpline + sizeof(tmpline) - 1, + '#', url_encode_map, uri); + *(h++) = '"'; + } + *h = '\0'; + + send_log(p, LOG_INFO, + "%s:%d [%02d/%s/%04d:%02d:%02d:%02d]" + " %s %s %d/%d/%d/%d/%s%d %d %s%lld" + " %s %s %c%c%c%c %d/%d/%d %d/%d%s\n", + pn, + (s->cli_addr.ss_family == AF_INET) ? + ntohs(((struct sockaddr_in *)&s->cli_addr)->sin_port) : + ntohs(((struct sockaddr_in6 *)&s->cli_addr)->sin6_port), + tm->tm_mday, monthname[tm->tm_mon], tm->tm_year+1900, + tm->tm_hour, tm->tm_min, tm->tm_sec, + pxid, srv, + s->logs.t_request, + (s->logs.t_queue >= 0) ? s->logs.t_queue - s->logs.t_request : -1, + (s->logs.t_connect >= 0) ? s->logs.t_connect - s->logs.t_queue : -1, + (s->logs.t_data >= 0) ? s->logs.t_data - s->logs.t_connect : -1, + (p->to_log & LW_BYTES) ? "" : "+", s->logs.t_close, + s->logs.status, + (p->to_log & LW_BYTES) ? "" : "+", s->logs.bytes, + s->logs.cli_cookie ? s->logs.cli_cookie : "-", + s->logs.srv_cookie ? s->logs.srv_cookie : "-", + sess_term_cond[(s->flags & SN_ERR_MASK) >> SN_ERR_SHIFT], + sess_fin_state[(s->flags & SN_FINST_MASK) >> SN_FINST_SHIFT], + (p->options & PR_O_COOK_ANY) ? sess_cookie[(s->flags & SN_CK_MASK) >> SN_CK_SHIFT] : '-', + (p->options & PR_O_COOK_ANY) ? sess_set_cookie[(s->flags & SN_SCK_MASK) >> SN_SCK_SHIFT] : '-', + s->srv ? s->srv->cur_sess : 0, p->nbconn, actconn, + s->logs.srv_queue_size, s->logs.prx_queue_size, tmpline); + } + else { + send_log(p, LOG_INFO, "%s:%d [%02d/%s/%04d:%02d:%02d:%02d]" + " %s %s %d/%d/%s%d %s%lld" + " %c%c %d/%d/%d %d/%d\n", + pn, + (s->cli_addr.ss_family == AF_INET) ? + ntohs(((struct sockaddr_in *)&s->cli_addr)->sin_port) : + ntohs(((struct sockaddr_in6 *)&s->cli_addr)->sin6_port), + tm->tm_mday, monthname[tm->tm_mon], tm->tm_year+1900, + tm->tm_hour, tm->tm_min, tm->tm_sec, + pxid, srv, + (s->logs.t_queue >= 0) ? s->logs.t_queue : -1, + (s->logs.t_connect >= 0) ? s->logs.t_connect - s->logs.t_queue : -1, + (p->to_log & LW_BYTES) ? "" : "+", s->logs.t_close, + (p->to_log & LW_BYTES) ? "" : "+", s->logs.bytes, + sess_term_cond[(s->flags & SN_ERR_MASK) >> SN_ERR_SHIFT], + sess_fin_state[(s->flags & SN_FINST_MASK) >> SN_FINST_SHIFT], + s->srv ? s->srv->cur_sess : 0, p->nbconn, actconn, + s->logs.srv_queue_size, s->logs.prx_queue_size); + } + + s->logs.logwait = 0; +} + + +/* + * Initializes some data needed later. + */ +void init_log() +{ + int i; + char *tmp; + + /* initialize the log header encoding map : '{|}"#' should be encoded with + * '#' as prefix, as well as non-printable characters ( <32 or >= 127 ). + * URL encoding only requires '"', '#' to be encoded as well as non- + * printable characters above. + */ + memset(hdr_encode_map, 0, sizeof(hdr_encode_map)); + memset(url_encode_map, 0, sizeof(url_encode_map)); + for (i = 0; i < 32; i++) { + FD_SET(i, hdr_encode_map); + FD_SET(i, url_encode_map); + } + for (i = 127; i < 256; i++) { + FD_SET(i, hdr_encode_map); + FD_SET(i, url_encode_map); + } + + tmp = "\"#{|}"; + while (*tmp) { + FD_SET(*tmp, hdr_encode_map); + tmp++; + } + + tmp = "\"#"; + while (*tmp) { + FD_SET(*tmp, url_encode_map); + tmp++; + } +} + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/polling.c b/src/polling.c new file mode 100644 index 000000000..af0129050 --- /dev/null +++ b/src/polling.c @@ -0,0 +1,455 @@ +/* + * File descriptors management functions. + * + * Copyright 2000-2006 Willy Tarreau + * + * 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 +#include + +#include +#include +#include + +#include +#include +#include + +#include + +fd_set *StaticReadEvent, *StaticWriteEvent; +int cfg_polling_mechanism = 0; /* POLL_USE_{SELECT|POLL|EPOLL} */ + +/* + * FIXME: this is dirty, but at the moment, there's no other solution to remove + * the old FDs from outside the loop. Perhaps we should export a global 'poll' + * structure with pointers to functions such as init_fd() and close_fd(), plus + * a private structure with several pointers to places such as below. + */ + +#if defined(ENABLE_EPOLL) +fd_set *PrevReadEvent = NULL, *PrevWriteEvent = NULL; + +#if defined(USE_MY_EPOLL) +_syscall1 (int, epoll_create, int, size); +_syscall4 (int, epoll_ctl, int, epfd, int, op, int, fd, struct epoll_event *, event); +_syscall4 (int, epoll_wait, int, epfd, struct epoll_event *, events, int, maxevents, int, timeout); +#endif + +#endif + + +#if defined(ENABLE_EPOLL) +/* + * Main epoll() loop. + * does 3 actions : + * 0 (POLL_LOOP_ACTION_INIT) : initializes necessary private structures + * 1 (POLL_LOOP_ACTION_RUN) : runs the loop + * 2 (POLL_LOOP_ACTION_CLEAN) : cleans up + * + * returns 0 if initialization failed, !0 otherwise. + */ + +int epoll_loop(int action) +{ + int next_time; + int status; + int fd; + + int fds, count; + int pr, pw, sr, sw; + unsigned rn, ro, wn, wo; /* read new, read old, write new, write old */ + struct epoll_event ev; + + /* private data */ + static struct epoll_event *epoll_events = NULL; + static int epoll_fd; + + if (action == POLL_LOOP_ACTION_INIT) { + epoll_fd = epoll_create(global.maxsock + 1); + if (epoll_fd < 0) + return 0; + else { + epoll_events = (struct epoll_event*) + calloc(1, sizeof(struct epoll_event) * global.maxsock); + PrevReadEvent = (fd_set *) + calloc(1, sizeof(fd_set) * (global.maxsock + FD_SETSIZE - 1) / FD_SETSIZE); + PrevWriteEvent = (fd_set *) + calloc(1, sizeof(fd_set) * (global.maxsock + FD_SETSIZE - 1) / FD_SETSIZE); + } + return 1; + } + else if (action == POLL_LOOP_ACTION_CLEAN) { + if (PrevWriteEvent) free(PrevWriteEvent); + if (PrevReadEvent) free(PrevReadEvent); + if (epoll_events) free(epoll_events); + close(epoll_fd); + epoll_fd = 0; + return 1; + } + + /* OK, it's POLL_LOOP_ACTION_RUN */ + + tv_now(&now); + + while (1) { + next_time = process_runnable_tasks(); + + /* stop when there's no connection left and we don't allow them anymore */ + if (!actconn && listeners == 0) + break; + + for (fds = 0; (fds << INTBITS) < maxfd; fds++) { + + rn = ((int*)StaticReadEvent)[fds]; ro = ((int*)PrevReadEvent)[fds]; + wn = ((int*)StaticWriteEvent)[fds]; wo = ((int*)PrevWriteEvent)[fds]; + + if ((ro^rn) | (wo^wn)) { + for (count = 0, fd = fds << INTBITS; count < (1<> count) & 1; + pw = (wo >> count) & 1; + sr = (rn >> count) & 1; + sw = (wn >> count) & 1; +#else + pr = FD_ISSET(fd&((1<> count) & 1; + sw = (wn >> count) & 1; +#else + sr = FD_ISSET(fd&((1< 0 && count < nbfd; count++) { + fd = poll_events[count].fd; + + if (!(poll_events[count].revents & ( POLLOUT | POLLIN | POLLERR | POLLHUP ))) + continue; + + /* ok, we found one active fd */ + status--; + + if (FD_ISSET(fd, StaticReadEvent)) { + if (fdtab[fd].state == FD_STCLOSE) + continue; + if (poll_events[count].revents & ( POLLIN | POLLERR | POLLHUP )) + fdtab[fd].read(fd); + } + + if (FD_ISSET(fd, StaticWriteEvent)) { + if (fdtab[fd].state == FD_STCLOSE) + continue; + if (poll_events[count].revents & ( POLLOUT | POLLERR | POLLHUP )) + fdtab[fd].write(fd); + } + } + } + return 1; +} +#endif + + + +/* + * Main select() loop. + * does 3 actions : + * 0 (POLL_LOOP_ACTION_INIT) : initializes necessary private structures + * 1 (POLL_LOOP_ACTION_RUN) : runs the loop + * 2 (POLL_LOOP_ACTION_CLEAN) : cleans up + * + * returns 0 if initialization failed, !0 otherwise. + */ + + +int select_loop(int action) +{ + int next_time; + int status; + int fd,i; + struct timeval delta; + int readnotnull, writenotnull; + static fd_set *ReadEvent = NULL, *WriteEvent = NULL; + + if (action == POLL_LOOP_ACTION_INIT) { + ReadEvent = (fd_set *) + calloc(1, sizeof(fd_set) * (global.maxsock + FD_SETSIZE - 1) / FD_SETSIZE); + WriteEvent = (fd_set *) + calloc(1, sizeof(fd_set) * (global.maxsock + FD_SETSIZE - 1) / FD_SETSIZE); + return 1; + } + else if (action == POLL_LOOP_ACTION_CLEAN) { + if (WriteEvent) free(WriteEvent); + if (ReadEvent) free(ReadEvent); + return 1; + } + + /* OK, it's POLL_LOOP_ACTION_RUN */ + + tv_now(&now); + + while (1) { + next_time = process_runnable_tasks(); + + /* stop when there's no connection left and we don't allow them anymore */ + if (!actconn && listeners == 0) + break; + + if (next_time > 0) { /* FIXME */ + /* Convert to timeval */ + /* to avoid eventual select loops due to timer precision */ + next_time += SCHEDULER_RESOLUTION; + delta.tv_sec = next_time / 1000; + delta.tv_usec = (next_time % 1000) * 1000; + } + else if (next_time == 0) { /* allow select to return immediately when needed */ + delta.tv_sec = delta.tv_usec = 0; + } + + + /* let's restore fdset state */ + + readnotnull = 0; writenotnull = 0; + for (i = 0; i < (maxfd + FD_SETSIZE - 1)/(8*sizeof(int)); i++) { + readnotnull |= (*(((int*)ReadEvent)+i) = *(((int*)StaticReadEvent)+i)) != 0; + writenotnull |= (*(((int*)WriteEvent)+i) = *(((int*)StaticWriteEvent)+i)) != 0; + } + + // /* just a verification code, needs to be removed for performance */ + // for (i=0; i= 0) ? &delta : NULL); + + /* this is an experiment on the separation of the select work */ + // status = (readnotnull ? select(maxfd, ReadEvent, NULL, NULL, (next_time >= 0) ? &delta : NULL) : 0); + // status |= (writenotnull ? select(maxfd, NULL, WriteEvent, NULL, (next_time >= 0) ? &delta : NULL) : 0); + + tv_now(&now); + + if (status > 0) { /* must proceed with events */ + + int fds; + char count; + + for (fds = 0; (fds << INTBITS) < maxfd; fds++) + if ((((int *)(ReadEvent))[fds] | ((int *)(WriteEvent))[fds]) != 0) + for (count = 1< + * + * 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 +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + +/* Warning: this one is an sprintf() fmt string, with as its only argument */ +const char *HTTP_401_fmt = + "HTTP/1.0 401 Unauthorized\r\n" + "Cache-Control: no-cache\r\n" + "Connection: close\r\n" + "WWW-Authenticate: Basic realm=\"%s\"\r\n" + "\r\n" + "

401 Unauthorized

\nYou need a valid user and password to access this content.\n\n"; + + +#ifdef DEBUG_FULL +static char *cli_stnames[5] = {"HDR", "DAT", "SHR", "SHW", "CLS" }; +static char *srv_stnames[7] = {"IDL", "CON", "HDR", "DAT", "SHR", "SHW", "CLS" }; +#endif + + +/* + * returns a message to the client ; the connection is shut down for read, + * and the request is cleared so that no server connection can be initiated. + * The client must be in a valid state for this (HEADER, DATA ...). + * Nothing is performed on the server side. + * The reply buffer doesn't need to be empty before this. + */ +void client_retnclose(struct session *s, int len, const char *msg) +{ + FD_CLR(s->cli_fd, StaticReadEvent); + FD_SET(s->cli_fd, StaticWriteEvent); + tv_eternity(&s->crexpire); + if (s->proxy->clitimeout) + tv_delayfrom(&s->cwexpire, &now, s->proxy->clitimeout); + else + tv_eternity(&s->cwexpire); + shutdown(s->cli_fd, SHUT_RD); + s->cli_state = CL_STSHUTR; + buffer_flush(s->rep); + buffer_write(s->rep, msg, len); + s->req->l = 0; +} + + +/* + * returns a message into the rep buffer, and flushes the req buffer. + * The reply buffer doesn't need to be empty before this. + */ +void client_return(struct session *s, int len, const char *msg) +{ + buffer_flush(s->rep); + buffer_write(s->rep, msg, len); + s->req->l = 0; +} + + +/* This function turns the server state into the SV_STCLOSE, and sets + * indicators accordingly. Note that if is 0, no message is + * returned. + */ +void srv_close_with_err(struct session *t, int err, int finst, + int status, int msglen, char *msg) +{ + t->srv_state = SV_STCLOSE; + if (status > 0) { + t->logs.status = status; + if (t->proxy->mode == PR_MODE_HTTP) + client_return(t, msglen, msg); + } + if (!(t->flags & SN_ERR_MASK)) + t->flags |= err; + if (!(t->flags & SN_FINST_MASK)) + t->flags |= finst; +} + + +/* Processes the client and server jobs of a session task, then + * puts it back to the wait queue in a clean state, or + * cleans up its resources if it must be deleted. Returns + * the time the task accepts to wait, or TIME_ETERNITY for + * infinity. + */ +int process_session(struct task *t) +{ + struct session *s = t->context; + int fsm_resync = 0; + + do { + fsm_resync = 0; + //fprintf(stderr,"before_cli:cli=%d, srv=%d\n", s->cli_state, s->srv_state); + fsm_resync |= process_cli(s); + //fprintf(stderr,"cli/srv:cli=%d, srv=%d\n", s->cli_state, s->srv_state); + fsm_resync |= process_srv(s); + //fprintf(stderr,"after_srv:cli=%d, srv=%d\n", s->cli_state, s->srv_state); + } while (fsm_resync); + + if (s->cli_state != CL_STCLOSE || s->srv_state != SV_STCLOSE) { + struct timeval min1, min2; + s->res_cw = s->res_cr = s->res_sw = s->res_sr = RES_SILENT; + + tv_min(&min1, &s->crexpire, &s->cwexpire); + tv_min(&min2, &s->srexpire, &s->swexpire); + tv_min(&min1, &min1, &s->cnexpire); + tv_min(&t->expire, &min1, &min2); + + /* restore t to its place in the task list */ + task_queue(t); + +#ifdef DEBUG_FULL + /* DEBUG code : this should never ever happen, otherwise it indicates + * that a task still has something to do and will provoke a quick loop. + */ + if (tv_remain2(&now, &t->expire) <= 0) + exit(100); +#endif + + return tv_remain2(&now, &t->expire); /* nothing more to do */ + } + + s->proxy->nbconn--; + actconn--; + + if ((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) { + int len; + len = sprintf(trash, "%08x:%s.closed[%04x:%04x]\n", s->uniq_id, s->proxy->id, (unsigned short)s->cli_fd, (unsigned short)s->srv_fd); + write(1, trash, len); + } + + s->logs.t_close = tv_diff(&s->logs.tv_accept, &now); + if (s->rep != NULL) + s->logs.bytes = s->rep->total; + + /* let's do a final log if we need it */ + if (s->logs.logwait && (!(s->proxy->options & PR_O_NULLNOLOG) || s->req->total)) + sess_log(s); + + /* the task MUST not be in the run queue anymore */ + task_delete(t); + session_free(s); + task_free(t); + return TIME_ETERNITY; /* rest in peace for eternity */ +} + + +/* + * FIXME: This should move to the HTTP_flow_analyzer code + */ + +/* + * manages the client FSM and its socket. BTW, it also tries to handle the + * cookie. It returns 1 if a state has changed (and a resync may be needed), + * 0 else. + */ +int process_cli(struct session *t) +{ + int s = t->srv_state; + int c = t->cli_state; + struct buffer *req = t->req; + struct buffer *rep = t->rep; + int method_checked = 0; + appsess *asession_temp = NULL; + appsess local_asession; + +#ifdef DEBUG_FULL + fprintf(stderr,"process_cli: c=%s s=%s set(r,w)=%d,%d exp(r,w)=%d.%d,%d.%d\n", + cli_stnames[c], srv_stnames[s], + FD_ISSET(t->cli_fd, StaticReadEvent), FD_ISSET(t->cli_fd, StaticWriteEvent), + t->crexpire.tv_sec, t->crexpire.tv_usec, + t->cwexpire.tv_sec, t->cwexpire.tv_usec); +#endif + //fprintf(stderr,"process_cli: c=%d, s=%d, cr=%d, cw=%d, sr=%d, sw=%d\n", c, s, + //FD_ISSET(t->cli_fd, StaticReadEvent), FD_ISSET(t->cli_fd, StaticWriteEvent), + //FD_ISSET(t->srv_fd, StaticReadEvent), FD_ISSET(t->srv_fd, StaticWriteEvent) + //); + if (c == CL_STHEADERS) { + /* now parse the partial (or complete) headers */ + while (req->lr < req->r) { /* this loop only sees one header at each iteration */ + char *ptr; + int delete_header; + char *request_line = NULL; + + ptr = req->lr; + + /* look for the end of the current header */ + while (ptr < req->r && *ptr != '\n' && *ptr != '\r') + ptr++; + + if (ptr == req->h) { /* empty line, end of headers */ + int line, len; + + /* + * first, let's check that it's not a leading empty line, in + * which case we'll ignore and remove it (according to RFC2616). + */ + if (req->h == req->data) { + /* to get a complete header line, we need the ending \r\n, \n\r, \r or \n too */ + if (ptr > req->r - 2) { + /* this is a partial header, let's wait for more to come */ + req->lr = ptr; + break; + } + + /* now we know that *ptr is either \r or \n, + * and that there are at least 1 char after it. + */ + if ((ptr[0] == ptr[1]) || (ptr[1] != '\r' && ptr[1] != '\n')) + req->lr = ptr + 1; /* \r\r, \n\n, \r[^\n], \n[^\r] */ + else + req->lr = ptr + 2; /* \r\n or \n\r */ + /* ignore empty leading lines */ + buffer_replace2(req, req->h, req->lr, NULL, 0); + req->h = req->lr; + continue; + } + + /* we can only get here after an end of headers */ + /* we'll have something else to do here : add new headers ... */ + + if (t->flags & SN_CLDENY) { + /* no need to go further */ + t->logs.status = 403; + t->logs.t_request = tv_diff(&t->logs.tv_accept, &now); /* let's log the request time */ + client_retnclose(t, t->proxy->errmsg.len403, t->proxy->errmsg.msg403); + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_PRXCOND; + if (!(t->flags & SN_FINST_MASK)) + t->flags |= SN_FINST_R; + return 1; + } + + /* Right now, we know that we have processed the entire headers + * and that unwanted requests have been filtered out. We can do + * whatever we want. + */ + + if (t->proxy->uri_auth != NULL + && t->req_line.len >= t->proxy->uri_auth->uri_len + 4) { /* +4 for "GET /" */ + if (!memcmp(t->req_line.str + 4, + t->proxy->uri_auth->uri_prefix, t->proxy->uri_auth->uri_len) + && !memcmp(t->req_line.str, "GET ", 4)) { + struct user_auth *user; + int authenticated; + + /* we are in front of a interceptable URI. Let's check + * if there's an authentication and if it's valid. + */ + user = t->proxy->uri_auth->users; + if (!user) { + /* no user auth required, it's OK */ + authenticated = 1; + } else { + authenticated = 0; + + /* a user list is defined, we have to check. + * skip 21 chars for "Authorization: Basic ". + */ + if (t->auth_hdr.len < 21 || memcmp(t->auth_hdr.str + 14, " Basic ", 7)) + user = NULL; + + while (user) { + if ((t->auth_hdr.len == user->user_len + 21) + && !memcmp(t->auth_hdr.str+21, user->user_pwd, user->user_len)) { + authenticated = 1; + break; + } + user = user->next; + } + } + + if (!authenticated) { + int msglen; + + /* no need to go further */ + + msglen = sprintf(trash, HTTP_401_fmt, t->proxy->uri_auth->auth_realm); + t->logs.status = 401; + client_retnclose(t, msglen, trash); + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_PRXCOND; + if (!(t->flags & SN_FINST_MASK)) + t->flags |= SN_FINST_R; + return 1; + } + + t->cli_state = CL_STSHUTR; + req->rlim = req->data + BUFSIZE; /* no more rewrite needed */ + t->logs.t_request = tv_diff(&t->logs.tv_accept, &now); + t->data_source = DATA_SRC_STATS; + t->data_state = DATA_ST_INIT; + produce_content(t); + return 1; + } + } + + + for (line = 0; line < t->proxy->nb_reqadd; line++) { + len = sprintf(trash, "%s\r\n", t->proxy->req_add[line]); + buffer_replace2(req, req->h, req->h, trash, len); + } + + if (t->proxy->options & PR_O_FWDFOR) { + if (t->cli_addr.ss_family == AF_INET) { + unsigned char *pn; + pn = (unsigned char *)&((struct sockaddr_in *)&t->cli_addr)->sin_addr; + len = sprintf(trash, "X-Forwarded-For: %d.%d.%d.%d\r\n", + pn[0], pn[1], pn[2], pn[3]); + buffer_replace2(req, req->h, req->h, trash, len); + } + else if (t->cli_addr.ss_family == AF_INET6) { + char pn[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, + (const void *)&((struct sockaddr_in6 *)(&t->cli_addr))->sin6_addr, + pn, sizeof(pn)); + len = sprintf(trash, "X-Forwarded-For: %s\r\n", pn); + buffer_replace2(req, req->h, req->h, trash, len); + } + } + + /* add a "connection: close" line if needed */ + if (t->proxy->options & PR_O_HTTP_CLOSE) + buffer_replace2(req, req->h, req->h, "Connection: close\r\n", 19); + + if (!memcmp(req->data, "POST ", 5)) { + /* this is a POST request, which is not cacheable by default */ + t->flags |= SN_POST; + } + + t->cli_state = CL_STDATA; + req->rlim = req->data + BUFSIZE; /* no more rewrite needed */ + + t->logs.t_request = tv_diff(&t->logs.tv_accept, &now); + /* FIXME: we'll set the client in a wait state while we try to + * connect to the server. Is this really needed ? wouldn't it be + * better to release the maximum of system buffers instead ? + * The solution is to enable the FD but set its time-out to + * eternity as long as the server-side does not enable data xfer. + * CL_STDATA also has to take care of this, which is done below. + */ + //FD_CLR(t->cli_fd, StaticReadEvent); + //tv_eternity(&t->crexpire); + + /* FIXME: if we break here (as up to 1.1.23), having the client + * shutdown its connection can lead to an abort further. + * it's better to either return 1 or even jump directly to the + * data state which will save one schedule. + */ + //break; + + if (!t->proxy->clitimeout || + (t->srv_state < SV_STDATA && t->proxy->srvtimeout)) + /* If the client has no timeout, or if the server is not ready yet, + * and we know for sure that it can expire, then it's cleaner to + * disable the timeout on the client side so that too low values + * cannot make the sessions abort too early. + * + * FIXME-20050705: the server needs a way to re-enable this time-out + * when it switches its state, otherwise a client can stay connected + * indefinitely. This now seems to be OK. + */ + tv_eternity(&t->crexpire); + + goto process_data; + } + + /* to get a complete header line, we need the ending \r\n, \n\r, \r or \n too */ + if (ptr > req->r - 2) { + /* this is a partial header, let's wait for more to come */ + req->lr = ptr; + break; + } + + /* now we know that *ptr is either \r or \n, + * and that there are at least 1 char after it. + */ + if ((ptr[0] == ptr[1]) || (ptr[1] != '\r' && ptr[1] != '\n')) + req->lr = ptr + 1; /* \r\r, \n\n, \r[^\n], \n[^\r] */ + else + req->lr = ptr + 2; /* \r\n or \n\r */ + + /* + * now we know that we have a full header ; we can do whatever + * we want with these pointers : + * req->h = beginning of header + * ptr = end of header (first \r or \n) + * req->lr = beginning of next line (next rep->h) + * req->r = end of data (not used at this stage) + */ + + if (!method_checked && (t->proxy->appsession_name != NULL) && + ((memcmp(req->h, "GET ", 4) == 0) || (memcmp(req->h, "POST ", 4) == 0)) && + ((request_line = memchr(req->h, ';', req->lr - req->h)) != NULL)) { + + /* skip ; */ + request_line++; + + /* look if we have a jsessionid */ + + if (strncasecmp(request_line, t->proxy->appsession_name, t->proxy->appsession_name_len) == 0) { + + /* skip jsessionid= */ + request_line += t->proxy->appsession_name_len + 1; + + /* First try if we allready have an appsession */ + asession_temp = &local_asession; + + if ((asession_temp->sessid = pool_alloc_from(apools.sessid, apools.ses_msize)) == NULL) { + Alert("Not enough memory process_cli():asession_temp->sessid:calloc().\n"); + send_log(t->proxy, LOG_ALERT, "Not enough Memory process_cli():asession_temp->sessid:calloc().\n"); + return 0; + } + + /* Copy the sessionid */ + memcpy(asession_temp->sessid, request_line, t->proxy->appsession_len); + asession_temp->sessid[t->proxy->appsession_len] = 0; + asession_temp->serverid = NULL; + + /* only do insert, if lookup fails */ + if (chtbl_lookup(&(t->proxy->htbl_proxy), (void *)&asession_temp)) { + if ((asession_temp = pool_alloc(appsess)) == NULL) { + Alert("Not enough memory process_cli():asession:calloc().\n"); + send_log(t->proxy, LOG_ALERT, "Not enough memory process_cli():asession:calloc().\n"); + return 0; + } + asession_temp->sessid = local_asession.sessid; + asession_temp->serverid = local_asession.serverid; + chtbl_insert(&(t->proxy->htbl_proxy), (void *) asession_temp); + } /* end if (chtbl_lookup()) */ + else { + /*free wasted memory;*/ + pool_free_to(apools.sessid, local_asession.sessid); + } + + tv_delayfrom(&asession_temp->expire, &now, t->proxy->appsession_timeout); + asession_temp->request_count++; + +#if defined(DEBUG_HASH) + print_table(&(t->proxy->htbl_proxy)); +#endif + + if (asession_temp->serverid == NULL) { + Alert("Found Application Session without matching server.\n"); + } else { + struct server *srv = t->proxy->srv; + while (srv) { + if (strcmp(srv->id, asession_temp->serverid) == 0) { + if (srv->state & SRV_RUNNING || t->proxy->options & PR_O_PERSIST) { + /* we found the server and it's usable */ + t->flags &= ~SN_CK_MASK; + t->flags |= SN_CK_VALID | SN_DIRECT | SN_ASSIGNED; + t->srv = srv; + break; + } else { + t->flags &= ~SN_CK_MASK; + t->flags |= SN_CK_DOWN; + } + } /* end if (strcmp()) */ + srv = srv->next; + }/* end while(srv) */ + }/* end else of if (asession_temp->serverid == NULL) */ + }/* end if (strncasecmp(request_line,t->proxy->appsession_name,apssesion_name_len) == 0) */ + else { + //fprintf(stderr,">>>>>>>>>>>>>>>>>>>>>>NO SESSION\n"); + } + method_checked = 1; + } /* end if (!method_checked ...) */ + else{ + //printf("No Methode-Header with Session-String\n"); + } + + if (t->logs.logwait & LW_REQ) { + /* we have a complete HTTP request that we must log */ + int urilen; + + if ((t->logs.uri = pool_alloc(requri)) == NULL) { + Alert("HTTP logging : out of memory.\n"); + t->logs.status = 500; + client_retnclose(t, t->proxy->errmsg.len500, t->proxy->errmsg.msg500); + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_PRXCOND; + if (!(t->flags & SN_FINST_MASK)) + t->flags |= SN_FINST_R; + return 1; + } + + urilen = ptr - req->h; + if (urilen >= REQURI_LEN) + urilen = REQURI_LEN - 1; + memcpy(t->logs.uri, req->h, urilen); + t->logs.uri[urilen] = 0; + + if (!(t->logs.logwait &= ~LW_REQ)) + sess_log(t); + } + else if (t->logs.logwait & LW_REQHDR) { + struct cap_hdr *h; + int len; + for (h = t->proxy->req_cap; h; h = h->next) { + if ((h->namelen + 2 <= ptr - req->h) && + (req->h[h->namelen] == ':') && + (strncasecmp(req->h, h->name, h->namelen) == 0)) { + + if (t->req_cap[h->index] == NULL) + t->req_cap[h->index] = pool_alloc_from(h->pool, h->len + 1); + + len = ptr - (req->h + h->namelen + 2); + if (len > h->len) + len = h->len; + + memcpy(t->req_cap[h->index], req->h + h->namelen + 2, len); + t->req_cap[h->index][len]=0; + } + } + + } + + delete_header = 0; + + if ((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) { + int len, max; + len = sprintf(trash, "%08x:%s.clihdr[%04x:%04x]: ", t->uniq_id, t->proxy->id, (unsigned short)t->cli_fd, (unsigned short)t->srv_fd); + max = ptr - req->h; + UBOUND(max, sizeof(trash) - len - 1); + len += strlcpy2(trash + len, req->h, max + 1); + trash[len++] = '\n'; + write(1, trash, len); + } + + + /* remove "connection: " if needed */ + if (!delete_header && (t->proxy->options & PR_O_HTTP_CLOSE) + && (strncasecmp(req->h, "Connection: ", 12) == 0)) { + delete_header = 1; + } + + /* try headers regexps */ + if (!delete_header && t->proxy->req_exp != NULL + && !(t->flags & SN_CLDENY)) { + struct hdr_exp *exp; + char term; + + term = *ptr; + *ptr = '\0'; + exp = t->proxy->req_exp; + do { + if (regexec(exp->preg, req->h, MAX_MATCH, pmatch, 0) == 0) { + switch (exp->action) { + case ACT_ALLOW: + if (!(t->flags & SN_CLDENY)) + t->flags |= SN_CLALLOW; + break; + case ACT_REPLACE: + if (!(t->flags & SN_CLDENY)) { + int len = exp_replace(trash, req->h, exp->replace, pmatch); + ptr += buffer_replace2(req, req->h, ptr, trash, len); + } + break; + case ACT_REMOVE: + if (!(t->flags & SN_CLDENY)) + delete_header = 1; + break; + case ACT_DENY: + if (!(t->flags & SN_CLALLOW)) + t->flags |= SN_CLDENY; + break; + case ACT_PASS: /* we simply don't deny this one */ + break; + } + break; + } + } while ((exp = exp->next) != NULL); + *ptr = term; /* restore the string terminator */ + } + + /* Now look for cookies. Conforming to RFC2109, we have to support + * attributes whose name begin with a '$', and associate them with + * the right cookie, if we want to delete this cookie. + * So there are 3 cases for each cookie read : + * 1) it's a special attribute, beginning with a '$' : ignore it. + * 2) it's a server id cookie that we *MAY* want to delete : save + * some pointers on it (last semi-colon, beginning of cookie...) + * 3) it's an application cookie : we *MAY* have to delete a previous + * "special" cookie. + * At the end of loop, if a "special" cookie remains, we may have to + * remove it. If no application cookie persists in the header, we + * *MUST* delete it + */ + if (!delete_header && + (t->proxy->cookie_name != NULL || t->proxy->capture_name != NULL || t->proxy->appsession_name !=NULL) + && !(t->flags & SN_CLDENY) && (ptr >= req->h + 8) + && (strncasecmp(req->h, "Cookie: ", 8) == 0)) { + char *p1, *p2, *p3, *p4; + char *del_colon, *del_cookie, *colon; + int app_cookies; + + p1 = req->h + 8; /* first char after 'Cookie: ' */ + colon = p1; + /* del_cookie == NULL => nothing to be deleted */ + del_colon = del_cookie = NULL; + app_cookies = 0; + + while (p1 < ptr) { + /* skip spaces and colons, but keep an eye on these ones */ + while (p1 < ptr) { + if (*p1 == ';' || *p1 == ',') + colon = p1; + else if (!isspace((int)*p1)) + break; + p1++; + } + + if (p1 == ptr) + break; + + /* p1 is at the beginning of the cookie name */ + p2 = p1; + while (p2 < ptr && *p2 != '=') + p2++; + + if (p2 == ptr) + break; + + p3 = p2 + 1; /* skips the '=' sign */ + if (p3 == ptr) + break; + + p4 = p3; + while (p4 < ptr && !isspace((int)*p4) && *p4 != ';' && *p4 != ',') + p4++; + + /* here, we have the cookie name between p1 and p2, + * and its value between p3 and p4. + * we can process it : + * + * Cookie: NAME=VALUE; + * | || || | + * | || || +--> p4 + * | || |+-------> p3 + * | || +--------> p2 + * | |+------------> p1 + * | +-------------> colon + * +--------------------> req->h + */ + + if (*p1 == '$') { + /* skip this one */ + } + else { + /* first, let's see if we want to capture it */ + if (t->proxy->capture_name != NULL && + t->logs.cli_cookie == NULL && + (p4 - p1 >= t->proxy->capture_namelen) && + memcmp(p1, t->proxy->capture_name, t->proxy->capture_namelen) == 0) { + int log_len = p4 - p1; + + if ((t->logs.cli_cookie = pool_alloc(capture)) == NULL) { + Alert("HTTP logging : out of memory.\n"); + } else { + if (log_len > t->proxy->capture_len) + log_len = t->proxy->capture_len; + memcpy(t->logs.cli_cookie, p1, log_len); + t->logs.cli_cookie[log_len] = 0; + } + } + + if ((p2 - p1 == t->proxy->cookie_len) && (t->proxy->cookie_name != NULL) && + (memcmp(p1, t->proxy->cookie_name, p2 - p1) == 0)) { + /* Cool... it's the right one */ + struct server *srv = t->proxy->srv; + char *delim; + + /* if we're in cookie prefix mode, we'll search the delimitor so that we + * have the server ID betweek p3 and delim, and the original cookie between + * delim+1 and p4. Otherwise, delim==p4 : + * + * Cookie: NAME=SRV~VALUE; + * | || || | | + * | || || | +--> p4 + * | || || +--------> delim + * | || |+-----------> p3 + * | || +------------> p2 + * | |+----------------> p1 + * | +-----------------> colon + * +------------------------> req->h + */ + + if (t->proxy->options & PR_O_COOK_PFX) { + for (delim = p3; delim < p4; delim++) + if (*delim == COOKIE_DELIM) + break; + } + else + delim = p4; + + + /* Here, we'll look for the first running server which supports the cookie. + * This allows to share a same cookie between several servers, for example + * to dedicate backup servers to specific servers only. + * However, to prevent clients from sticking to cookie-less backup server + * when they have incidentely learned an empty cookie, we simply ignore + * empty cookies and mark them as invalid. + */ + if (delim == p3) + srv = NULL; + + while (srv) { + if ((srv->cklen == delim - p3) && !memcmp(p3, srv->cookie, delim - p3)) { + if (srv->state & SRV_RUNNING || t->proxy->options & PR_O_PERSIST) { + /* we found the server and it's usable */ + t->flags &= ~SN_CK_MASK; + t->flags |= SN_CK_VALID | SN_DIRECT | SN_ASSIGNED; + t->srv = srv; + break; + } else { + /* we found a server, but it's down */ + t->flags &= ~SN_CK_MASK; + t->flags |= SN_CK_DOWN; + } + } + srv = srv->next; + } + + if (!srv && !(t->flags & SN_CK_DOWN)) { + /* no server matched this cookie */ + t->flags &= ~SN_CK_MASK; + t->flags |= SN_CK_INVALID; + } + + /* depending on the cookie mode, we may have to either : + * - delete the complete cookie if we're in insert+indirect mode, so that + * the server never sees it ; + * - remove the server id from the cookie value, and tag the cookie as an + * application cookie so that it does not get accidentely removed later, + * if we're in cookie prefix mode + */ + if ((t->proxy->options & PR_O_COOK_PFX) && (delim != p4)) { + buffer_replace2(req, p3, delim + 1, NULL, 0); + p4 -= (delim + 1 - p3); + ptr -= (delim + 1 - p3); + del_cookie = del_colon = NULL; + app_cookies++; /* protect the header from deletion */ + } + else if (del_cookie == NULL && + (t->proxy->options & (PR_O_COOK_INS | PR_O_COOK_IND)) == (PR_O_COOK_INS | PR_O_COOK_IND)) { + del_cookie = p1; + del_colon = colon; + } + } else { + /* now we know that we must keep this cookie since it's + * not ours. But if we wanted to delete our cookie + * earlier, we cannot remove the complete header, but we + * can remove the previous block itself. + */ + app_cookies++; + + if (del_cookie != NULL) { + buffer_replace2(req, del_cookie, p1, NULL, 0); + p4 -= (p1 - del_cookie); + ptr -= (p1 - del_cookie); + del_cookie = del_colon = NULL; + } + } + + if ((t->proxy->appsession_name != NULL) && + (memcmp(p1, t->proxy->appsession_name, p2 - p1) == 0)) { + /* first, let's see if the cookie is our appcookie*/ + + /* Cool... it's the right one */ + + asession_temp = &local_asession; + + if ((asession_temp->sessid = pool_alloc_from(apools.sessid, apools.ses_msize)) == NULL) { + Alert("Not enough memory process_cli():asession->sessid:malloc().\n"); + send_log(t->proxy, LOG_ALERT, "Not enough memory process_cli():asession->sessid:malloc().\n"); + return 0; + } + + memcpy(asession_temp->sessid, p3, t->proxy->appsession_len); + asession_temp->sessid[t->proxy->appsession_len] = 0; + asession_temp->serverid = NULL; + + /* only do insert, if lookup fails */ + if (chtbl_lookup(&(t->proxy->htbl_proxy), (void *) &asession_temp) != 0) { + if ((asession_temp = pool_alloc(appsess)) == NULL) { + Alert("Not enough memory process_cli():asession:calloc().\n"); + send_log(t->proxy, LOG_ALERT, "Not enough memory process_cli():asession:calloc().\n"); + return 0; + } + + asession_temp->sessid = local_asession.sessid; + asession_temp->serverid = local_asession.serverid; + chtbl_insert(&(t->proxy->htbl_proxy), (void *) asession_temp); + } else { + /* free wasted memory */ + pool_free_to(apools.sessid, local_asession.sessid); + } + + if (asession_temp->serverid == NULL) { + Alert("Found Application Session without matching server.\n"); + } else { + struct server *srv = t->proxy->srv; + while (srv) { + if (strcmp(srv->id, asession_temp->serverid) == 0) { + if (srv->state & SRV_RUNNING || t->proxy->options & PR_O_PERSIST) { + /* we found the server and it's usable */ + t->flags &= ~SN_CK_MASK; + t->flags |= SN_CK_VALID | SN_DIRECT | SN_ASSIGNED; + t->srv = srv; + break; + } else { + t->flags &= ~SN_CK_MASK; + t->flags |= SN_CK_DOWN; + } + } + srv = srv->next; + }/* end while(srv) */ + }/* end else if server == NULL */ + + tv_delayfrom(&asession_temp->expire, &now, t->proxy->appsession_timeout); + }/* end if ((t->proxy->appsession_name != NULL) ... */ + } + + /* we'll have to look for another cookie ... */ + p1 = p4; + } /* while (p1 < ptr) */ + + /* There's no more cookie on this line. + * We may have marked the last one(s) for deletion. + * We must do this now in two ways : + * - if there is no app cookie, we simply delete the header ; + * - if there are app cookies, we must delete the end of the + * string properly, including the colon/semi-colon before + * the cookie name. + */ + if (del_cookie != NULL) { + if (app_cookies) { + buffer_replace2(req, del_colon, ptr, NULL, 0); + /* WARNING! becomes invalid for now. If some code + * below needs to rely on it before the end of the global + * header loop, we need to correct it with this code : + */ + ptr = del_colon; + } + else + delete_header = 1; + } + } /* end of cookie processing on this header */ + + /* let's look if we have to delete this header */ + if (delete_header && !(t->flags & SN_CLDENY)) { + buffer_replace2(req, req->h, req->lr, NULL, 0); + /* WARNING: ptr is not valid anymore, since the header may have + * been deleted or truncated ! */ + } else { + /* try to catch the first line as the request */ + if (t->req_line.len < 0) { + t->req_line.str = req->h; + t->req_line.len = ptr - req->h; + } + + /* We might also need the 'Authorization: ' header */ + if (t->auth_hdr.len < 0 && + t->proxy->uri_auth != NULL && + ptr > req->h + 15 && + !strncasecmp("Authorization: ", req->h, 15)) { + t->auth_hdr.str = req->h; + t->auth_hdr.len = ptr - req->h; + } + } + + req->h = req->lr; + } /* while (req->lr < req->r) */ + + /* end of header processing (even if incomplete) */ + + if ((req->l < req->rlim - req->data) && ! FD_ISSET(t->cli_fd, StaticReadEvent)) { + /* fd in StaticReadEvent was disabled, perhaps because of a previous buffer + * full. We cannot loop here since event_cli_read will disable it only if + * req->l == rlim-data + */ + FD_SET(t->cli_fd, StaticReadEvent); + if (t->proxy->clitimeout) + tv_delayfrom(&t->crexpire, &now, t->proxy->clitimeout); + else + tv_eternity(&t->crexpire); + } + + /* Since we are in header mode, if there's no space left for headers, we + * won't be able to free more later, so the session will never terminate. + */ + if (req->l >= req->rlim - req->data) { + t->logs.status = 400; + client_retnclose(t, t->proxy->errmsg.len400, t->proxy->errmsg.msg400); + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_PRXCOND; + if (!(t->flags & SN_FINST_MASK)) + t->flags |= SN_FINST_R; + return 1; + } + else if (t->res_cr == RES_ERROR || t->res_cr == RES_NULL) { + /* read error, or last read : give up. */ + tv_eternity(&t->crexpire); + fd_delete(t->cli_fd); + t->cli_state = CL_STCLOSE; + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_CLICL; + if (!(t->flags & SN_FINST_MASK)) + t->flags |= SN_FINST_R; + return 1; + } + else if (tv_cmp2_ms(&t->crexpire, &now) <= 0) { + + /* read timeout : give up with an error message. + */ + t->logs.status = 408; + client_retnclose(t, t->proxy->errmsg.len408, t->proxy->errmsg.msg408); + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_CLITO; + if (!(t->flags & SN_FINST_MASK)) + t->flags |= SN_FINST_R; + return 1; + } + + return t->cli_state != CL_STHEADERS; + } + else if (c == CL_STDATA) { + process_data: + /* FIXME: this error handling is partly buggy because we always report + * a 'DATA' phase while we don't know if the server was in IDLE, CONN + * or HEADER phase. BTW, it's not logical to expire the client while + * we're waiting for the server to connect. + */ + /* read or write error */ + if (t->res_cw == RES_ERROR || t->res_cr == RES_ERROR) { + tv_eternity(&t->crexpire); + tv_eternity(&t->cwexpire); + fd_delete(t->cli_fd); + t->cli_state = CL_STCLOSE; + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_CLICL; + if (!(t->flags & SN_FINST_MASK)) { + if (t->pend_pos) + t->flags |= SN_FINST_Q; + else if (s == SV_STCONN) + t->flags |= SN_FINST_C; + else + t->flags |= SN_FINST_D; + } + return 1; + } + /* last read, or end of server write */ + else if (t->res_cr == RES_NULL || s == SV_STSHUTW || s == SV_STCLOSE) { + FD_CLR(t->cli_fd, StaticReadEvent); + tv_eternity(&t->crexpire); + shutdown(t->cli_fd, SHUT_RD); + t->cli_state = CL_STSHUTR; + return 1; + } + /* last server read and buffer empty */ + else if ((s == SV_STSHUTR || s == SV_STCLOSE) && (rep->l == 0)) { + FD_CLR(t->cli_fd, StaticWriteEvent); + tv_eternity(&t->cwexpire); + shutdown(t->cli_fd, SHUT_WR); + /* We must ensure that the read part is still alive when switching + * to shutw */ + FD_SET(t->cli_fd, StaticReadEvent); + if (t->proxy->clitimeout) + tv_delayfrom(&t->crexpire, &now, t->proxy->clitimeout); + t->cli_state = CL_STSHUTW; + //fprintf(stderr,"%p:%s(%d), c=%d, s=%d\n", t, __FUNCTION__, __LINE__, t->cli_state, t->cli_state); + return 1; + } + /* read timeout */ + else if (tv_cmp2_ms(&t->crexpire, &now) <= 0) { + FD_CLR(t->cli_fd, StaticReadEvent); + tv_eternity(&t->crexpire); + shutdown(t->cli_fd, SHUT_RD); + t->cli_state = CL_STSHUTR; + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_CLITO; + if (!(t->flags & SN_FINST_MASK)) { + if (t->pend_pos) + t->flags |= SN_FINST_Q; + else if (s == SV_STCONN) + t->flags |= SN_FINST_C; + else + t->flags |= SN_FINST_D; + } + return 1; + } + /* write timeout */ + else if (tv_cmp2_ms(&t->cwexpire, &now) <= 0) { + FD_CLR(t->cli_fd, StaticWriteEvent); + tv_eternity(&t->cwexpire); + shutdown(t->cli_fd, SHUT_WR); + /* We must ensure that the read part is still alive when switching + * to shutw */ + FD_SET(t->cli_fd, StaticReadEvent); + if (t->proxy->clitimeout) + tv_delayfrom(&t->crexpire, &now, t->proxy->clitimeout); + + t->cli_state = CL_STSHUTW; + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_CLITO; + if (!(t->flags & SN_FINST_MASK)) { + if (t->pend_pos) + t->flags |= SN_FINST_Q; + else if (s == SV_STCONN) + t->flags |= SN_FINST_C; + else + t->flags |= SN_FINST_D; + } + return 1; + } + + if (req->l >= req->rlim - req->data) { + /* no room to read more data */ + if (FD_ISSET(t->cli_fd, StaticReadEvent)) { + /* stop reading until we get some space */ + FD_CLR(t->cli_fd, StaticReadEvent); + tv_eternity(&t->crexpire); + } + } else { + /* there's still some space in the buffer */ + if (! FD_ISSET(t->cli_fd, StaticReadEvent)) { + FD_SET(t->cli_fd, StaticReadEvent); + if (!t->proxy->clitimeout || + (t->srv_state < SV_STDATA && t->proxy->srvtimeout)) + /* If the client has no timeout, or if the server not ready yet, and we + * know for sure that it can expire, then it's cleaner to disable the + * timeout on the client side so that too low values cannot make the + * sessions abort too early. + */ + tv_eternity(&t->crexpire); + else + tv_delayfrom(&t->crexpire, &now, t->proxy->clitimeout); + } + } + + if ((rep->l == 0) || + ((s < SV_STDATA) /* FIXME: this may be optimized && (rep->w == rep->h)*/)) { + if (FD_ISSET(t->cli_fd, StaticWriteEvent)) { + FD_CLR(t->cli_fd, StaticWriteEvent); /* stop writing */ + tv_eternity(&t->cwexpire); + } + } else { + /* buffer not empty */ + if (! FD_ISSET(t->cli_fd, StaticWriteEvent)) { + FD_SET(t->cli_fd, StaticWriteEvent); /* restart writing */ + if (t->proxy->clitimeout) { + tv_delayfrom(&t->cwexpire, &now, t->proxy->clitimeout); + /* FIXME: to prevent the client from expiring read timeouts during writes, + * we refresh it. */ + t->crexpire = t->cwexpire; + } + else + tv_eternity(&t->cwexpire); + } + } + return 0; /* other cases change nothing */ + } + else if (c == CL_STSHUTR) { + if (t->res_cw == RES_ERROR) { + tv_eternity(&t->cwexpire); + fd_delete(t->cli_fd); + t->cli_state = CL_STCLOSE; + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_CLICL; + if (!(t->flags & SN_FINST_MASK)) { + if (t->pend_pos) + t->flags |= SN_FINST_Q; + else if (s == SV_STCONN) + t->flags |= SN_FINST_C; + else + t->flags |= SN_FINST_D; + } + return 1; + } + else if ((s == SV_STSHUTR || s == SV_STCLOSE) && (rep->l == 0) + && !(t->flags & SN_SELF_GEN)) { + tv_eternity(&t->cwexpire); + fd_delete(t->cli_fd); + t->cli_state = CL_STCLOSE; + return 1; + } + else if (tv_cmp2_ms(&t->cwexpire, &now) <= 0) { + tv_eternity(&t->cwexpire); + fd_delete(t->cli_fd); + t->cli_state = CL_STCLOSE; + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_CLITO; + if (!(t->flags & SN_FINST_MASK)) { + if (t->pend_pos) + t->flags |= SN_FINST_Q; + else if (s == SV_STCONN) + t->flags |= SN_FINST_C; + else + t->flags |= SN_FINST_D; + } + return 1; + } + + if (t->flags & SN_SELF_GEN) { + produce_content(t); + if (rep->l == 0) { + tv_eternity(&t->cwexpire); + fd_delete(t->cli_fd); + t->cli_state = CL_STCLOSE; + return 1; + } + } + + if ((rep->l == 0) + || ((s == SV_STHEADERS) /* FIXME: this may be optimized && (rep->w == rep->h)*/)) { + if (FD_ISSET(t->cli_fd, StaticWriteEvent)) { + FD_CLR(t->cli_fd, StaticWriteEvent); /* stop writing */ + tv_eternity(&t->cwexpire); + } + } else { + /* buffer not empty */ + if (! FD_ISSET(t->cli_fd, StaticWriteEvent)) { + FD_SET(t->cli_fd, StaticWriteEvent); /* restart writing */ + if (t->proxy->clitimeout) { + tv_delayfrom(&t->cwexpire, &now, t->proxy->clitimeout); + /* FIXME: to prevent the client from expiring read timeouts during writes, + * we refresh it. */ + t->crexpire = t->cwexpire; + } + else + tv_eternity(&t->cwexpire); + } + } + return 0; + } + else if (c == CL_STSHUTW) { + if (t->res_cr == RES_ERROR) { + tv_eternity(&t->crexpire); + fd_delete(t->cli_fd); + t->cli_state = CL_STCLOSE; + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_CLICL; + if (!(t->flags & SN_FINST_MASK)) { + if (t->pend_pos) + t->flags |= SN_FINST_Q; + else if (s == SV_STCONN) + t->flags |= SN_FINST_C; + else + t->flags |= SN_FINST_D; + } + return 1; + } + else if (t->res_cr == RES_NULL || s == SV_STSHUTW || s == SV_STCLOSE) { + tv_eternity(&t->crexpire); + fd_delete(t->cli_fd); + t->cli_state = CL_STCLOSE; + return 1; + } + else if (tv_cmp2_ms(&t->crexpire, &now) <= 0) { + tv_eternity(&t->crexpire); + fd_delete(t->cli_fd); + t->cli_state = CL_STCLOSE; + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_CLITO; + if (!(t->flags & SN_FINST_MASK)) { + if (t->pend_pos) + t->flags |= SN_FINST_Q; + else if (s == SV_STCONN) + t->flags |= SN_FINST_C; + else + t->flags |= SN_FINST_D; + } + return 1; + } + else if (req->l >= req->rlim - req->data) { + /* no room to read more data */ + + /* FIXME-20050705: is it possible for a client to maintain a session + * after the timeout by sending more data after it receives a close ? + */ + + if (FD_ISSET(t->cli_fd, StaticReadEvent)) { + /* stop reading until we get some space */ + FD_CLR(t->cli_fd, StaticReadEvent); + tv_eternity(&t->crexpire); + //fprintf(stderr,"%p:%s(%d), c=%d, s=%d\n", t, __FUNCTION__, __LINE__, t->cli_state, t->cli_state); + } + } else { + /* there's still some space in the buffer */ + if (! FD_ISSET(t->cli_fd, StaticReadEvent)) { + FD_SET(t->cli_fd, StaticReadEvent); + if (t->proxy->clitimeout) + tv_delayfrom(&t->crexpire, &now, t->proxy->clitimeout); + else + tv_eternity(&t->crexpire); + //fprintf(stderr,"%p:%s(%d), c=%d, s=%d\n", t, __FUNCTION__, __LINE__, t->cli_state, t->cli_state); + } + } + return 0; + } + else { /* CL_STCLOSE: nothing to do */ + if ((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) { + int len; + len = sprintf(trash, "%08x:%s.clicls[%04x:%04x]\n", t->uniq_id, t->proxy->id, (unsigned short)t->cli_fd, (unsigned short)t->srv_fd); + write(1, trash, len); + } + return 0; + } + return 0; +} + + +/* + * manages the server FSM and its socket. It returns 1 if a state has changed + * (and a resync may be needed), 0 else. + */ +int process_srv(struct session *t) +{ + int s = t->srv_state; + int c = t->cli_state; + struct buffer *req = t->req; + struct buffer *rep = t->rep; + appsess *asession_temp = NULL; + appsess local_asession; + int conn_err; + +#ifdef DEBUG_FULL + fprintf(stderr,"process_srv: c=%s, s=%s\n", cli_stnames[c], srv_stnames[s]); +#endif + //fprintf(stderr,"process_srv: c=%d, s=%d, cr=%d, cw=%d, sr=%d, sw=%d\n", c, s, + //FD_ISSET(t->cli_fd, StaticReadEvent), FD_ISSET(t->cli_fd, StaticWriteEvent), + //FD_ISSET(t->srv_fd, StaticReadEvent), FD_ISSET(t->srv_fd, StaticWriteEvent) + //); + if (s == SV_STIDLE) { + if (c == CL_STHEADERS) + return 0; /* stay in idle, waiting for data to reach the client side */ + else if (c == CL_STCLOSE || c == CL_STSHUTW || + (c == CL_STSHUTR && + (t->req->l == 0 || t->proxy->options & PR_O_ABRT_CLOSE))) { /* give up */ + tv_eternity(&t->cnexpire); + if (t->pend_pos) + t->logs.t_queue = tv_diff(&t->logs.tv_accept, &now); + /* note that this must not return any error because it would be able to + * overwrite the client_retnclose() output. + */ + srv_close_with_err(t, SN_ERR_CLICL, t->pend_pos ? SN_FINST_Q : SN_FINST_C, 0, 0, NULL); + + return 1; + } + else { + /* Right now, we will need to create a connection to the server. + * We might already have tried, and got a connection pending, in + * which case we will not do anything till it's pending. It's up + * to any other session to release it and wake us up again. + */ + if (t->pend_pos) { + if (tv_cmp2_ms(&t->cnexpire, &now) > 0) + return 0; + else { + /* we've been waiting too long here */ + tv_eternity(&t->cnexpire); + t->logs.t_queue = tv_diff(&t->logs.tv_accept, &now); + srv_close_with_err(t, SN_ERR_SRVTO, SN_FINST_Q, + 503, t->proxy->errmsg.len503, t->proxy->errmsg.msg503); + if (t->srv) + t->srv->failed_conns++; + t->proxy->failed_conns++; + return 1; + } + } + + do { + /* first, get a connection */ + if (srv_redispatch_connect(t)) + return t->srv_state != SV_STIDLE; + + /* try to (re-)connect to the server, and fail if we expire the + * number of retries. + */ + if (srv_retryable_connect(t)) { + t->logs.t_queue = tv_diff(&t->logs.tv_accept, &now); + return t->srv_state != SV_STIDLE; + } + + } while (1); + } + } + else if (s == SV_STCONN) { /* connection in progress */ + if (c == CL_STCLOSE || c == CL_STSHUTW || + (c == CL_STSHUTR && + (t->req->l == 0 || t->proxy->options & PR_O_ABRT_CLOSE))) { /* give up */ + tv_eternity(&t->cnexpire); + fd_delete(t->srv_fd); + if (t->srv) + t->srv->cur_sess--; + + /* note that this must not return any error because it would be able to + * overwrite the client_retnclose() output. + */ + srv_close_with_err(t, SN_ERR_CLICL, SN_FINST_C, 0, 0, NULL); + return 1; + } + if (t->res_sw == RES_SILENT && tv_cmp2_ms(&t->cnexpire, &now) > 0) { + //fprintf(stderr,"1: c=%d, s=%d, now=%d.%06d, exp=%d.%06d\n", c, s, now.tv_sec, now.tv_usec, t->cnexpire.tv_sec, t->cnexpire.tv_usec); + return 0; /* nothing changed */ + } + else if (t->res_sw == RES_SILENT || t->res_sw == RES_ERROR) { + /* timeout, asynchronous connect error or first write error */ + //fprintf(stderr,"2: c=%d, s=%d\n", c, s); + + fd_delete(t->srv_fd); + if (t->srv) + t->srv->cur_sess--; + + if (t->res_sw == RES_SILENT) + conn_err = SN_ERR_SRVTO; // it was a connect timeout. + else + conn_err = SN_ERR_SRVCL; // it was an asynchronous connect error. + + /* ensure that we have enough retries left */ + if (srv_count_retry_down(t, conn_err)) + return 1; + + do { + /* Now we will try to either reconnect to the same server or + * connect to another server. If the connection gets queued + * because all servers are saturated, then we will go back to + * the SV_STIDLE state. + */ + if (srv_retryable_connect(t)) { + t->logs.t_queue = tv_diff(&t->logs.tv_accept, &now); + return t->srv_state != SV_STCONN; + } + + /* we need to redispatch the connection to another server */ + if (srv_redispatch_connect(t)) + return t->srv_state != SV_STCONN; + } while (1); + } + else { /* no error or write 0 */ + t->logs.t_connect = tv_diff(&t->logs.tv_accept, &now); + + //fprintf(stderr,"3: c=%d, s=%d\n", c, s); + if (req->l == 0) /* nothing to write */ { + FD_CLR(t->srv_fd, StaticWriteEvent); + tv_eternity(&t->swexpire); + } else /* need the right to write */ { + FD_SET(t->srv_fd, StaticWriteEvent); + if (t->proxy->srvtimeout) { + tv_delayfrom(&t->swexpire, &now, t->proxy->srvtimeout); + /* FIXME: to prevent the server from expiring read timeouts during writes, + * we refresh it. */ + t->srexpire = t->swexpire; + } + else + tv_eternity(&t->swexpire); + } + + if (t->proxy->mode == PR_MODE_TCP) { /* let's allow immediate data connection in this case */ + FD_SET(t->srv_fd, StaticReadEvent); + if (t->proxy->srvtimeout) + tv_delayfrom(&t->srexpire, &now, t->proxy->srvtimeout); + else + tv_eternity(&t->srexpire); + + t->srv_state = SV_STDATA; + if (t->srv) + t->srv->cum_sess++; + rep->rlim = rep->data + BUFSIZE; /* no rewrite needed */ + + /* if the user wants to log as soon as possible, without counting + bytes from the server, then this is the right moment. */ + if (t->proxy->to_log && !(t->logs.logwait & LW_BYTES)) { + t->logs.t_close = t->logs.t_connect; /* to get a valid end date */ + sess_log(t); + } + } + else { + t->srv_state = SV_STHEADERS; + if (t->srv) + t->srv->cum_sess++; + rep->rlim = rep->data + BUFSIZE - MAXREWRITE; /* rewrite needed */ + } + tv_eternity(&t->cnexpire); + return 1; + } + } + else if (s == SV_STHEADERS) { /* receiving server headers */ + /* now parse the partial (or complete) headers */ + while (rep->lr < rep->r) { /* this loop only sees one header at each iteration */ + char *ptr; + int delete_header; + + ptr = rep->lr; + + /* look for the end of the current header */ + while (ptr < rep->r && *ptr != '\n' && *ptr != '\r') + ptr++; + + if (ptr == rep->h) { + int line, len; + + /* we can only get here after an end of headers */ + + /* first, we'll block if security checks have caught nasty things */ + if (t->flags & SN_CACHEABLE) { + if ((t->flags & SN_CACHE_COOK) && + (t->flags & SN_SCK_ANY) && + (t->proxy->options & PR_O_CHK_CACHE)) { + + /* we're in presence of a cacheable response containing + * a set-cookie header. We'll block it as requested by + * the 'checkcache' option, and send an alert. + */ + tv_eternity(&t->srexpire); + tv_eternity(&t->swexpire); + fd_delete(t->srv_fd); + if (t->srv) { + t->srv->cur_sess--; + t->srv->failed_secu++; + } + t->proxy->failed_secu++; + t->srv_state = SV_STCLOSE; + t->logs.status = 502; + client_return(t, t->proxy->errmsg.len502, t->proxy->errmsg.msg502); + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_PRXCOND; + if (!(t->flags & SN_FINST_MASK)) + t->flags |= SN_FINST_H; + + Alert("Blocking cacheable cookie in response from instance %s, server %s.\n", t->proxy->id, t->srv->id); + send_log(t->proxy, LOG_ALERT, "Blocking cacheable cookie in response from instance %s, server %s.\n", t->proxy->id, t->srv->id); + + /* We used to have a free connection slot. Since we'll never use it, + * we have to inform the server that it may be used by another session. + */ + if (may_dequeue_tasks(t->srv, t->proxy)) + task_wakeup(&rq, t->srv->queue_mgt); + + return 1; + } + } + + /* next, we'll block if an 'rspideny' or 'rspdeny' filter matched */ + if (t->flags & SN_SVDENY) { + tv_eternity(&t->srexpire); + tv_eternity(&t->swexpire); + fd_delete(t->srv_fd); + if (t->srv) { + t->srv->cur_sess--; + t->srv->failed_secu++; + } + t->proxy->failed_secu++; + t->srv_state = SV_STCLOSE; + t->logs.status = 502; + client_return(t, t->proxy->errmsg.len502, t->proxy->errmsg.msg502); + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_PRXCOND; + if (!(t->flags & SN_FINST_MASK)) + t->flags |= SN_FINST_H; + /* We used to have a free connection slot. Since we'll never use it, + * we have to inform the server that it may be used by another session. + */ + if (may_dequeue_tasks(t->srv, t->proxy)) + task_wakeup(&rq, t->srv->queue_mgt); + + return 1; + } + + /* we'll have something else to do here : add new headers ... */ + + if ((t->srv) && !(t->flags & SN_DIRECT) && (t->proxy->options & PR_O_COOK_INS) && + (!(t->proxy->options & PR_O_COOK_POST) || (t->flags & SN_POST))) { + /* the server is known, it's not the one the client requested, we have to + * insert a set-cookie here, except if we want to insert only on POST + * requests and this one isn't. Note that servers which don't have cookies + * (eg: some backup servers) will return a full cookie removal request. + */ + len = sprintf(trash, "Set-Cookie: %s=%s; path=/\r\n", + t->proxy->cookie_name, + t->srv->cookie ? t->srv->cookie : "; Expires=Thu, 01-Jan-1970 00:00:01 GMT"); + + t->flags |= SN_SCK_INSERTED; + + /* Here, we will tell an eventual cache on the client side that we don't + * want it to cache this reply because HTTP/1.0 caches also cache cookies ! + * Some caches understand the correct form: 'no-cache="set-cookie"', but + * others don't (eg: apache <= 1.3.26). So we use 'private' instead. + */ + if (t->proxy->options & PR_O_COOK_NOC) + //len += sprintf(newhdr + len, "Cache-control: no-cache=\"set-cookie\"\r\n"); + len += sprintf(trash + len, "Cache-control: private\r\n"); + + if (rep->data + rep->l < rep->h) + /* The data has been stolen, we will crash cleanly instead of corrupting memory */ + *(int *)0 = 0; + buffer_replace2(rep, rep->h, rep->h, trash, len); + } + + /* headers to be added */ + for (line = 0; line < t->proxy->nb_rspadd; line++) { + len = sprintf(trash, "%s\r\n", t->proxy->rsp_add[line]); + buffer_replace2(rep, rep->h, rep->h, trash, len); + } + + /* add a "connection: close" line if needed */ + if (t->proxy->options & PR_O_HTTP_CLOSE) + buffer_replace2(rep, rep->h, rep->h, "Connection: close\r\n", 19); + + t->srv_state = SV_STDATA; + rep->rlim = rep->data + BUFSIZE; /* no more rewrite needed */ + t->logs.t_data = tv_diff(&t->logs.tv_accept, &now); + + /* client connection already closed or option 'httpclose' required : + * we close the server's outgoing connection right now. + */ + if ((req->l == 0) && + (c == CL_STSHUTR || c == CL_STCLOSE || t->proxy->options & PR_O_FORCE_CLO)) { + FD_CLR(t->srv_fd, StaticWriteEvent); + tv_eternity(&t->swexpire); + + /* We must ensure that the read part is still alive when switching + * to shutw */ + FD_SET(t->srv_fd, StaticReadEvent); + if (t->proxy->srvtimeout) + tv_delayfrom(&t->srexpire, &now, t->proxy->srvtimeout); + + shutdown(t->srv_fd, SHUT_WR); + t->srv_state = SV_STSHUTW; + } + + /* if the user wants to log as soon as possible, without counting + bytes from the server, then this is the right moment. */ + if (t->proxy->to_log && !(t->logs.logwait & LW_BYTES)) { + t->logs.t_close = t->logs.t_data; /* to get a valid end date */ + t->logs.bytes = rep->h - rep->data; + sess_log(t); + } + break; + } + + /* to get a complete header line, we need the ending \r\n, \n\r, \r or \n too */ + if (ptr > rep->r - 2) { + /* this is a partial header, let's wait for more to come */ + rep->lr = ptr; + break; + } + + // fprintf(stderr,"h=%p, ptr=%p, lr=%p, r=%p, *h=", rep->h, ptr, rep->lr, rep->r); + // write(2, rep->h, ptr - rep->h); fprintf(stderr,"\n"); + + /* now we know that *ptr is either \r or \n, + * and that there are at least 1 char after it. + */ + if ((ptr[0] == ptr[1]) || (ptr[1] != '\r' && ptr[1] != '\n')) + rep->lr = ptr + 1; /* \r\r, \n\n, \r[^\n], \n[^\r] */ + else + rep->lr = ptr + 2; /* \r\n or \n\r */ + + /* + * now we know that we have a full header ; we can do whatever + * we want with these pointers : + * rep->h = beginning of header + * ptr = end of header (first \r or \n) + * rep->lr = beginning of next line (next rep->h) + * rep->r = end of data (not used at this stage) + */ + + + if (t->logs.status == -1) { + t->logs.logwait &= ~LW_RESP; + t->logs.status = atoi(rep->h + 9); + switch (t->logs.status) { + case 200: + case 203: + case 206: + case 300: + case 301: + case 410: + /* RFC2616 @13.4: + * "A response received with a status code of + * 200, 203, 206, 300, 301 or 410 MAY be stored + * by a cache (...) unless a cache-control + * directive prohibits caching." + * + * RFC2616 @9.5: POST method : + * "Responses to this method are not cacheable, + * unless the response includes appropriate + * Cache-Control or Expires header fields." + */ + if (!(t->flags & SN_POST) && (t->proxy->options & PR_O_CHK_CACHE)) + t->flags |= SN_CACHEABLE | SN_CACHE_COOK; + break; + default: + break; + } + } + else if (t->logs.logwait & LW_RSPHDR) { + struct cap_hdr *h; + int len; + for (h = t->proxy->rsp_cap; h; h = h->next) { + if ((h->namelen + 2 <= ptr - rep->h) && + (rep->h[h->namelen] == ':') && + (strncasecmp(rep->h, h->name, h->namelen) == 0)) { + + if (t->rsp_cap[h->index] == NULL) + t->rsp_cap[h->index] = pool_alloc_from(h->pool, h->len + 1); + + len = ptr - (rep->h + h->namelen + 2); + if (len > h->len) + len = h->len; + + memcpy(t->rsp_cap[h->index], rep->h + h->namelen + 2, len); + t->rsp_cap[h->index][len]=0; + } + } + + } + + delete_header = 0; + + if ((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) { + int len, max; + len = sprintf(trash, "%08x:%s.srvhdr[%04x:%04x]: ", t->uniq_id, t->proxy->id, (unsigned short)t->cli_fd, (unsigned short)t->srv_fd); + max = ptr - rep->h; + UBOUND(max, sizeof(trash) - len - 1); + len += strlcpy2(trash + len, rep->h, max + 1); + trash[len++] = '\n'; + write(1, trash, len); + } + + /* remove "connection: " if needed */ + if (!delete_header && (t->proxy->options & PR_O_HTTP_CLOSE) + && (strncasecmp(rep->h, "Connection: ", 12) == 0)) { + delete_header = 1; + } + + /* try headers regexps */ + if (!delete_header && t->proxy->rsp_exp != NULL + && !(t->flags & SN_SVDENY)) { + struct hdr_exp *exp; + char term; + + term = *ptr; + *ptr = '\0'; + exp = t->proxy->rsp_exp; + do { + if (regexec(exp->preg, rep->h, MAX_MATCH, pmatch, 0) == 0) { + switch (exp->action) { + case ACT_ALLOW: + if (!(t->flags & SN_SVDENY)) + t->flags |= SN_SVALLOW; + break; + case ACT_REPLACE: + if (!(t->flags & SN_SVDENY)) { + int len = exp_replace(trash, rep->h, exp->replace, pmatch); + ptr += buffer_replace2(rep, rep->h, ptr, trash, len); + } + break; + case ACT_REMOVE: + if (!(t->flags & SN_SVDENY)) + delete_header = 1; + break; + case ACT_DENY: + if (!(t->flags & SN_SVALLOW)) + t->flags |= SN_SVDENY; + break; + case ACT_PASS: /* we simply don't deny this one */ + break; + } + break; + } + } while ((exp = exp->next) != NULL); + *ptr = term; /* restore the string terminator */ + } + + /* check for cache-control: or pragma: headers */ + if (!delete_header && (t->flags & SN_CACHEABLE)) { + if (strncasecmp(rep->h, "Pragma: no-cache", 16) == 0) + t->flags &= ~SN_CACHEABLE & ~SN_CACHE_COOK; + else if (strncasecmp(rep->h, "Cache-control: ", 15) == 0) { + if (strncasecmp(rep->h + 15, "no-cache", 8) == 0) { + if (rep->h + 23 == ptr || rep->h[23] == ',') + t->flags &= ~SN_CACHEABLE & ~SN_CACHE_COOK; + else { + if (strncasecmp(rep->h + 23, "=\"set-cookie", 12) == 0 + && (rep->h[35] == '"' || rep->h[35] == ',')) + t->flags &= ~SN_CACHE_COOK; + } + } else if ((strncasecmp(rep->h + 15, "private", 7) == 0 && + (rep->h + 22 == ptr || rep->h[22] == ',')) + || (strncasecmp(rep->h + 15, "no-store", 8) == 0 && + (rep->h + 23 == ptr || rep->h[23] == ','))) { + t->flags &= ~SN_CACHEABLE & ~SN_CACHE_COOK; + } else if (strncasecmp(rep->h + 15, "max-age=0", 9) == 0 && + (rep->h + 24 == ptr || rep->h[24] == ',')) { + t->flags &= ~SN_CACHEABLE & ~SN_CACHE_COOK; + } else if (strncasecmp(rep->h + 15, "s-maxage=0", 10) == 0 && + (rep->h + 25 == ptr || rep->h[25] == ',')) { + t->flags &= ~SN_CACHEABLE & ~SN_CACHE_COOK; + } else if (strncasecmp(rep->h + 15, "public", 6) == 0 && + (rep->h + 21 == ptr || rep->h[21] == ',')) { + t->flags |= SN_CACHEABLE | SN_CACHE_COOK; + } + } + } + + /* check for server cookies */ + if (!delete_header /*&& (t->proxy->options & PR_O_COOK_ANY)*/ + && (t->proxy->cookie_name != NULL || t->proxy->capture_name != NULL || t->proxy->appsession_name !=NULL) + && (strncasecmp(rep->h, "Set-Cookie: ", 12) == 0)) { + char *p1, *p2, *p3, *p4; + + t->flags |= SN_SCK_ANY; + + p1 = rep->h + 12; /* first char after 'Set-Cookie: ' */ + + while (p1 < ptr) { /* in fact, we'll break after the first cookie */ + while (p1 < ptr && (isspace((int)*p1))) + p1++; + + if (p1 == ptr || *p1 == ';') /* end of cookie */ + break; + + /* p1 is at the beginning of the cookie name */ + p2 = p1; + + while (p2 < ptr && *p2 != '=' && *p2 != ';') + p2++; + + if (p2 == ptr || *p2 == ';') /* next cookie */ + break; + + p3 = p2 + 1; /* skips the '=' sign */ + if (p3 == ptr) + break; + + p4 = p3; + while (p4 < ptr && !isspace((int)*p4) && *p4 != ';') + p4++; + + /* here, we have the cookie name between p1 and p2, + * and its value between p3 and p4. + * we can process it. + */ + + /* first, let's see if we want to capture it */ + if (t->proxy->capture_name != NULL && + t->logs.srv_cookie == NULL && + (p4 - p1 >= t->proxy->capture_namelen) && + memcmp(p1, t->proxy->capture_name, t->proxy->capture_namelen) == 0) { + int log_len = p4 - p1; + + if ((t->logs.srv_cookie = pool_alloc(capture)) == NULL) { + Alert("HTTP logging : out of memory.\n"); + } + + if (log_len > t->proxy->capture_len) + log_len = t->proxy->capture_len; + memcpy(t->logs.srv_cookie, p1, log_len); + t->logs.srv_cookie[log_len] = 0; + } + + if ((p2 - p1 == t->proxy->cookie_len) && (t->proxy->cookie_name != NULL) && + (memcmp(p1, t->proxy->cookie_name, p2 - p1) == 0)) { + /* Cool... it's the right one */ + t->flags |= SN_SCK_SEEN; + + /* If the cookie is in insert mode on a known server, we'll delete + * this occurrence because we'll insert another one later. + * We'll delete it too if the "indirect" option is set and we're in + * a direct access. */ + if (((t->srv) && (t->proxy->options & PR_O_COOK_INS)) || + ((t->flags & SN_DIRECT) && (t->proxy->options & PR_O_COOK_IND))) { + /* this header must be deleted */ + delete_header = 1; + t->flags |= SN_SCK_DELETED; + } + else if ((t->srv) && (t->proxy->options & PR_O_COOK_RW)) { + /* replace bytes p3->p4 with the cookie name associated + * with this server since we know it. + */ + buffer_replace2(rep, p3, p4, t->srv->cookie, t->srv->cklen); + t->flags |= SN_SCK_INSERTED | SN_SCK_DELETED; + } + else if ((t->srv) && (t->proxy->options & PR_O_COOK_PFX)) { + /* insert the cookie name associated with this server + * before existing cookie, and insert a delimitor between them.. + */ + buffer_replace2(rep, p3, p3, t->srv->cookie, t->srv->cklen + 1); + p3[t->srv->cklen] = COOKIE_DELIM; + t->flags |= SN_SCK_INSERTED | SN_SCK_DELETED; + } + break; + } + + /* first, let's see if the cookie is our appcookie*/ + if ((t->proxy->appsession_name != NULL) && + (memcmp(p1, t->proxy->appsession_name, p2 - p1) == 0)) { + + /* Cool... it's the right one */ + + size_t server_id_len = strlen(t->srv->id) + 1; + asession_temp = &local_asession; + + if ((asession_temp->sessid = pool_alloc_from(apools.sessid, apools.ses_msize)) == NULL) { + Alert("Not enought Memory process_srv():asession->sessid:malloc().\n"); + send_log(t->proxy, LOG_ALERT, "Not enought Memory process_srv():asession->sessid:malloc().\n"); + } + memcpy(asession_temp->sessid, p3, t->proxy->appsession_len); + asession_temp->sessid[t->proxy->appsession_len] = 0; + asession_temp->serverid = NULL; + + /* only do insert, if lookup fails */ + if (chtbl_lookup(&(t->proxy->htbl_proxy), (void *) &asession_temp) != 0) { + if ((asession_temp = pool_alloc(appsess)) == NULL) { + Alert("Not enought Memory process_srv():asession:calloc().\n"); + send_log(t->proxy, LOG_ALERT, "Not enought Memory process_srv():asession:calloc().\n"); + return 0; + } + asession_temp->sessid = local_asession.sessid; + asession_temp->serverid = local_asession.serverid; + chtbl_insert(&(t->proxy->htbl_proxy), (void *) asession_temp); + }/* end if (chtbl_lookup()) */ + else { + /* free wasted memory */ + pool_free_to(apools.sessid, local_asession.sessid); + } /* end else from if (chtbl_lookup()) */ + + if (asession_temp->serverid == NULL) { + if ((asession_temp->serverid = pool_alloc_from(apools.serverid, apools.ser_msize)) == NULL) { + Alert("Not enought Memory process_srv():asession->sessid:malloc().\n"); + send_log(t->proxy, LOG_ALERT, "Not enought Memory process_srv():asession->sessid:malloc().\n"); + } + asession_temp->serverid[0] = '\0'; + } + + if (asession_temp->serverid[0] == '\0') + memcpy(asession_temp->serverid,t->srv->id,server_id_len); + + tv_delayfrom(&asession_temp->expire, &now, t->proxy->appsession_timeout); + +#if defined(DEBUG_HASH) + print_table(&(t->proxy->htbl_proxy)); +#endif + break; + }/* end if ((t->proxy->appsession_name != NULL) ... */ + else { + // fprintf(stderr,"Ignoring unknown cookie : "); + // write(2, p1, p2-p1); + // fprintf(stderr," = "); + // write(2, p3, p4-p3); + // fprintf(stderr,"\n"); + } + break; /* we don't want to loop again since there cannot be another cookie on the same line */ + } /* we're now at the end of the cookie value */ + } /* end of cookie processing */ + + /* check for any set-cookie in case we check for cacheability */ + if (!delete_header && !(t->flags & SN_SCK_ANY) && + (t->proxy->options & PR_O_CHK_CACHE) && + (strncasecmp(rep->h, "Set-Cookie: ", 12) == 0)) { + t->flags |= SN_SCK_ANY; + } + + /* let's look if we have to delete this header */ + if (delete_header && !(t->flags & SN_SVDENY)) + buffer_replace2(rep, rep->h, rep->lr, "", 0); + + rep->h = rep->lr; + } /* while (rep->lr < rep->r) */ + + /* end of header processing (even if incomplete) */ + + if ((rep->l < rep->rlim - rep->data) && ! FD_ISSET(t->srv_fd, StaticReadEvent)) { + /* fd in StaticReadEvent was disabled, perhaps because of a previous buffer + * full. We cannot loop here since event_srv_read will disable it only if + * rep->l == rlim-data + */ + FD_SET(t->srv_fd, StaticReadEvent); + if (t->proxy->srvtimeout) + tv_delayfrom(&t->srexpire, &now, t->proxy->srvtimeout); + else + tv_eternity(&t->srexpire); + } + + /* read error, write error */ + if (t->res_sw == RES_ERROR || t->res_sr == RES_ERROR) { + tv_eternity(&t->srexpire); + tv_eternity(&t->swexpire); + fd_delete(t->srv_fd); + if (t->srv) { + t->srv->cur_sess--; + t->srv->failed_resp++; + } + t->proxy->failed_resp++; + + t->srv_state = SV_STCLOSE; + t->logs.status = 502; + client_return(t, t->proxy->errmsg.len502, t->proxy->errmsg.msg502); + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_SRVCL; + if (!(t->flags & SN_FINST_MASK)) + t->flags |= SN_FINST_H; + /* We used to have a free connection slot. Since we'll never use it, + * we have to inform the server that it may be used by another session. + */ + if (may_dequeue_tasks(t->srv, t->proxy)) + task_wakeup(&rq, t->srv->queue_mgt); + + return 1; + } + /* end of client write or end of server read. + * since we are in header mode, if there's no space left for headers, we + * won't be able to free more later, so the session will never terminate. + */ + else if (t->res_sr == RES_NULL || c == CL_STSHUTW || c == CL_STCLOSE || rep->l >= rep->rlim - rep->data) { + FD_CLR(t->srv_fd, StaticReadEvent); + tv_eternity(&t->srexpire); + shutdown(t->srv_fd, SHUT_RD); + t->srv_state = SV_STSHUTR; + //fprintf(stderr,"%p:%s(%d), c=%d, s=%d\n", t, __FUNCTION__, __LINE__, t->cli_state, t->cli_state); + return 1; + } + /* read timeout : return a 504 to the client. + */ + else if (FD_ISSET(t->srv_fd, StaticReadEvent) && tv_cmp2_ms(&t->srexpire, &now) <= 0) { + tv_eternity(&t->srexpire); + tv_eternity(&t->swexpire); + fd_delete(t->srv_fd); + if (t->srv) { + t->srv->cur_sess--; + t->srv->failed_resp++; + } + t->proxy->failed_resp++; + t->srv_state = SV_STCLOSE; + t->logs.status = 504; + client_return(t, t->proxy->errmsg.len504, t->proxy->errmsg.msg504); + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_SRVTO; + if (!(t->flags & SN_FINST_MASK)) + t->flags |= SN_FINST_H; + /* We used to have a free connection slot. Since we'll never use it, + * we have to inform the server that it may be used by another session. + */ + if (may_dequeue_tasks(t->srv, t->proxy)) + task_wakeup(&rq, t->srv->queue_mgt); + + return 1; + } + /* last client read and buffer empty */ + /* FIXME!!! here, we don't want to switch to SHUTW if the + * client shuts read too early, because we may still have + * some work to do on the headers. + * The side-effect is that if the client completely closes its + * connection during SV_STHEADER, the connection to the server + * is kept until a response comes back or the timeout is reached. + */ + else if ((/*c == CL_STSHUTR ||*/ c == CL_STCLOSE) && (req->l == 0)) { + FD_CLR(t->srv_fd, StaticWriteEvent); + tv_eternity(&t->swexpire); + + /* We must ensure that the read part is still alive when switching + * to shutw */ + FD_SET(t->srv_fd, StaticReadEvent); + if (t->proxy->srvtimeout) + tv_delayfrom(&t->srexpire, &now, t->proxy->srvtimeout); + + shutdown(t->srv_fd, SHUT_WR); + t->srv_state = SV_STSHUTW; + return 1; + } + /* write timeout */ + /* FIXME!!! here, we don't want to switch to SHUTW if the + * client shuts read too early, because we may still have + * some work to do on the headers. + */ + else if (FD_ISSET(t->srv_fd, StaticWriteEvent) && tv_cmp2_ms(&t->swexpire, &now) <= 0) { + FD_CLR(t->srv_fd, StaticWriteEvent); + tv_eternity(&t->swexpire); + shutdown(t->srv_fd, SHUT_WR); + /* We must ensure that the read part is still alive when switching + * to shutw */ + FD_SET(t->srv_fd, StaticReadEvent); + if (t->proxy->srvtimeout) + tv_delayfrom(&t->srexpire, &now, t->proxy->srvtimeout); + + /* We must ensure that the read part is still alive when switching + * to shutw */ + FD_SET(t->srv_fd, StaticReadEvent); + if (t->proxy->srvtimeout) + tv_delayfrom(&t->srexpire, &now, t->proxy->srvtimeout); + + t->srv_state = SV_STSHUTW; + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_SRVTO; + if (!(t->flags & SN_FINST_MASK)) + t->flags |= SN_FINST_H; + return 1; + } + + if (req->l == 0) { + if (FD_ISSET(t->srv_fd, StaticWriteEvent)) { + FD_CLR(t->srv_fd, StaticWriteEvent); /* stop writing */ + tv_eternity(&t->swexpire); + } + } + else { /* client buffer not empty */ + if (! FD_ISSET(t->srv_fd, StaticWriteEvent)) { + FD_SET(t->srv_fd, StaticWriteEvent); /* restart writing */ + if (t->proxy->srvtimeout) { + tv_delayfrom(&t->swexpire, &now, t->proxy->srvtimeout); + /* FIXME: to prevent the server from expiring read timeouts during writes, + * we refresh it. */ + t->srexpire = t->swexpire; + } + else + tv_eternity(&t->swexpire); + } + } + + /* be nice with the client side which would like to send a complete header + * FIXME: COMPLETELY BUGGY !!! not all headers may be processed because the client + * would read all remaining data at once ! The client should not write past rep->lr + * when the server is in header state. + */ + //return header_processed; + return t->srv_state != SV_STHEADERS; + } + else if (s == SV_STDATA) { + /* read or write error */ + if (t->res_sw == RES_ERROR || t->res_sr == RES_ERROR) { + tv_eternity(&t->srexpire); + tv_eternity(&t->swexpire); + fd_delete(t->srv_fd); + if (t->srv) { + t->srv->cur_sess--; + t->srv->failed_resp++; + } + t->proxy->failed_resp++; + t->srv_state = SV_STCLOSE; + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_SRVCL; + if (!(t->flags & SN_FINST_MASK)) + t->flags |= SN_FINST_D; + /* We used to have a free connection slot. Since we'll never use it, + * we have to inform the server that it may be used by another session. + */ + if (may_dequeue_tasks(t->srv, t->proxy)) + task_wakeup(&rq, t->srv->queue_mgt); + + return 1; + } + /* last read, or end of client write */ + else if (t->res_sr == RES_NULL || c == CL_STSHUTW || c == CL_STCLOSE) { + FD_CLR(t->srv_fd, StaticReadEvent); + tv_eternity(&t->srexpire); + shutdown(t->srv_fd, SHUT_RD); + t->srv_state = SV_STSHUTR; + //fprintf(stderr,"%p:%s(%d), c=%d, s=%d\n", t, __FUNCTION__, __LINE__, t->cli_state, t->cli_state); + return 1; + } + /* end of client read and no more data to send */ + else if ((c == CL_STSHUTR || c == CL_STCLOSE) && (req->l == 0)) { + FD_CLR(t->srv_fd, StaticWriteEvent); + tv_eternity(&t->swexpire); + shutdown(t->srv_fd, SHUT_WR); + /* We must ensure that the read part is still alive when switching + * to shutw */ + FD_SET(t->srv_fd, StaticReadEvent); + if (t->proxy->srvtimeout) + tv_delayfrom(&t->srexpire, &now, t->proxy->srvtimeout); + + t->srv_state = SV_STSHUTW; + return 1; + } + /* read timeout */ + else if (tv_cmp2_ms(&t->srexpire, &now) <= 0) { + FD_CLR(t->srv_fd, StaticReadEvent); + tv_eternity(&t->srexpire); + shutdown(t->srv_fd, SHUT_RD); + t->srv_state = SV_STSHUTR; + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_SRVTO; + if (!(t->flags & SN_FINST_MASK)) + t->flags |= SN_FINST_D; + return 1; + } + /* write timeout */ + else if (tv_cmp2_ms(&t->swexpire, &now) <= 0) { + FD_CLR(t->srv_fd, StaticWriteEvent); + tv_eternity(&t->swexpire); + shutdown(t->srv_fd, SHUT_WR); + /* We must ensure that the read part is still alive when switching + * to shutw */ + FD_SET(t->srv_fd, StaticReadEvent); + if (t->proxy->srvtimeout) + tv_delayfrom(&t->srexpire, &now, t->proxy->srvtimeout); + t->srv_state = SV_STSHUTW; + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_SRVTO; + if (!(t->flags & SN_FINST_MASK)) + t->flags |= SN_FINST_D; + return 1; + } + + /* recompute request time-outs */ + if (req->l == 0) { + if (FD_ISSET(t->srv_fd, StaticWriteEvent)) { + FD_CLR(t->srv_fd, StaticWriteEvent); /* stop writing */ + tv_eternity(&t->swexpire); + } + } + else { /* buffer not empty, there are still data to be transferred */ + if (! FD_ISSET(t->srv_fd, StaticWriteEvent)) { + FD_SET(t->srv_fd, StaticWriteEvent); /* restart writing */ + if (t->proxy->srvtimeout) { + tv_delayfrom(&t->swexpire, &now, t->proxy->srvtimeout); + /* FIXME: to prevent the server from expiring read timeouts during writes, + * we refresh it. */ + t->srexpire = t->swexpire; + } + else + tv_eternity(&t->swexpire); + } + } + + /* recompute response time-outs */ + if (rep->l == BUFSIZE) { /* no room to read more data */ + if (FD_ISSET(t->srv_fd, StaticReadEvent)) { + FD_CLR(t->srv_fd, StaticReadEvent); + tv_eternity(&t->srexpire); + } + } + else { + if (! FD_ISSET(t->srv_fd, StaticReadEvent)) { + FD_SET(t->srv_fd, StaticReadEvent); + if (t->proxy->srvtimeout) + tv_delayfrom(&t->srexpire, &now, t->proxy->srvtimeout); + else + tv_eternity(&t->srexpire); + } + } + + return 0; /* other cases change nothing */ + } + else if (s == SV_STSHUTR) { + if (t->res_sw == RES_ERROR) { + //FD_CLR(t->srv_fd, StaticWriteEvent); + tv_eternity(&t->swexpire); + fd_delete(t->srv_fd); + if (t->srv) { + t->srv->cur_sess--; + t->srv->failed_resp++; + } + t->proxy->failed_resp++; + //close(t->srv_fd); + t->srv_state = SV_STCLOSE; + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_SRVCL; + if (!(t->flags & SN_FINST_MASK)) + t->flags |= SN_FINST_D; + /* We used to have a free connection slot. Since we'll never use it, + * we have to inform the server that it may be used by another session. + */ + if (may_dequeue_tasks(t->srv, t->proxy)) + task_wakeup(&rq, t->srv->queue_mgt); + + return 1; + } + else if ((c == CL_STSHUTR || c == CL_STCLOSE) && (req->l == 0)) { + //FD_CLR(t->srv_fd, StaticWriteEvent); + tv_eternity(&t->swexpire); + fd_delete(t->srv_fd); + if (t->srv) + t->srv->cur_sess--; + //close(t->srv_fd); + t->srv_state = SV_STCLOSE; + /* We used to have a free connection slot. Since we'll never use it, + * we have to inform the server that it may be used by another session. + */ + if (may_dequeue_tasks(t->srv, t->proxy)) + task_wakeup(&rq, t->srv->queue_mgt); + + return 1; + } + else if (tv_cmp2_ms(&t->swexpire, &now) <= 0) { + //FD_CLR(t->srv_fd, StaticWriteEvent); + tv_eternity(&t->swexpire); + fd_delete(t->srv_fd); + if (t->srv) + t->srv->cur_sess--; + //close(t->srv_fd); + t->srv_state = SV_STCLOSE; + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_SRVTO; + if (!(t->flags & SN_FINST_MASK)) + t->flags |= SN_FINST_D; + /* We used to have a free connection slot. Since we'll never use it, + * we have to inform the server that it may be used by another session. + */ + if (may_dequeue_tasks(t->srv, t->proxy)) + task_wakeup(&rq, t->srv->queue_mgt); + + return 1; + } + else if (req->l == 0) { + if (FD_ISSET(t->srv_fd, StaticWriteEvent)) { + FD_CLR(t->srv_fd, StaticWriteEvent); /* stop writing */ + tv_eternity(&t->swexpire); + } + } + else { /* buffer not empty */ + if (! FD_ISSET(t->srv_fd, StaticWriteEvent)) { + FD_SET(t->srv_fd, StaticWriteEvent); /* restart writing */ + if (t->proxy->srvtimeout) { + tv_delayfrom(&t->swexpire, &now, t->proxy->srvtimeout); + /* FIXME: to prevent the server from expiring read timeouts during writes, + * we refresh it. */ + t->srexpire = t->swexpire; + } + else + tv_eternity(&t->swexpire); + } + } + return 0; + } + else if (s == SV_STSHUTW) { + if (t->res_sr == RES_ERROR) { + //FD_CLR(t->srv_fd, StaticReadEvent); + tv_eternity(&t->srexpire); + fd_delete(t->srv_fd); + if (t->srv) { + t->srv->cur_sess--; + t->srv->failed_resp++; + } + t->proxy->failed_resp++; + //close(t->srv_fd); + t->srv_state = SV_STCLOSE; + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_SRVCL; + if (!(t->flags & SN_FINST_MASK)) + t->flags |= SN_FINST_D; + /* We used to have a free connection slot. Since we'll never use it, + * we have to inform the server that it may be used by another session. + */ + if (may_dequeue_tasks(t->srv, t->proxy)) + task_wakeup(&rq, t->srv->queue_mgt); + + return 1; + } + else if (t->res_sr == RES_NULL || c == CL_STSHUTW || c == CL_STCLOSE) { + //FD_CLR(t->srv_fd, StaticReadEvent); + tv_eternity(&t->srexpire); + fd_delete(t->srv_fd); + if (t->srv) + t->srv->cur_sess--; + //close(t->srv_fd); + t->srv_state = SV_STCLOSE; + /* We used to have a free connection slot. Since we'll never use it, + * we have to inform the server that it may be used by another session. + */ + if (may_dequeue_tasks(t->srv, t->proxy)) + task_wakeup(&rq, t->srv->queue_mgt); + + return 1; + } + else if (tv_cmp2_ms(&t->srexpire, &now) <= 0) { + //FD_CLR(t->srv_fd, StaticReadEvent); + tv_eternity(&t->srexpire); + fd_delete(t->srv_fd); + if (t->srv) + t->srv->cur_sess--; + //close(t->srv_fd); + t->srv_state = SV_STCLOSE; + if (!(t->flags & SN_ERR_MASK)) + t->flags |= SN_ERR_SRVTO; + if (!(t->flags & SN_FINST_MASK)) + t->flags |= SN_FINST_D; + /* We used to have a free connection slot. Since we'll never use it, + * we have to inform the server that it may be used by another session. + */ + if (may_dequeue_tasks(t->srv, t->proxy)) + task_wakeup(&rq, t->srv->queue_mgt); + + return 1; + } + else if (rep->l == BUFSIZE) { /* no room to read more data */ + if (FD_ISSET(t->srv_fd, StaticReadEvent)) { + FD_CLR(t->srv_fd, StaticReadEvent); + tv_eternity(&t->srexpire); + } + } + else { + if (! FD_ISSET(t->srv_fd, StaticReadEvent)) { + FD_SET(t->srv_fd, StaticReadEvent); + if (t->proxy->srvtimeout) + tv_delayfrom(&t->srexpire, &now, t->proxy->srvtimeout); + else + tv_eternity(&t->srexpire); + } + } + return 0; + } + else { /* SV_STCLOSE : nothing to do */ + if ((global.mode & MODE_DEBUG) && (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE))) { + int len; + len = sprintf(trash, "%08x:%s.srvcls[%04x:%04x]\n", t->uniq_id, t->proxy->id, (unsigned short)t->cli_fd, (unsigned short)t->srv_fd); + write(1, trash, len); + } + return 0; + } + return 0; +} + + +/* + * Produces data for the session depending on its source. Expects to be + * called with s->cli_state == CL_STSHUTR. Right now, only statistics can be + * produced. It stops by itself by unsetting the SN_SELF_GEN flag from the + * session, which it uses to keep on being called when there is free space in + * the buffer, of simply by letting an empty buffer upon return. It returns 1 + * if it changes the session state from CL_STSHUTR, otherwise 0. + */ +int produce_content(struct session *s) +{ + struct buffer *rep = s->rep; + struct proxy *px; + struct server *sv; + int msglen; + + if (s->data_source == DATA_SRC_NONE) { + s->flags &= ~SN_SELF_GEN; + return 1; + } + else if (s->data_source == DATA_SRC_STATS) { + msglen = 0; + + if (s->data_state == DATA_ST_INIT) { /* the function had not been called yet */ + unsigned int up; + + s->flags |= SN_SELF_GEN; // more data will follow + + /* send the start of the HTTP response */ + msglen += snprintf(trash + msglen, sizeof(trash) - msglen, + "HTTP/1.0 200 OK\r\n" + "Cache-Control: no-cache\r\n" + "Connection: close\r\n" + "\r\n\r\n"); + + s->logs.status = 200; + client_retnclose(s, msglen, trash); // send the start of the response. + msglen = 0; + + if (!(s->flags & SN_ERR_MASK)) // this is not really an error but it is + s->flags |= SN_ERR_PRXCOND; // to mark that it comes from the proxy + if (!(s->flags & SN_FINST_MASK)) + s->flags |= SN_FINST_R; + + /* WARNING! This must fit in the first buffer !!! */ + msglen += snprintf(trash + msglen, sizeof(trash) - msglen, + "Statistics Report for " PRODUCT_NAME "\n" + "\n" + ""); + + if (buffer_write(rep, trash, msglen) != 0) + return 0; + msglen = 0; + + up = (now.tv_sec - start_date.tv_sec); + + /* WARNING! this has to fit the first packet too */ + msglen += snprintf(trash + msglen, sizeof(trash) - msglen, + "

" PRODUCT_NAME "

\n" + "

Statistics Report for pid %d

\n" + "
\n" + "

> General process information

\n" + "
\n" + "

pid = %d (nbproc = %d)
\n" + "uptime = %dd %dh%02dm%02ds
\n" + "system limits : memmax = %s%s ; ulimit-n = %d
\n" + "maxsock = %d
\n" + "maxconn = %d (current conns = %d)
\n" + "

\n" + "\n" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "
 active UP  backup UP
active UP, going down backup UP, going down
active DOWN, going up backup DOWN, going up
active or backup DOWN  not checked
\n" + "
\n" + "", + pid, pid, global.nbproc, + up / 86400, (up % 86400) / 3600, + (up % 3600) / 60, (up % 60), + global.rlimit_memmax ? ultoa(global.rlimit_memmax) : "unlimited", + global.rlimit_memmax ? " MB" : "", + global.rlimit_nofile, + global.maxsock, + global.maxconn, + actconn + ); + + if (buffer_write(rep, trash, msglen) != 0) + return 0; + msglen = 0; + + s->data_state = DATA_ST_DATA; + memset(&s->data_ctx, 0, sizeof(s->data_ctx)); + + px = s->data_ctx.stats.px = proxy; + s->data_ctx.stats.px_st = DATA_ST_INIT; + } + + while (s->data_ctx.stats.px) { + int dispatch_sess, dispatch_cum; + int failed_checks, down_trans; + int failed_secu, failed_conns, failed_resp; + + if (s->data_ctx.stats.px_st == DATA_ST_INIT) { + /* we are on a new proxy */ + px = s->data_ctx.stats.px; + + /* skip the disabled proxies */ + if (px->state == PR_STSTOPPED) + goto next_proxy; + + if (s->proxy->uri_auth && s->proxy->uri_auth->scope) { + /* we have a limited scope, we have to check the proxy name */ + struct stat_scope *scope; + int len; + + len = strlen(px->id); + scope = s->proxy->uri_auth->scope; + + while (scope) { + /* match exact proxy name */ + if (scope->px_len == len && !memcmp(px->id, scope->px_id, len)) + break; + + /* match '.' which means 'self' proxy */ + if (!strcmp(scope->px_id, ".") && px == s->proxy) + break; + scope = scope->next; + } + + /* proxy name not found */ + if (scope == NULL) + goto next_proxy; + } + + msglen += snprintf(trash + msglen, sizeof(trash) - msglen, + "

> Proxy instance %s : " + "%d conns (maxconn=%d), %d queued (%d unassigned), %d total conns

\n" + "", + px->id, + px->nbconn, px->maxconn, px->totpend, px->nbpend, px->cum_conn); + + msglen += snprintf(trash + msglen, sizeof(trash) - msglen, + "\n" + "" + "" + "" + "" + "\n" + "" + "" + "" + "" + "\n"); + + if (buffer_write(rep, trash, msglen) != 0) + return 0; + msglen = 0; + + s->data_ctx.stats.sv = px->srv; + s->data_ctx.stats.px_st = DATA_ST_DATA; + } + + px = s->data_ctx.stats.px; + + /* stats.sv has been initialized above */ + while (s->data_ctx.stats.sv != NULL) { + static char *act_tab_bg[5] = { /*down*/"#FF9090", /*rising*/"#FFD020", /*failing*/"#FFFFA0", /*up*/"#C0FFC0", /*unchecked*/"#E0E0E0" }; + static char *bck_tab_bg[5] = { /*down*/"#FF9090", /*rising*/"#FF80ff", /*failing*/"#C060FF", /*up*/"#B0D0FF", /*unchecked*/"#E0E0E0" }; + static char *srv_hlt_st[5] = { "DOWN", "DN %d/%d ↑", "UP %d/%d ↓", "UP", "no check" }; + int sv_state; /* 0=DOWN, 1=going up, 2=going down, 3=UP */ + + sv = s->data_ctx.stats.sv; + + /* FIXME: produce some small strings for "UP/DOWN x/y &#xxxx;" */ + if (!(sv->state & SRV_CHECKED)) + sv_state = 4; + else if (sv->state & SRV_RUNNING) + if (sv->health == sv->rise + sv->fall - 1) + sv_state = 3; /* UP */ + else + sv_state = 2; /* going down */ + else + if (sv->health) + sv_state = 1; /* going up */ + else + sv_state = 0; /* DOWN */ + + /* name, weight */ + msglen += snprintf(trash, sizeof(trash), + "", + (sv->state & SRV_BACKUP) ? "-" : "Y", + (sv->state & SRV_BACKUP) ? "Y" : "-"); + + /* queue : current, max */ + msglen += snprintf(trash + msglen, sizeof(trash) - msglen, + "", + sv->nbpend, sv->nbpend_max); + + /* sessions : current, max, limit, cumul */ + msglen += snprintf(trash + msglen, sizeof(trash) - msglen, + "", + sv->cur_sess, sv->cur_sess_max, sv->maxconn ? ultoa(sv->maxconn) : "-", sv->cum_sess); + + /* errors : connect, response, security */ + msglen += snprintf(trash + msglen, sizeof(trash) - msglen, + "\n", + sv->failed_conns, sv->failed_resp, sv->failed_secu); + + /* check failures : unique, fatal */ + if (sv->state & SRV_CHECKED) + msglen += snprintf(trash + msglen, sizeof(trash) - msglen, + "\n", + sv->failed_checks, sv->down_trans); + else + msglen += snprintf(trash + msglen, sizeof(trash) - msglen, + "\n"); + + if (buffer_write(rep, trash, msglen) != 0) + return 0; + msglen = 0; + + s->data_ctx.stats.sv = sv->next; + } /* while sv */ + + /* now we are past the last server, we'll dump information about the dispatcher */ + + /* We have to count down from the proxy to the servers to tell how + * many sessions are on the dispatcher, and how many checks have + * failed. We cannot count this during the servers dump because it + * might be interrupted multiple times. + */ + dispatch_sess = px->nbconn; + dispatch_cum = px->cum_conn; + failed_secu = px->failed_secu; + failed_conns = px->failed_conns; + failed_resp = px->failed_resp; + failed_checks = down_trans = 0; + + sv = px->srv; + while (sv) { + dispatch_sess -= sv->cur_sess; + dispatch_cum -= sv->cum_sess; + failed_conns -= sv->failed_conns; + failed_resp -= sv->failed_resp; + failed_secu -= sv->failed_secu; + if (sv->state & SRV_CHECKED) { + failed_checks += sv->failed_checks; + down_trans += sv->down_trans; + } + sv = sv->next; + } + + /* name, weight, status, act, bck */ + msglen += snprintf(trash + msglen, sizeof(trash), + "" + "" + "", + px->state == PR_STRUN ? "UP" : "DOWN"); + + /* queue : current, max */ + msglen += snprintf(trash + msglen, sizeof(trash) - msglen, + "", + px->nbpend, px->nbpend_max); + + /* sessions : current, max, limit, cumul. */ + msglen += snprintf(trash + msglen, sizeof(trash) - msglen, + "", + dispatch_sess, px->nbconn_max, px->maxconn, dispatch_cum); + + /* errors : connect, response, security */ + msglen += snprintf(trash + msglen, sizeof(trash) - msglen, + "\n", + failed_conns, failed_resp, failed_secu); + + /* check failures : unique, fatal */ + msglen += snprintf(trash + msglen, sizeof(trash) - msglen, + "\n"); + + + /* now the summary for the whole proxy */ + /* name, weight, status, act, bck */ + msglen += snprintf(trash + msglen, sizeof(trash), + "" + "" + "", + (px->state == PR_STRUN && ((px->srv == NULL) || px->srv_act || px->srv_bck)) ? "UP" : "DOWN", + px->srv_act, px->srv_bck); + + /* queue : current, max */ + msglen += snprintf(trash + msglen, sizeof(trash) - msglen, + "", + px->totpend, px->nbpend_max); + + /* sessions : current, max, limit, cumul */ + msglen += snprintf(trash + msglen, sizeof(trash) - msglen, + "", + px->nbconn, px->nbconn_max, px->maxconn, px->cum_conn); + + /* errors : connect, response, security */ + msglen += snprintf(trash + msglen, sizeof(trash) - msglen, + "\n", + px->failed_conns, px->failed_resp, px->failed_secu); + + /* check failures : unique, fatal */ + msglen += snprintf(trash + msglen, sizeof(trash) - msglen, + "\n", + failed_checks, down_trans); + + msglen += snprintf(trash + msglen, sizeof(trash) - msglen, "
ServerQueueSessionsErrors
NameWeightStatusAct.Bck.Curr.Max.Curr.Max.LimitCumul.Conn.Resp.Sec.CheckDown
%s%d", + (sv->state & SRV_BACKUP) ? bck_tab_bg[sv_state] : act_tab_bg[sv_state], + sv->id, sv->uweight+1); + /* status */ + msglen += snprintf(trash + msglen, sizeof(trash) - msglen, srv_hlt_st[sv_state], + (sv->state & SRV_RUNNING) ? (sv->health - sv->rise + 1) : (sv->health), + (sv->state & SRV_RUNNING) ? (sv->fall) : (sv->rise)); + + /* act, bck */ + msglen += snprintf(trash + msglen, sizeof(trash) - msglen, + "%s%s%d%d%d%d%s%d%d%d%d%d%d
--
Dispatcher-%s--%d%d%d%d%d%d%d%d%d--
Total-%s%d%d%d%d%d%d%d%d%d%d%d%d%d

\n"); + + if (buffer_write(rep, trash, msglen) != 0) + return 0; + msglen = 0; + + s->data_ctx.stats.px_st = DATA_ST_INIT; + next_proxy: + s->data_ctx.stats.px = px->next; + } /* proxy loop */ + /* here, we just have reached the sv == NULL and px == NULL */ + s->flags &= ~SN_SELF_GEN; + return 1; + } + else { + /* unknown data source */ + s->logs.status = 500; + client_retnclose(s, s->proxy->errmsg.len500, s->proxy->errmsg.msg500); + if (!(s->flags & SN_ERR_MASK)) + s->flags |= SN_ERR_PRXCOND; + if (!(s->flags & SN_FINST_MASK)) + s->flags |= SN_FINST_R; + s->flags &= SN_SELF_GEN; + return 1; + } +} + + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/proxy.c b/src/proxy.c new file mode 100644 index 000000000..b1beb4b74 --- /dev/null +++ b/src/proxy.c @@ -0,0 +1,357 @@ +/* + * Proxy variables and functions. + * + * Copyright 2000-2006 Willy Tarreau + * + * 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 +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + + +int listeners; /* # of listeners */ +struct proxy *proxy = NULL; /* list of all existing proxies */ + + +/* + * this function starts all the proxies. Its return value is composed from + * ERR_NONE, ERR_RETRYABLE and ERR_FATAL. Retryable errors will only be printed + * if is not zero. + */ +int start_proxies(int verbose) +{ + struct proxy *curproxy; + struct listener *listener; + int err = ERR_NONE; + int fd, pxerr; + + for (curproxy = proxy; curproxy != NULL; curproxy = curproxy->next) { + if (curproxy->state != PR_STNEW) + continue; /* already initialized */ + + pxerr = 0; + for (listener = curproxy->listen; listener != NULL; listener = listener->next) { + if (listener->fd != -1) + continue; /* already initialized */ + + if ((fd = socket(listener->addr.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) { + if (verbose) + Alert("cannot create listening socket for proxy %s. Aborting.\n", + curproxy->id); + err |= ERR_RETRYABLE; + pxerr |= 1; + continue; + } + + if (fd >= global.maxsock) { + Alert("socket(): not enough free sockets for proxy %s. Raise -n argument. Aborting.\n", + curproxy->id); + close(fd); + err |= ERR_FATAL; + pxerr |= 1; + break; + } + + if ((fcntl(fd, F_SETFL, O_NONBLOCK) == -1) || + (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, + (char *) &one, sizeof(one)) == -1)) { + Alert("cannot make socket non-blocking for proxy %s. Aborting.\n", + curproxy->id); + close(fd); + err |= ERR_FATAL; + pxerr |= 1; + break; + } + + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(one)) == -1) { + Alert("cannot do so_reuseaddr for proxy %s. Continuing.\n", + curproxy->id); + } + +#ifdef SO_REUSEPORT + /* OpenBSD supports this. As it's present in old libc versions of Linux, + * it might return an error that we will silently ignore. + */ + setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (char *) &one, sizeof(one)); +#endif + if (bind(fd, + (struct sockaddr *)&listener->addr, + listener->addr.ss_family == AF_INET6 ? + sizeof(struct sockaddr_in6) : + sizeof(struct sockaddr_in)) == -1) { + if (verbose) + Alert("cannot bind socket for proxy %s. Aborting.\n", + curproxy->id); + close(fd); + err |= ERR_RETRYABLE; + pxerr |= 1; + continue; + } + + if (listen(fd, curproxy->maxconn) == -1) { + if (verbose) + Alert("cannot listen to socket for proxy %s. Aborting.\n", + curproxy->id); + close(fd); + err |= ERR_RETRYABLE; + pxerr |= 1; + continue; + } + + /* the socket is ready */ + listener->fd = fd; + + /* the function for the accept() event */ + fdtab[fd].read = &event_accept; + fdtab[fd].write = NULL; /* never called */ + fdtab[fd].owner = (struct task *)curproxy; /* reference the proxy instead of a task */ + fdtab[fd].state = FD_STLISTEN; + FD_SET(fd, StaticReadEvent); + fd_insert(fd); + listeners++; + } + + if (!pxerr) { + curproxy->state = PR_STRUN; + send_log(curproxy, LOG_NOTICE, "Proxy %s started.\n", curproxy->id); + } + } + + return err; +} + + +/* + * this function enables proxies when there are enough free sessions, + * or stops them when the table is full. It is designed to be called from the + * select_loop(). It returns the time left before next expiration event + * during stop time, TIME_ETERNITY otherwise. + */ +int maintain_proxies(void) +{ + struct proxy *p; + struct listener *l; + int tleft; /* time left */ + + p = proxy; + tleft = TIME_ETERNITY; /* infinite time */ + + /* if there are enough free sessions, we'll activate proxies */ + if (actconn < global.maxconn) { + while (p) { + if (p->nbconn < p->maxconn) { + if (p->state == PR_STIDLE) { + for (l = p->listen; l != NULL; l = l->next) { + FD_SET(l->fd, StaticReadEvent); + } + p->state = PR_STRUN; + } + } + else { + if (p->state == PR_STRUN) { + for (l = p->listen; l != NULL; l = l->next) { + FD_CLR(l->fd, StaticReadEvent); + } + p->state = PR_STIDLE; + } + } + p = p->next; + } + } + else { /* block all proxies */ + while (p) { + if (p->state == PR_STRUN) { + for (l = p->listen; l != NULL; l = l->next) { + FD_CLR(l->fd, StaticReadEvent); + } + p->state = PR_STIDLE; + } + p = p->next; + } + } + + if (stopping) { + p = proxy; + while (p) { + if (p->state != PR_STSTOPPED) { + int t; + t = tv_remain2(&now, &p->stop_time); + if (t == 0) { + Warning("Proxy %s stopped.\n", p->id); + send_log(p, LOG_WARNING, "Proxy %s stopped.\n", p->id); + + for (l = p->listen; l != NULL; l = l->next) { + fd_delete(l->fd); + listeners--; + } + p->state = PR_STSTOPPED; + } + else { + tleft = MINTIME(t, tleft); + } + } + p = p->next; + } + } + return tleft; +} + + +/* + * this function disables health-check servers so that the process will quickly be ignored + * by load balancers. Note that if a proxy was already in the PAUSED state, then its grace + * time will not be used since it would already not listen anymore to the socket. + */ +void soft_stop(void) +{ + struct proxy *p; + + stopping = 1; + p = proxy; + tv_now(&now); /* else, the old time before select will be used */ + while (p) { + if (p->state != PR_STSTOPPED) { + Warning("Stopping proxy %s in %d ms.\n", p->id, p->grace); + send_log(p, LOG_WARNING, "Stopping proxy %s in %d ms.\n", p->id, p->grace); + tv_delayfrom(&p->stop_time, &now, p->grace); + } + p = p->next; + } +} + + +/* + * Linux unbinds the listen socket after a SHUT_RD, and ignores SHUT_WR. + * Solaris refuses either shutdown(). + * OpenBSD ignores SHUT_RD but closes upon SHUT_WR and refuses to rebind. + * So a common validation path involves SHUT_WR && listen && SHUT_RD. + * If disabling at least one listener returns an error, then the proxy + * state is set to PR_STERROR because we don't know how to resume from this. + */ +void pause_proxy(struct proxy *p) +{ + struct listener *l; + for (l = p->listen; l != NULL; l = l->next) { + if (shutdown(l->fd, SHUT_WR) == 0 && + listen(l->fd, p->maxconn) == 0 && + shutdown(l->fd, SHUT_RD) == 0) { + FD_CLR(l->fd, StaticReadEvent); + if (p->state != PR_STERROR) + p->state = PR_STPAUSED; + } + else + p->state = PR_STERROR; + } +} + +/* + * This function temporarily disables listening so that another new instance + * can start listening. It is designed to be called upon reception of a + * SIGTTOU, after which either a SIGUSR1 can be sent to completely stop + * the proxy, or a SIGTTIN can be sent to listen again. + */ +void pause_proxies(void) +{ + int err; + struct proxy *p; + + err = 0; + p = proxy; + tv_now(&now); /* else, the old time before select will be used */ + while (p) { + if (p->state != PR_STERROR && + p->state != PR_STSTOPPED && + p->state != PR_STPAUSED) { + Warning("Pausing proxy %s.\n", p->id); + send_log(p, LOG_WARNING, "Pausing proxy %s.\n", p->id); + pause_proxy(p); + if (p->state != PR_STPAUSED) { + err |= 1; + Warning("Proxy %s failed to enter pause mode.\n", p->id); + send_log(p, LOG_WARNING, "Proxy %s failed to enter pause mode.\n", p->id); + } + } + p = p->next; + } + if (err) { + Warning("Some proxies refused to pause, performing soft stop now.\n"); + send_log(p, LOG_WARNING, "Some proxies refused to pause, performing soft stop now.\n"); + soft_stop(); + } +} + + +/* + * This function reactivates listening. This can be used after a call to + * sig_pause(), for example when a new instance has failed starting up. + * It is designed to be called upon reception of a SIGTTIN. + */ +void listen_proxies(void) +{ + struct proxy *p; + struct listener *l; + + p = proxy; + tv_now(&now); /* else, the old time before select will be used */ + while (p) { + if (p->state == PR_STPAUSED) { + Warning("Enabling proxy %s.\n", p->id); + send_log(p, LOG_WARNING, "Enabling proxy %s.\n", p->id); + + for (l = p->listen; l != NULL; l = l->next) { + if (listen(l->fd, p->maxconn) == 0) { + if (actconn < global.maxconn && p->nbconn < p->maxconn) { + FD_SET(l->fd, StaticReadEvent); + p->state = PR_STRUN; + } + else + p->state = PR_STIDLE; + } else { + int port; + + if (l->addr.ss_family == AF_INET6) + port = ntohs(((struct sockaddr_in6 *)(&l->addr))->sin6_port); + else + port = ntohs(((struct sockaddr_in *)(&l->addr))->sin_port); + + Warning("Port %d busy while trying to enable proxy %s.\n", + port, p->id); + send_log(p, LOG_WARNING, "Port %d busy while trying to enable proxy %s.\n", + port, p->id); + /* Another port might have been enabled. Let's stop everything. */ + pause_proxy(p); + break; + } + } + } + p = p->next; + } +} + + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/queue.c b/src/queue.c new file mode 100644 index 000000000..67c0d7cc6 --- /dev/null +++ b/src/queue.c @@ -0,0 +1,139 @@ +/* + * Queue management functions. + * + * Copyright 2000-2006 Willy Tarreau + * + * 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 +#include + +#include +#include +#include + + +void **pool_pendconn = NULL; + +/* returns the effective dynamic maxconn for a server, considering the minconn + * and the proxy's usage relative to its saturation. + */ +unsigned int srv_dynamic_maxconn(struct server *s) +{ + return s->minconn ? + ((s->maxconn * s->proxy->nbconn / s->proxy->maxconn) < s->minconn) ? s->minconn : + (s->maxconn * s->proxy->nbconn / s->proxy->maxconn) : s->maxconn; +} + + +/* + * Manages a server's connection queue. If woken up, will try to dequeue as + * many pending sessions as possible, and wake them up. The task has nothing + * else to do, so it always returns TIME_ETERNITY. + */ +int process_srv_queue(struct task *t) +{ + struct server *s = (struct server*)t->context; + struct proxy *p = s->proxy; + int xferred; + + /* First, check if we can handle some connections queued at the proxy. We + * will take as many as we can handle. + */ + for (xferred = 0; s->cur_sess + xferred < srv_dynamic_maxconn(s); xferred++) { + struct session *sess; + + sess = pendconn_get_next_sess(s, p); + if (sess == NULL) + break; + task_wakeup(&rq, sess->task); + } + + return TIME_ETERNITY; +} + +/* Detaches the next pending connection from either a server or a proxy, and + * returns its associated session. If no pending connection is found, NULL is + * returned. Note that neither nor can be NULL. + */ +struct session *pendconn_get_next_sess(struct server *srv, struct proxy *px) +{ + struct pendconn *p; + struct session *sess; + + p = pendconn_from_srv(srv); + if (!p) { + p = pendconn_from_px(px); + if (!p) + return NULL; + p->sess->srv = srv; + } + sess = p->sess; + pendconn_free(p); + return sess; +} + +/* Adds the session to the pending connection list of server ->srv + * or to the one of ->proxy if srv is NULL. All counters and back pointers + * are updated accordingly. Returns NULL if no memory is available, otherwise the + * pendconn itself. + */ +struct pendconn *pendconn_add(struct session *sess) +{ + struct pendconn *p; + + p = pool_alloc(pendconn); + if (!p) + return NULL; + + sess->pend_pos = p; + p->sess = sess; + p->srv = sess->srv; + if (sess->srv) { + LIST_ADDQ(&sess->srv->pendconns, &p->list); + sess->logs.srv_queue_size += sess->srv->nbpend; + sess->srv->nbpend++; + if (sess->srv->nbpend > sess->srv->nbpend_max) + sess->srv->nbpend_max = sess->srv->nbpend; + } else { + LIST_ADDQ(&sess->proxy->pendconns, &p->list); + sess->logs.prx_queue_size += sess->proxy->nbpend; + sess->proxy->nbpend++; + if (sess->proxy->nbpend > sess->proxy->nbpend_max) + sess->proxy->nbpend_max = sess->proxy->nbpend; + } + sess->proxy->totpend++; + return p; +} + +/* + * Detaches pending connection

, decreases the pending count, and frees + * the pending connection. The connection might have been queued to a specific + * server as well as to the proxy. The session also gets marked unqueued. + */ +void pendconn_free(struct pendconn *p) +{ + LIST_DEL(&p->list); + p->sess->pend_pos = NULL; + if (p->srv) + p->srv->nbpend--; + else + p->sess->proxy->nbpend--; + p->sess->proxy->totpend--; + pool_free(pendconn, p); +} + + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/regex.c b/src/regex.c new file mode 100644 index 000000000..4c3776012 --- /dev/null +++ b/src/regex.c @@ -0,0 +1,130 @@ +/* + * Regex and string management functions. + * + * Copyright 2000-2006 Willy Tarreau + * + * 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 +#include + +#include +#include +#include + +/* regex trash buffer used by various regex tests */ +regmatch_t pmatch[MAX_MATCH]; /* rm_so, rm_eo for regular expressions */ + + + +int exp_replace(char *dst, char *src, char *str, regmatch_t *matches) +{ + char *old_dst = dst; + + while (*str) { + if (*str == '\\') { + str++; + if (isdigit((int)*str)) { + int len, num; + + num = *str - '0'; + str++; + + if (matches[num].rm_eo > -1 && matches[num].rm_so > -1) { + len = matches[num].rm_eo - matches[num].rm_so; + memcpy(dst, src + matches[num].rm_so, len); + dst += len; + } + + } else if (*str == 'x') { + unsigned char hex1, hex2; + str++; + + hex1 = toupper(*str++) - '0'; + hex2 = toupper(*str++) - '0'; + + if (hex1 > 9) hex1 -= 'A' - '9' - 1; + if (hex2 > 9) hex2 -= 'A' - '9' - 1; + *dst++ = (hex1<<4) + hex2; + } else { + *dst++ = *str++; + } + } else { + *dst++ = *str++; + } + } + *dst = '\0'; + return dst - old_dst; +} + +/* returns NULL if the replacement string is valid, or the pointer to the first error */ +char *check_replace_string(char *str) +{ + char *err = NULL; + while (*str) { + if (*str == '\\') { + err = str; /* in case of a backslash, we return the pointer to it */ + str++; + if (!*str) + return err; + else if (isdigit((int)*str)) + err = NULL; + else if (*str == 'x') { + str++; + if (!ishex(*str)) + return err; + str++; + if (!ishex(*str)) + return err; + err = NULL; + } + else { + Warning("'\\%c' : deprecated use of a backslash before something not '\\','x' or a digit.\n", *str); + err = NULL; + } + } + str++; + } + return err; +} + + +/* returns the pointer to an error in the replacement string, or NULL if OK */ +char *chain_regex(struct hdr_exp **head, regex_t *preg, int action, char *replace) +{ + struct hdr_exp *exp; + + if (replace != NULL) { + char *err; + err = check_replace_string(replace); + if (err) + return err; + } + + while (*head != NULL) + head = &(*head)->next; + + exp = calloc(1, sizeof(struct hdr_exp)); + + exp->preg = preg; + exp->replace = replace; + exp->action = action; + *head = exp; + + return NULL; +} + + + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/server.c b/src/server.c new file mode 100644 index 000000000..50c025d3f --- /dev/null +++ b/src/server.c @@ -0,0 +1,23 @@ +/* + * Server management functions. + * + * Copyright 2000-2006 Willy Tarreau + * + * 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 +#include + + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/session.c b/src/session.c new file mode 100644 index 000000000..0722d0f4c --- /dev/null +++ b/src/session.c @@ -0,0 +1,73 @@ +/* + * Server management functions. + * + * Copyright 2000-2006 Willy Tarreau + * + * 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 + +#include +#include +#include +#include +#include + +#include +#include + + +void **pool_session = NULL; + +/* + * frees the context associated to a session. It must have been removed first. + */ +void session_free(struct session *s) +{ + if (s->pend_pos) + pendconn_free(s->pend_pos); + if (s->req) + pool_free(buffer, s->req); + if (s->rep) + pool_free(buffer, s->rep); + + if (s->rsp_cap != NULL) { + struct cap_hdr *h; + for (h = s->proxy->rsp_cap; h; h = h->next) { + if (s->rsp_cap[h->index] != NULL) + pool_free_to(h->pool, s->rsp_cap[h->index]); + } + pool_free_to(s->proxy->rsp_cap_pool, s->rsp_cap); + } + if (s->req_cap != NULL) { + struct cap_hdr *h; + for (h = s->proxy->req_cap; h; h = h->next) { + if (s->req_cap[h->index] != NULL) + pool_free_to(h->pool, s->req_cap[h->index]); + } + pool_free_to(s->proxy->req_cap_pool, s->req_cap); + } + + if (s->logs.uri) + pool_free(requri, s->logs.uri); + if (s->logs.cli_cookie) + pool_free(capture, s->logs.cli_cookie); + if (s->logs.srv_cookie) + pool_free(capture, s->logs.srv_cookie); + + pool_free(session, s); +} + + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/standard.c b/src/standard.c new file mode 100644 index 000000000..e218055fa --- /dev/null +++ b/src/standard.c @@ -0,0 +1,214 @@ +/* + * General purpose functions. + * + * Copyright 2000-2006 Willy Tarreau + * + * 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 +#include +#include +#include + +#include +#include + +/* enough to store 2^63=18446744073709551615 */ +static char itoa_str[21]; + +/* + * copies at most chars from to . Last char is always + * set to 0, unless is 0. The number of chars copied is returned + * (excluding the terminating zero). + * This code has been optimized for size and speed : on x86, it's 45 bytes + * long, uses only registers, and consumes only 4 cycles per char. + */ +int strlcpy2(char *dst, const char *src, int size) +{ + char *orig = dst; + if (size) { + while (--size && (*dst = *src)) { + src++; dst++; + } + *dst = 0; + } + return dst - orig; +} + +/* + * This function simply returns a statically allocated string containing + * the ascii representation for number 'n' in decimal. + */ +char *ultoa(unsigned long n) +{ + char *pos; + + pos = itoa_str + sizeof(itoa_str) - 1; + *pos-- = '\0'; + + do { + *pos-- = '0' + n % 10; + n /= 10; + } while (n && pos >= itoa_str); + return pos + 1; +} + + +/* + * Returns non-zero if character is a hex digit (0-9, a-f, A-F), else zero. + * + * It looks like this one would be a good candidate for inlining, but this is + * not interesting because it around 35 bytes long and often called multiple + * times within the same function. + */ +int ishex(char s) +{ + s -= '0'; + if ((unsigned char)s <= 9) + return 1; + s -= 'A' - '0'; + if ((unsigned char)s <= 5) + return 1; + s -= 'a' - 'A'; + if ((unsigned char)s <= 5) + return 1; + return 0; +} + + +/* + * converts to a struct sockaddr_in* which is locally allocated. + * The format is "addr:port", where "addr" can be a dotted IPv4 address, + * a host name, or empty or "*" to indicate INADDR_ANY. + */ +struct sockaddr_in *str2sa(char *str) +{ + static struct sockaddr_in sa; + char *c; + int port; + + memset(&sa, 0, sizeof(sa)); + str = strdup(str); + + if ((c = strrchr(str,':')) != NULL) { + *c++ = '\0'; + port = atol(c); + } + else + port = 0; + + if (*str == '*' || *str == '\0') { /* INADDR_ANY */ + sa.sin_addr.s_addr = INADDR_ANY; + } + else if (!inet_pton(AF_INET, str, &sa.sin_addr)) { + struct hostent *he; + + if ((he = gethostbyname(str)) == NULL) { + Alert("Invalid server name: '%s'\n", str); + } + else + sa.sin_addr = *(struct in_addr *) *(he->h_addr_list); + } + sa.sin_port = htons(port); + sa.sin_family = AF_INET; + + free(str); + return &sa; +} + +/* + * converts to a two struct in_addr* which are locally allocated. + * The format is "addr[/mask]", where "addr" cannot be empty, and mask + * is optionnal and either in the dotted or CIDR notation. + * Note: "addr" can also be a hostname. Returns 1 if OK, 0 if error. + */ +int str2net(char *str, struct in_addr *addr, struct in_addr *mask) +{ + char *c; + unsigned long len; + + memset(mask, 0, sizeof(*mask)); + memset(addr, 0, sizeof(*addr)); + str = strdup(str); + + if ((c = strrchr(str, '/')) != NULL) { + *c++ = '\0'; + /* c points to the mask */ + if (strchr(c, '.') != NULL) { /* dotted notation */ + if (!inet_pton(AF_INET, c, mask)) + return 0; + } + else { /* mask length */ + char *err; + len = strtol(c, &err, 10); + if (!*c || (err && *err) || (unsigned)len > 32) + return 0; + if (len) + mask->s_addr = htonl(~0UL << (32 - len)); + else + mask->s_addr = 0; + } + } + else { + mask->s_addr = ~0UL; + } + if (!inet_pton(AF_INET, str, addr)) { + struct hostent *he; + + if ((he = gethostbyname(str)) == NULL) { + return 0; + } + else + *addr = *(struct in_addr *) *(he->h_addr_list); + } + free(str); + return 1; +} + +/* will try to encode the string replacing all characters tagged in + * with the hexadecimal representation of their ASCII-code (2 digits) + * prefixed by , and will store the result between (included) + * and (excluded), and will always terminate the string with a '\0' + * before . The position of the '\0' is returned if the conversion + * completes. If bytes are missing between and , then the + * conversion will be incomplete and truncated. If <= , the '\0' + * cannot even be stored so we return without writing the 0. + * The input string must also be zero-terminated. + */ +const char hextab[16] = "0123456789ABCDEF"; +char *encode_string(char *start, char *stop, + const char escape, const fd_set *map, + const char *string) +{ + if (start < stop) { + stop--; /* reserve one byte for the final '\0' */ + while (start < stop && *string != '\0') { + if (!FD_ISSET((unsigned char)(*string), map)) + *start++ = *string; + else { + if (start + 3 >= stop) + break; + *start++ = escape; + *start++ = hextab[(*string >> 4) & 15]; + *start++ = hextab[*string & 15]; + } + string++; + } + *start = '\0'; + } + return start; +} + + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/stream_sock.c b/src/stream_sock.c new file mode 100644 index 000000000..e30546117 --- /dev/null +++ b/src/stream_sock.c @@ -0,0 +1,462 @@ +/* + * Functions operating on SOCK_STREAM and buffers. + * + * Copyright 2000-2006 Willy Tarreau + * + * 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 +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +/* + * this function is called on a read event from a client socket. + * It returns 0. + */ +int event_cli_read(int fd) { + struct task *t = fdtab[fd].owner; + struct session *s = t->context; + struct buffer *b = s->req; + int ret, max; + +#ifdef DEBUG_FULL + fprintf(stderr,"event_cli_read : fd=%d, s=%p\n", fd, s); +#endif + + if (fdtab[fd].state != FD_STERROR) { +#ifdef FILL_BUFFERS + while (1) +#else + do +#endif + { + if (b->l == 0) { /* let's realign the buffer to optimize I/O */ + b->r = b->w = b->h = b->lr = b->data; + max = b->rlim - b->data; + } + else if (b->r > b->w) { + max = b->rlim - b->r; + } + else { + max = b->w - b->r; + /* FIXME: theorically, if w>0, we shouldn't have rlim < data+size anymore + * since it means that the rewrite protection has been removed. This + * implies that the if statement can be removed. + */ + if (max > b->rlim - b->data) + max = b->rlim - b->data; + } + + if (max == 0) { /* not anymore room to store data */ + FD_CLR(fd, StaticReadEvent); + break; + } + +#ifndef MSG_NOSIGNAL + { + int skerr; + socklen_t lskerr = sizeof(skerr); + + getsockopt(fd, SOL_SOCKET, SO_ERROR, &skerr, &lskerr); + if (skerr) + ret = -1; + else + ret = recv(fd, b->r, max, 0); + } +#else + ret = recv(fd, b->r, max, MSG_NOSIGNAL); +#endif + if (ret > 0) { + b->r += ret; + b->l += ret; + s->res_cr = RES_DATA; + + if (b->r == b->data + BUFSIZE) { + b->r = b->data; /* wrap around the buffer */ + } + + b->total += ret; + /* we hope to read more data or to get a close on next round */ + continue; + } + else if (ret == 0) { + s->res_cr = RES_NULL; + break; + } + else if (errno == EAGAIN) {/* ignore EAGAIN */ + break; + } + else { + s->res_cr = RES_ERROR; + fdtab[fd].state = FD_STERROR; + break; + } + } /* while(1) */ +#ifndef FILL_BUFFERS + while (0); +#endif + } + else { + s->res_cr = RES_ERROR; + fdtab[fd].state = FD_STERROR; + } + + if (s->res_cr != RES_SILENT) { + if (s->proxy->clitimeout && FD_ISSET(fd, StaticReadEvent)) + tv_delayfrom(&s->crexpire, &now, s->proxy->clitimeout); + else + tv_eternity(&s->crexpire); + + task_wakeup(&rq, t); + } + + return 0; +} + + +/* + * this function is called on a write event from a client socket. + * It returns 0. + */ +int event_cli_write(int fd) { + struct task *t = fdtab[fd].owner; + struct session *s = t->context; + struct buffer *b = s->rep; + int ret, max; + +#ifdef DEBUG_FULL + fprintf(stderr,"event_cli_write : fd=%d, s=%p\n", fd, s); +#endif + + if (b->l == 0) { /* let's realign the buffer to optimize I/O */ + b->r = b->w = b->h = b->lr = b->data; + // max = BUFSIZE; BUG !!!! + max = 0; + } + else if (b->r > b->w) { + max = b->r - b->w; + } + else + max = b->data + BUFSIZE - b->w; + + if (fdtab[fd].state != FD_STERROR) { + if (max == 0) { + s->res_cw = RES_NULL; + task_wakeup(&rq, t); + tv_eternity(&s->cwexpire); + FD_CLR(fd, StaticWriteEvent); + return 0; + } + +#ifndef MSG_NOSIGNAL + { + int skerr; + socklen_t lskerr = sizeof(skerr); + + getsockopt(fd, SOL_SOCKET, SO_ERROR, &skerr, &lskerr); + if (skerr) + ret = -1; + else + ret = send(fd, b->w, max, MSG_DONTWAIT); + } +#else + ret = send(fd, b->w, max, MSG_DONTWAIT | MSG_NOSIGNAL); +#endif + + if (ret > 0) { + b->l -= ret; + b->w += ret; + + s->res_cw = RES_DATA; + + if (b->w == b->data + BUFSIZE) { + b->w = b->data; /* wrap around the buffer */ + } + } + else if (ret == 0) { + /* nothing written, just make as if we were never called */ + // s->res_cw = RES_NULL; + return 0; + } + else if (errno == EAGAIN) /* ignore EAGAIN */ + return 0; + else { + s->res_cw = RES_ERROR; + fdtab[fd].state = FD_STERROR; + } + } + else { + s->res_cw = RES_ERROR; + fdtab[fd].state = FD_STERROR; + } + + if (s->proxy->clitimeout) { + tv_delayfrom(&s->cwexpire, &now, s->proxy->clitimeout); + /* FIXME: to prevent the client from expiring read timeouts during writes, + * we refresh it. A solution would be to merge read+write timeouts into a + * unique one, although that needs some study particularly on full-duplex + * TCP connections. */ + s->crexpire = s->cwexpire; + } + else + tv_eternity(&s->cwexpire); + + task_wakeup(&rq, t); + return 0; +} + + +/* + * this function is called on a read event from a server socket. + * It returns 0. + */ +int event_srv_read(int fd) { + struct task *t = fdtab[fd].owner; + struct session *s = t->context; + struct buffer *b = s->rep; + int ret, max; + +#ifdef DEBUG_FULL + fprintf(stderr,"event_srv_read : fd=%d, s=%p\n", fd, s); +#endif + + if (fdtab[fd].state != FD_STERROR) { +#ifdef FILL_BUFFERS + while (1) +#else + do +#endif + { + if (b->l == 0) { /* let's realign the buffer to optimize I/O */ + b->r = b->w = b->h = b->lr = b->data; + max = b->rlim - b->data; + } + else if (b->r > b->w) { + max = b->rlim - b->r; + } + else { + max = b->w - b->r; + /* FIXME: theorically, if w>0, we shouldn't have rlim < data+size anymore + * since it means that the rewrite protection has been removed. This + * implies that the if statement can be removed. + */ + if (max > b->rlim - b->data) + max = b->rlim - b->data; + } + + if (max == 0) { /* not anymore room to store data */ + FD_CLR(fd, StaticReadEvent); + break; + } + +#ifndef MSG_NOSIGNAL + { + int skerr; + socklen_t lskerr = sizeof(skerr); + + getsockopt(fd, SOL_SOCKET, SO_ERROR, &skerr, &lskerr); + if (skerr) + ret = -1; + else + ret = recv(fd, b->r, max, 0); + } +#else + ret = recv(fd, b->r, max, MSG_NOSIGNAL); +#endif + if (ret > 0) { + b->r += ret; + b->l += ret; + s->res_sr = RES_DATA; + + if (b->r == b->data + BUFSIZE) { + b->r = b->data; /* wrap around the buffer */ + } + + b->total += ret; + /* we hope to read more data or to get a close on next round */ + continue; + } + else if (ret == 0) { + s->res_sr = RES_NULL; + break; + } + else if (errno == EAGAIN) {/* ignore EAGAIN */ + break; + } + else { + s->res_sr = RES_ERROR; + fdtab[fd].state = FD_STERROR; + break; + } + } /* while(1) */ +#ifndef FILL_BUFFERS + while (0); +#endif + } + else { + s->res_sr = RES_ERROR; + fdtab[fd].state = FD_STERROR; + } + + if (s->res_sr != RES_SILENT) { + if (s->proxy->srvtimeout && FD_ISSET(fd, StaticReadEvent)) + tv_delayfrom(&s->srexpire, &now, s->proxy->srvtimeout); + else + tv_eternity(&s->srexpire); + + task_wakeup(&rq, t); + } + + return 0; +} + + +/* + * this function is called on a write event from a server socket. + * It returns 0. + */ +int event_srv_write(int fd) { + struct task *t = fdtab[fd].owner; + struct session *s = t->context; + struct buffer *b = s->req; + int ret, max; + +#ifdef DEBUG_FULL + fprintf(stderr,"event_srv_write : fd=%d, s=%p\n", fd, s); +#endif + + if (b->l == 0) { /* let's realign the buffer to optimize I/O */ + b->r = b->w = b->h = b->lr = b->data; + // max = BUFSIZE; BUG !!!! + max = 0; + } + else if (b->r > b->w) { + max = b->r - b->w; + } + else + max = b->data + BUFSIZE - b->w; + + if (fdtab[fd].state != FD_STERROR) { + if (max == 0) { + /* may be we have received a connection acknowledgement in TCP mode without data */ + if (s->srv_state == SV_STCONN) { + int skerr; + socklen_t lskerr = sizeof(skerr); + getsockopt(fd, SOL_SOCKET, SO_ERROR, &skerr, &lskerr); + if (skerr) { + s->res_sw = RES_ERROR; + fdtab[fd].state = FD_STERROR; + task_wakeup(&rq, t); + tv_eternity(&s->swexpire); + FD_CLR(fd, StaticWriteEvent); + return 0; + } + } + + s->res_sw = RES_NULL; + task_wakeup(&rq, t); + fdtab[fd].state = FD_STREADY; + tv_eternity(&s->swexpire); + FD_CLR(fd, StaticWriteEvent); + return 0; + } + +#ifndef MSG_NOSIGNAL + { + int skerr; + socklen_t lskerr = sizeof(skerr); + getsockopt(fd, SOL_SOCKET, SO_ERROR, &skerr, &lskerr); + if (skerr) + ret = -1; + else + ret = send(fd, b->w, max, MSG_DONTWAIT); + } +#else + ret = send(fd, b->w, max, MSG_DONTWAIT | MSG_NOSIGNAL); +#endif + fdtab[fd].state = FD_STREADY; + if (ret > 0) { + b->l -= ret; + b->w += ret; + + s->res_sw = RES_DATA; + + if (b->w == b->data + BUFSIZE) { + b->w = b->data; /* wrap around the buffer */ + } + } + else if (ret == 0) { + /* nothing written, just make as if we were never called */ + // s->res_sw = RES_NULL; + return 0; + } + else if (errno == EAGAIN) /* ignore EAGAIN */ + return 0; + else { + s->res_sw = RES_ERROR; + fdtab[fd].state = FD_STERROR; + } + } + else { + s->res_sw = RES_ERROR; + fdtab[fd].state = FD_STERROR; + } + + /* We don't want to re-arm read/write timeouts if we're trying to connect, + * otherwise it could loop indefinitely ! + */ + if (s->srv_state != SV_STCONN) { + if (s->proxy->srvtimeout) { + tv_delayfrom(&s->swexpire, &now, s->proxy->srvtimeout); + /* FIXME: to prevent the server from expiring read timeouts during writes, + * we refresh it. A solution would be to merge read+write+connect timeouts + * into a unique one since we don't mind expiring on read or write, and none + * of them is enabled while waiting for connect(), although that needs some + * study particularly on full-duplex TCP connections. */ + s->srexpire = s->swexpire; + } + else + tv_eternity(&s->swexpire); + } + + task_wakeup(&rq, t); + return 0; +} + + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/task.c b/src/task.c new file mode 100644 index 000000000..2c4adee3e --- /dev/null +++ b/src/task.c @@ -0,0 +1,201 @@ +/* + * Task management functions. + * + * Copyright 2000-2006 Willy Tarreau + * + * 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 +#include + +#include + + +/* FIXME : this should be removed very quickly ! */ +extern int maintain_proxies(void); + +void **pool_task= NULL; +struct task *rq = NULL; /* global run queue */ +struct task wait_queue[2] = { /* global wait queue */ + { + prev:LIST_HEAD(wait_queue[0]), /* expirable tasks */ + next:LIST_HEAD(wait_queue[0]), + }, + { + prev:LIST_HEAD(wait_queue[1]), /* non-expirable tasks */ + next:LIST_HEAD(wait_queue[1]), + }, +}; + + +/* inserts into its assigned wait queue, where it may already be. In this case, it + * may be only moved or left where it was, depending on its timing requirements. + * is returned. + */ +struct task *task_queue(struct task *task) +{ + struct task *list = task->wq; + struct task *start_from; + + /* This is a very dirty hack to queue non-expirable tasks in another queue + * in order to avoid pulluting the tail of the standard queue. This will go + * away with the new O(log(n)) scheduler anyway. + */ + if (tv_iseternity(&task->expire)) { + /* if the task was queued in the standard wait queue, we must dequeue it */ + if (task->prev) { + if (task->wq == LIST_HEAD(wait_queue[1])) + return task; + else { + task_delete(task); + task->prev = NULL; + } + } + list = task->wq = LIST_HEAD(wait_queue[1]); + } else { + /* if the task was queued in the eternity queue, we must dequeue it */ + if (task->prev && (task->wq == LIST_HEAD(wait_queue[1]))) { + task_delete(task); + task->prev = NULL; + list = task->wq = LIST_HEAD(wait_queue[0]); + } + } + + /* next, test if the task was already in a list */ + if (task->prev == NULL) { + // start_from = list; + start_from = list->prev; + /* insert the unlinked into the list, searching back from the last entry */ + while (start_from != list && tv_cmp2(&task->expire, &start_from->expire) < 0) { + start_from = start_from->prev; + } + + // while (start_from->next != list && tv_cmp2(&task->expire, &start_from->next->expire) > 0) { + // start_from = start_from->next; + // stats_tsk_nsrch++; + // } + } + else if (task->prev == list || + tv_cmp2(&task->expire, &task->prev->expire) >= 0) { /* walk right */ + start_from = task->next; + if (start_from == list || tv_cmp2(&task->expire, &start_from->expire) <= 0) { + return task; /* it's already in the right place */ + } + + /* if the task is not at the right place, there's little chance that + * it has only shifted a bit, and it will nearly always be queued + * at the end of the list because of constant timeouts + * (observed in real case). + */ +#ifndef WE_REALLY_THINK_THAT_THIS_TASK_MAY_HAVE_SHIFTED + start_from = list->prev; /* assume we'll queue to the end of the list */ + while (start_from != list && tv_cmp2(&task->expire, &start_from->expire) < 0) { + start_from = start_from->prev; + } +#else /* WE_REALLY_... */ + /* insert the unlinked into the list, searching after position */ + while (start_from->next != list && tv_cmp2(&task->expire, &start_from->next->expire) > 0) { + start_from = start_from->next; + } +#endif /* WE_REALLY_... */ + + /* we need to unlink it now */ + task_delete(task); + } + else { /* walk left. */ +#ifdef LEFT_TO_TOP /* not very good */ + start_from = list; + while (start_from->next != list && tv_cmp2(&task->expire, &start_from->next->expire) > 0) { + start_from = start_from->next; + } +#else + start_from = task->prev->prev; /* valid because of the previous test above */ + while (start_from != list && tv_cmp2(&task->expire, &start_from->expire) < 0) { + start_from = start_from->prev; + } +#endif + /* we need to unlink it now */ + task_delete(task); + } + task->prev = start_from; + task->next = start_from->next; + task->next->prev = task; + start_from->next = task; + return task; +} + +/* + * This does 4 things : + * - wake up all expired tasks + * - call all runnable tasks + * - call maintain_proxies() to enable/disable the listeners + * - return the delay till next event in ms, -1 = wait indefinitely + * Note: this part should be rewritten with the O(ln(n)) scheduler. + * + */ + +int process_runnable_tasks() +{ + int next_time; + int time2; + struct task *t, *tnext; + + next_time = TIME_ETERNITY; /* set the timer to wait eternally first */ + + /* look for expired tasks and add them to the run queue. + */ + tnext = ((struct task *)LIST_HEAD(wait_queue[0]))->next; + while ((t = tnext) != LIST_HEAD(wait_queue[0])) { /* we haven't looped ? */ + tnext = t->next; + if (t->state & TASK_RUNNING) + continue; + + if (tv_iseternity(&t->expire)) + continue; + + /* wakeup expired entries. It doesn't matter if they are + * already running because of a previous event + */ + if (tv_cmp_ms(&t->expire, &now) <= 0) { + task_wakeup(&rq, t); + } + else { + /* first non-runnable task. Use its expiration date as an upper bound */ + int temp_time = tv_remain(&now, &t->expire); + if (temp_time) + next_time = temp_time; + break; + } + } + + /* process each task in the run queue now. Each task may be deleted + * since we only use the run queue's head. Note that any task can be + * woken up by any other task and it will be processed immediately + * after as it will be queued on the run queue's head. + */ + while ((t = rq) != NULL) { + int temp_time; + + task_sleep(&rq, t); + temp_time = t->process(t); + next_time = MINTIME(temp_time, next_time); + } + + /* maintain all proxies in a consistent state. This should quickly become a task */ + time2 = maintain_proxies(); + return MINTIME(time2, next_time); +} + + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/time.c b/src/time.c new file mode 100644 index 000000000..d495ed166 --- /dev/null +++ b/src/time.c @@ -0,0 +1,172 @@ +/* + * Time calculation functions. + * + * Copyright 2000-2006 Willy Tarreau + * + * 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 + +struct timeval now; /* the current date at any moment */ +struct timeval start_date; /* the process's start date */ + +/* + * adds ms to , set the result to and returns a pointer + */ +struct timeval *tv_delayfrom(struct timeval *tv, struct timeval *from, int ms) +{ + if (!tv || !from) + return NULL; + tv->tv_usec = from->tv_usec + (ms%1000)*1000; + tv->tv_sec = from->tv_sec + (ms/1000); + while (tv->tv_usec >= 1000000) { + tv->tv_usec -= 1000000; + tv->tv_sec++; + } + return tv; +} + +/* + * compares and modulo 1ms: returns 0 if equal, -1 if tv1 < tv2, 1 if tv1 > tv2 + * Must not be used when either argument is eternity. Use tv_cmp2_ms() for that. + */ +int tv_cmp_ms(struct timeval *tv1, struct timeval *tv2) +{ + if (tv1->tv_sec == tv2->tv_sec) { + if (tv2->tv_usec >= tv1->tv_usec + 1000) + return -1; + else if (tv1->tv_usec >= tv2->tv_usec + 1000) + return 1; + else + return 0; + } + else if ((tv2->tv_sec > tv1->tv_sec + 1) || + ((tv2->tv_sec == tv1->tv_sec + 1) && (tv2->tv_usec + 1000000 >= tv1->tv_usec + 1000))) + return -1; + else if ((tv1->tv_sec > tv2->tv_sec + 1) || + ((tv1->tv_sec == tv2->tv_sec + 1) && (tv1->tv_usec + 1000000 >= tv2->tv_usec + 1000))) + return 1; + else + return 0; +} + +/* + * compares and : returns 0 if equal, -1 if tv1 < tv2, 1 if tv1 > tv2, + * considering that 0 is the eternity. + */ +int tv_cmp2(struct timeval *tv1, struct timeval *tv2) +{ + if (tv_iseternity(tv1)) + if (tv_iseternity(tv2)) + return 0; /* same */ + else + return 1; /* tv1 later than tv2 */ + else if (tv_iseternity(tv2)) + return -1; /* tv2 later than tv1 */ + + if (tv1->tv_sec > tv2->tv_sec) + return 1; + else if (tv1->tv_sec < tv2->tv_sec) + return -1; + else if (tv1->tv_usec > tv2->tv_usec) + return 1; + else if (tv1->tv_usec < tv2->tv_usec) + return -1; + else + return 0; +} + +/* + * compares and modulo 1 ms: returns 0 if equal, -1 if tv1 < tv2, 1 if tv1 > tv2, + * considering that 0 is the eternity. + */ +int tv_cmp2_ms(struct timeval *tv1, struct timeval *tv2) +{ + if (tv_iseternity(tv1)) + if (tv_iseternity(tv2)) + return 0; /* same */ + else + return 1; /* tv1 later than tv2 */ + else if (tv_iseternity(tv2)) + return -1; /* tv2 later than tv1 */ + + if (tv1->tv_sec == tv2->tv_sec) { + if (tv1->tv_usec >= tv2->tv_usec + 1000) + return 1; + else if (tv2->tv_usec >= tv1->tv_usec + 1000) + return -1; + else + return 0; + } + else if ((tv1->tv_sec > tv2->tv_sec + 1) || + ((tv1->tv_sec == tv2->tv_sec + 1) && (tv1->tv_usec + 1000000 >= tv2->tv_usec + 1000))) + return 1; + else if ((tv2->tv_sec > tv1->tv_sec + 1) || + ((tv2->tv_sec == tv1->tv_sec + 1) && (tv2->tv_usec + 1000000 >= tv1->tv_usec + 1000))) + return -1; + else + return 0; +} + +/* + * returns the remaining time between tv1=now and event=tv2 + * if tv2 is passed, 0 is returned. + * Returns TIME_ETERNITY if tv2 is eternity. + */ +unsigned long tv_remain2(struct timeval *tv1, struct timeval *tv2) +{ + unsigned long ret; + + if (tv_iseternity(tv2)) + return TIME_ETERNITY; + + if (tv_cmp_ms(tv1, tv2) >= 0) + return 0; /* event elapsed */ + + ret = (tv2->tv_sec - tv1->tv_sec) * 1000; + if (tv2->tv_usec > tv1->tv_usec) + ret += (tv2->tv_usec - tv1->tv_usec) / 1000; + else + ret -= (tv1->tv_usec - tv2->tv_usec) / 1000; + return (unsigned long) ret; +} + + +/* + * returns the absolute difference, in ms, between tv1 and tv2 + * Must not be used when either argument is eternity. + */ +unsigned long tv_delta(struct timeval *tv1, struct timeval *tv2) +{ + int cmp; + unsigned long ret; + + + cmp = tv_cmp(tv1, tv2); + if (!cmp) + return 0; /* same dates, null diff */ + else if (cmp < 0) { + struct timeval *tmp = tv1; + tv1 = tv2; + tv2 = tmp; + } + ret = (tv1->tv_sec - tv2->tv_sec) * 1000; + if (tv1->tv_usec > tv2->tv_usec) + ret += (tv1->tv_usec - tv2->tv_usec) / 1000; + else + ret -= (tv2->tv_usec - tv1->tv_usec) / 1000; + return (unsigned long) ret; +} + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/uri_auth.c b/src/uri_auth.c index 84570efd2..498d73512 100644 --- a/src/uri_auth.c +++ b/src/uri_auth.c @@ -1,7 +1,7 @@ /* * URI-based user authentication using the HTTP basic method. * - * Copyright 2006 Willy Tarreau + * Copyright 2006 Willy Tarreau * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -12,8 +12,9 @@ #include #include -#include -#include + +#include +#include /*