Created
April 15, 2013 20:54
-
-
Save seveas/5391216 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 4a9745b590c9912d5e1b6e139f0dcbe374196fad Mon Sep 17 00:00:00 2001 | |
From: Dennis Kaarsemaker <[email protected]> | |
Date: Thu, 8 Nov 2012 10:07:57 +0100 | |
Subject: [PATCH] Add a start delay option that allows healthchecks to run | |
early | |
When restarting haproxy with the -sf/-st options, the restart is almost | |
seamless. The problem is that all state (admin down via socket, | |
monitoring down) gets lost. This delay allows for health checks to | |
happen before the listeners start and the older process gets killed, | |
thus reducing the chance of errors. | |
--- | |
src/haproxy.c | 144 +++++++++++++++++++++++++++++++++++----------------------- | |
1 file changed, 86 insertions(+), 58 deletions(-) | |
diff --git a/src/haproxy.c b/src/haproxy.c | |
index 7f6a299..3ce3b6b 100644 | |
--- a/src/haproxy.c | |
+++ b/src/haproxy.c | |
@@ -148,6 +148,8 @@ const int one = 1; | |
const struct linger nolinger = { .l_onoff = 1, .l_linger = 0 }; | |
char hostname[MAX_HOSTNAME_LEN]; | |
+static char *progname; | |
+static int cfg_delay = 0; | |
/*********************************************************************/ | |
@@ -205,12 +207,13 @@ void usage(char *name) | |
display_version(); | |
fprintf(stderr, | |
"Usage : %s [-f <cfgfile>]* [ -vdV" | |
- "D ] [ -n <maxconn> ] [ -N <maxpconn> ]\n" | |
+ "D ] [ -n <maxconn> ] [ -N <maxpconn> ] [-T <delay> ]\n" | |
" [ -p <pidfile> ] [ -m <max megs> ]\n" | |
" -v displays version ; -vv shows known build options.\n" | |
" -d enters debug mode ; -db only disables background mode.\n" | |
" -V enters verbose mode (disables quiet mode)\n" | |
" -D goes daemon\n" | |
+ " -T wait some ticks (ms) after starting health checks\n" | |
" -q quiet mode : don't display messages\n" | |
" -c check mode : only check config files and exit\n" | |
" -n sets the maximum total # of connections (%d)\n" | |
@@ -396,7 +399,6 @@ void init(int argc, char **argv) | |
char *cfg_pidfile = NULL; | |
int err_code = 0; | |
struct wordlist *wl; | |
- char *progname; | |
/* | |
* Initialize the previously static variables. | |
@@ -529,6 +531,7 @@ void init(int argc, char **argv) | |
LIST_ADDQ(&cfg_cfgfiles, &wl->list); | |
break; | |
case 'p' : cfg_pidfile = *argv; break; | |
+ case 'T' : cfg_delay = atol(*argv); break; | |
default: usage(progname); | |
} | |
} | |
@@ -997,53 +1000,10 @@ void run_poll_loop() | |
} | |
-int main(int argc, char **argv) | |
-{ | |
- int err, retry; | |
+struct task *start(struct task *t) { | |
struct rlimit limit; | |
+ int err, retry; | |
FILE *pidfile = NULL; | |
- init(argc, argv); | |
- | |
- signal_register(SIGQUIT, dump); | |
- signal_register(SIGUSR1, sig_soft_stop); | |
- signal_register(SIGHUP, sig_dump_state); | |
-#ifdef DEBUG_MEMORY | |
- signal_register(SIGINT, sig_int); | |
- signal_register(SIGTERM, sig_term); | |
-#endif | |
- | |
- /* Always catch SIGPIPE even on platforms which define MSG_NOSIGNAL. | |
- * Some recent FreeBSD setups report broken pipes, and MSG_NOSIGNAL | |
- * was defined there, so let's stay on the safe side. | |
- */ | |
- signal(SIGPIPE, SIG_IGN); | |
- | |
- /* 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 | |
- } | |
/* 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 | |
@@ -1086,14 +1046,14 @@ int main(int argc, char **argv) | |
} | |
if (listeners == 0) { | |
- Alert("[%s.main()] No enabled listener found (check the <listen> keywords) ! Exiting.\n", argv[0]); | |
+ Alert("[%s.main()] No enabled listener found (check the <listen> keywords) ! Exiting.\n", progname); | |
/* Note: we don't have to send anything to the old pids because we | |
* never stopped them. */ | |
exit(1); | |
} | |
if ((protocol_bind_all() & ~ERR_WARN) != ERR_NONE) { | |
- Alert("[%s.main()] Some protocols failed to start their listeners! Exiting.\n", argv[0]); | |
+ Alert("[%s.main()] Some protocols failed to start their listeners! Exiting.\n", progname); | |
protocol_unbind_all(); /* cleanup everything we can */ | |
if (nb_oldpids) | |
tell_old_pids(SIGTTIN); | |
@@ -1118,7 +1078,7 @@ int main(int argc, char **argv) | |
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); | |
+ Alert("[%s.main()] Cannot create pidfile %s\n", progname, global.pidfile); | |
if (nb_oldpids) | |
tell_old_pids(SIGTTIN); | |
protocol_unbind_all(); | |
@@ -1134,7 +1094,7 @@ int main(int argc, char **argv) | |
ret = check_cttproxy_version(); | |
if (ret < 0) { | |
Alert("[%s.main()] Cannot enable cttproxy.\n%s", | |
- argv[0], | |
+ progname, | |
(ret == -1) ? " Incorrect module version.\n" | |
: " Make sure you have enough permissions and that the module is loaded.\n"); | |
protocol_unbind_all(); | |
@@ -1145,7 +1105,7 @@ int main(int argc, char **argv) | |
if ((global.last_checks & LSTCHK_NETADM) && global.uid) { | |
Alert("[%s.main()] Some configuration options require full privileges, so global.uid cannot be changed.\n" | |
- "", argv[0]); | |
+ "", progname); | |
protocol_unbind_all(); | |
exit(1); | |
} | |
@@ -1156,12 +1116,12 @@ int main(int argc, char **argv) | |
if ((global.last_checks & LSTCHK_NETADM) && getuid()) | |
Warning("[%s.main()] Some options which require full privileges" | |
" might not work well.\n" | |
- "", argv[0]); | |
+ "", progname); | |
/* chroot if needed */ | |
if (global.chroot != NULL) { | |
if (chroot(global.chroot) == -1) { | |
- Alert("[%s.main()] Cannot chroot(%s).\n", argv[0], global.chroot); | |
+ Alert("[%s.main()] Cannot chroot(%s).\n", progname, global.chroot); | |
if (nb_oldpids) | |
tell_old_pids(SIGTTIN); | |
protocol_unbind_all(); | |
@@ -1179,13 +1139,13 @@ int main(int argc, char **argv) | |
/* setgid / setuid */ | |
if (global.gid && setgid(global.gid) == -1) { | |
- Alert("[%s.main()] Cannot set gid %d.\n", argv[0], global.gid); | |
+ Alert("[%s.main()] Cannot set gid %d.\n", progname, global.gid); | |
protocol_unbind_all(); | |
exit(1); | |
} | |
if (global.uid && setuid(global.uid) == -1) { | |
- Alert("[%s.main()] Cannot set uid %d.\n", argv[0], global.uid); | |
+ Alert("[%s.main()] Cannot set uid %d.\n", progname, global.uid); | |
protocol_unbind_all(); | |
exit(1); | |
} | |
@@ -1195,7 +1155,7 @@ int main(int argc, char **argv) | |
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], (int)limit.rlim_cur, global.maxconn, global.maxsock, global.maxsock); | |
+ progname, (int)limit.rlim_cur, global.maxconn, global.maxsock, global.maxsock); | |
} | |
if (global.mode & MODE_DAEMON) { | |
@@ -1207,7 +1167,7 @@ int main(int argc, char **argv) | |
for (proc = 0; proc < global.nbproc; proc++) { | |
ret = fork(); | |
if (ret < 0) { | |
- Alert("[%s.main()] Cannot fork.\n", argv[0]); | |
+ Alert("[%s.main()] Cannot fork.\n", progname); | |
protocol_unbind_all(); | |
exit(1); /* there has been an error */ | |
} | |
@@ -1258,6 +1218,74 @@ int main(int argc, char **argv) | |
} | |
protocol_enable_all(); | |
+ if(t) { | |
+ task_free(t); | |
+ } | |
+ return NULL; | |
+} | |
+ | |
+ | |
+int main(int argc, char **argv) | |
+{ | |
+ struct rlimit limit; | |
+ struct task *t; | |
+ init(argc, argv); | |
+ | |
+ signal_register(SIGQUIT, dump); | |
+ signal_register(SIGUSR1, sig_soft_stop); | |
+ signal_register(SIGHUP, sig_dump_state); | |
+#ifdef DEBUG_MEMORY | |
+ signal_register(SIGINT, sig_int); | |
+ signal_register(SIGTERM, sig_term); | |
+#endif | |
+ | |
+ /* Always catch SIGPIPE even on platforms which define MSG_NOSIGNAL. | |
+ * Some recent FreeBSD setups report broken pipes, and MSG_NOSIGNAL | |
+ * was defined there, so let's stay on the safe side. | |
+ */ | |
+ signal(SIGPIPE, SIG_IGN); | |
+ | |
+ /* 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", progname, 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", | |
+ progname, global.rlimit_memmax); | |
+ } | |
+#else | |
+ if (setrlimit(RLIMIT_DATA, &limit) == -1) { | |
+ Warning("[%s.main()] Cannot fix MEM limit to %d megs.\n", | |
+ progname, global.rlimit_memmax); | |
+ } | |
+#endif | |
+ } | |
+ | |
+ /* | |
+ * Start all proxies, maybe after a little wait to let the | |
+ * first health checks run | |
+ */ | |
+ if(cfg_delay) { | |
+ t = task_new(); | |
+ t->process = start; | |
+ t->expire = tick_add(now_ms, MS_TO_TICKS(cfg_delay)); | |
+ task_queue(t); | |
+ } | |
+ else { | |
+ start(NULL); | |
+ } | |
+ | |
/* | |
* That's it : the central polling loop. Run until we stop. | |
*/ | |
-- | |
1.8.2.4.g940421e | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 7f6b1a953d42456b9b23819e05cc7eb90a70ce4a Mon Sep 17 00:00:00 2001 | |
From: Dennis Kaarsemaker <[email protected]> | |
Date: Mon, 17 Sep 2012 13:55:16 +0200 | |
Subject: [PATCH] Turn haproxy into a DNS server | |
With a config entry like the following | |
listen test-dns | |
log global | |
mode dns | |
bind *:53 | |
haproxy will now act as a DNS server, allowing you to use it to balance | |
load without concentrating traffic. Of course only the (weighted) round | |
robin and (weighted) random algorithms make sense here as haproxy knows | |
nothing about actual connections. | |
To get an answer from haproxy, request an A record for | |
<frontend name>.<random domain>. It will respond with an IP address if a | |
server is available, NXDOMAIN if the frontend you asked about does not | |
exist and SERVFAIL if the frontend has no available backends. | |
--- | |
Makefile | 3 +- | |
include/proto/proto_dns.h | 42 ++++ | |
include/types/fd.h | 1 + | |
include/types/proxy.h | 3 +- | |
src/cfgparse.c | 11 +- | |
src/proto_dns.c | 499 ++++++++++++++++++++++++++++++++++++++++++++++ | |
src/proxy.c | 7 +- | |
7 files changed, 561 insertions(+), 5 deletions(-) | |
create mode 100644 include/proto/proto_dns.h | |
create mode 100644 src/proto_dns.c | |
diff --git a/Makefile b/Makefile | |
index 8def6d3..7d313e3 100644 | |
--- a/Makefile | |
+++ b/Makefile | |
@@ -494,7 +494,8 @@ OBJS = src/haproxy.o src/sessionhash.o src/base64.o src/protocols.o \ | |
src/lb_chash.o src/lb_fwlc.o src/lb_fwrr.o src/lb_map.o \ | |
src/stream_interface.o src/dumpstats.o src/proto_tcp.o \ | |
src/session.o src/hdr_idx.o src/ev_select.o src/signal.o \ | |
- src/acl.o src/pattern.o src/memory.o src/freq_ctr.o src/auth.o | |
+ src/acl.o src/pattern.o src/memory.o src/freq_ctr.o src/auth.o \ | |
+ src/proto_dns.o | |
EBTREE_OBJS = $(EBTREE_DIR)/ebtree.o \ | |
$(EBTREE_DIR)/eb32tree.o $(EBTREE_DIR)/eb64tree.o \ | |
diff --git a/include/proto/proto_dns.h b/include/proto/proto_dns.h | |
new file mode 100644 | |
index 0000000..a7b0c60 | |
--- /dev/null | |
+++ b/include/proto/proto_dns.h | |
@@ -0,0 +1,42 @@ | |
+/* | |
+ * include/proto/proto_dns.h | |
+ * This file contains loadbalanced DNS protocol definitions. | |
+ * | |
+ * Copyright (C) 2000-2010 Willy Tarreau - [email protected] | |
+ * | |
+ * 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_TCP_H | |
+#define _PROTO_PROTO_TCP_H | |
+ | |
+#include <common/config.h> | |
+#include <types/proto_tcp.h> | |
+#include <types/task.h> | |
+ | |
+int dns_event_accept(int fd); | |
+int dnsv4_bind_socket(int fd, int flags, struct sockaddr_in *local, struct sockaddr_in *remote); | |
+void dnsv4_add_listener(struct listener *listener); | |
+void dnsv6_add_listener(struct listener *listener); | |
+int dns_bind_listener(struct listener *listener, char *errmsg, int errlen); | |
+ | |
+#endif /* _PROTO_PROTO_TCP_H */ | |
+ | |
+/* | |
+ * Local variables: | |
+ * c-indent-level: 8 | |
+ * c-basic-offset: 8 | |
+ * End: | |
+ */ | |
diff --git a/include/types/fd.h b/include/types/fd.h | |
index f698863..60b74c1 100644 | |
--- a/include/types/fd.h | |
+++ b/include/types/fd.h | |
@@ -64,6 +64,7 @@ enum { | |
#define FD_FL_TCP 0x0001 /* socket is TCP */ | |
#define FD_FL_TCP_NODELAY 0x0002 | |
#define FD_FL_TCP_NOLING 0x0004 /* lingering disabled */ | |
+#define FD_FL_UDP 0x0008 /* socket is UDP */ | |
/* info about one given fd */ | |
struct fdtab { | |
diff --git a/include/types/proxy.h b/include/types/proxy.h | |
index 4312e0f..9db3460 100644 | |
--- a/include/types/proxy.h | |
+++ b/include/types/proxy.h | |
@@ -60,6 +60,7 @@ | |
#define PR_MODE_TCP 0 | |
#define PR_MODE_HTTP 1 | |
#define PR_MODE_HEALTH 2 | |
+#define PR_MODE_DNS 3 | |
/* flag values for proxy->cap. This is a bitmask of capabilities supported by the proxy */ | |
#define PR_CAP_NONE 0x0000 | |
@@ -181,7 +182,7 @@ struct proxy { | |
int options2; /* PR_O2_* */ | |
unsigned int fe_req_ana, be_req_ana; /* bitmap of common request protocol analysers for the frontend and backend */ | |
unsigned int fe_rsp_ana, be_rsp_ana; /* bitmap of common response protocol analysers for the frontend and backend */ | |
- int mode; /* mode = PR_MODE_TCP, PR_MODE_HTTP or PR_MODE_HEALTH */ | |
+ int mode; /* mode = PR_MODE_TCP, PR_MODE_HTTP, PR_MODE_HEALTH or PR_MODE_DNS */ | |
struct sockaddr_in dispatch_addr; /* the default address to connect to */ | |
union { | |
struct proxy *be; /* default backend, or NULL if none set */ | |
diff --git a/src/cfgparse.c b/src/cfgparse.c | |
index e7e8602..b8ff39b 100644 | |
--- a/src/cfgparse.c | |
+++ b/src/cfgparse.c | |
@@ -261,10 +261,16 @@ static int str2listener(char *str, struct proxy *curproxy) | |
if (ss.ss_family == AF_INET6) { | |
((struct sockaddr_in6 *)(&l->addr))->sin6_port = htons(port); | |
- tcpv6_add_listener(l); | |
+ if(curproxy->mode == PR_MODE_DNS) | |
+ dnsv6_add_listener(l); | |
+ else | |
+ tcpv6_add_listener(l); | |
} else { | |
((struct sockaddr_in *)(&l->addr))->sin_port = htons(port); | |
- tcpv4_add_listener(l); | |
+ if(curproxy->mode == PR_MODE_DNS) | |
+ dnsv4_add_listener(l); | |
+ else | |
+ tcpv4_add_listener(l); | |
} | |
listeners++; | |
@@ -1560,6 +1566,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) | |
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 if (!strcmp(args[1], "dns")) curproxy->mode = PR_MODE_DNS; | |
else { | |
Alert("parsing [%s:%d] : unknown proxy mode '%s'.\n", file, linenum, args[1]); | |
err_code |= ERR_ALERT | ERR_FATAL; | |
diff --git a/src/proto_dns.c b/src/proto_dns.c | |
new file mode 100644 | |
index 0000000..d143c63 | |
--- /dev/null | |
+++ b/src/proto_dns.c | |
@@ -0,0 +1,499 @@ | |
+/* | |
+ * DNS protocol layer | |
+ * | |
+ * Copyright (C) 2000-2010 Willy Tarreau - [email protected] | |
+ * | |
+ * 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 <ctype.h> | |
+#include <errno.h> | |
+#include <fcntl.h> | |
+#include <stdio.h> | |
+#include <stdlib.h> | |
+#include <string.h> | |
+#include <time.h> | |
+ | |
+#include <sys/param.h> | |
+#include <sys/socket.h> | |
+#include <sys/stat.h> | |
+#include <sys/types.h> | |
+#include <sys/un.h> | |
+ | |
+#include <netinet/udp.h> | |
+ | |
+#include <common/cfgparse.h> | |
+#include <common/compat.h> | |
+#include <common/config.h> | |
+#include <common/debug.h> | |
+#include <common/errors.h> | |
+#include <common/memory.h> | |
+#include <common/mini-clist.h> | |
+#include <common/standard.h> | |
+#include <common/time.h> | |
+#include <common/version.h> | |
+ | |
+#include <types/global.h> | |
+#include <types/server.h> | |
+ | |
+#include <proto/acl.h> | |
+#include <proto/backend.h> | |
+#include <proto/buffers.h> | |
+#include <proto/checks.h> | |
+#include <proto/fd.h> | |
+#include <proto/log.h> | |
+#include <proto/port_range.h> | |
+#include <proto/protocols.h> | |
+#include <proto/proto_dns.h> | |
+#include <proto/proxy.h> | |
+#include <proto/queue.h> | |
+#include <proto/session.h> | |
+#include <proto/stream_sock.h> | |
+#include <proto/task.h> | |
+ | |
+#define ID(buf) (ntohs(*(uint16_t*)(buf))) | |
+#define QR(buf) ((buf[2] >> 7) & 0x01) | |
+#define OPCODE(buf) ((buf[2] >> 3) & 0x0F) | |
+#define AA(buf) ((buf[2] >> 2) & 0x01) | |
+#define TC(buf) ((buf[2] >> 1) & 0x01) | |
+#define RD(buf) ((buf[2] ) & 0x01) | |
+#define RA(buf) ((buf[3] >> 7) & 0x01) | |
+#define Z(buf) ((buf[3] >> 4) & 0x07) | |
+#define RCODE(buf) ((buf[3] ) & 0x0f) | |
+#define QDCOUNT(buf) (ntohs(*(uint16_t*)(buf+4))) | |
+#define ANCOUNT(buf) (ntohs(*(uint16_t*)(buf+6))) | |
+#define NSCOUNT(buf) (ntohs(*(uint16_t*)(buf+8))) | |
+#define ARCOUNT(buf) (ntohs(*(uint16_t*)(buf+10))) | |
+ | |
+#define SET_ID(buf, val) (*(uint16_t*)(buf) = htons(val)) | |
+#define SET_QR(buf, val) (buf[2] |= ((val & 0x01) << 7)) | |
+#define SET_OPCODE(buf, val) (buf[2] |= ((val & 0x0f) << 3)) | |
+#define SET_AA(buf, val) (buf[2] |= ((val & 0x01) << 2)) | |
+#define SET_TC(buf, val) (buf[2] |= ((val & 0x01) << 1)) | |
+#define SET_RD(buf, val) (buf[2] |= ((val & 0x01) )) | |
+#define SET_RA(buf, val) (buf[3] |= ((val & 0x01) << 7)) | |
+#define SET_Z(buf, val) (buf[3] |= ((val & 0x07) << 4)) | |
+#define SET_RCODE(buf, val) (buf[3] |= ((val & 0x0f) )) | |
+#define SET_QDCOUNT(buf, val) (*(uint16_t*)(buf+4) = htons(val)) | |
+#define SET_ANCOUNT(buf, val) (*(uint16_t*)(buf+6) = htons(val)) | |
+#define SET_NSCOUNT(buf, val) (*(uint16_t*)(buf+8) = htons(val)) | |
+#define SET_ARCOUNT(buf, val) (*(uint16_t*)(buf+10) = htons(val)) | |
+ | |
+static int dns_bind_listeners(struct protocol *proto); | |
+ | |
+/* Note: must not be declared <const> as its list will be overwritten */ | |
+static struct protocol proto_dnsv4 = { | |
+ .name = "dnsv4", | |
+ .sock_domain = AF_INET, | |
+ .sock_type = SOCK_DGRAM, | |
+ .sock_prot = IPPROTO_UDP, | |
+ .sock_family = AF_INET, | |
+ .sock_addrlen = sizeof(struct sockaddr_in), | |
+ .l3_addrlen = 32/8, | |
+ .bind_all = dns_bind_listeners, | |
+ .unbind_all = unbind_all_listeners, | |
+ .enable_all = enable_all_listeners, | |
+ .listeners = LIST_HEAD_INIT(proto_dnsv4.listeners), | |
+ .nb_listeners = 0, | |
+}; | |
+ | |
+/* Note: must not be declared <const> as its list will be overwritten */ | |
+static struct protocol proto_dnsv6 = { | |
+ .name = "dnsv6", | |
+ .sock_domain = AF_INET6, | |
+ .sock_type = SOCK_DGRAM, | |
+ .sock_prot = IPPROTO_UDP, | |
+ .sock_family = AF_INET6, | |
+ .sock_addrlen = sizeof(struct sockaddr_in6), | |
+ .l3_addrlen = 128/8, | |
+ .bind_all = dns_bind_listeners, | |
+ .unbind_all = unbind_all_listeners, | |
+ .enable_all = enable_all_listeners, | |
+ .listeners = LIST_HEAD_INIT(proto_dnsv6.listeners), | |
+ .nb_listeners = 0, | |
+}; | |
+ | |
+ | |
+/* Binds ipv4 address <local> to socket <fd>, unless <flags> is set, in which | |
+ * case we try to bind <remote>. <flags> is a 2-bit field consisting of : | |
+ * - 0 : ignore remote address (may even be a NULL pointer) | |
+ * - 1 : use provided address | |
+ * - 2 : use provided port | |
+ * - 3 : use both | |
+ * | |
+ * The function supports multiple foreign binding methods : | |
+ * - linux_tproxy: we directly bind to the foreign address | |
+ * - cttproxy: we bind to a local address then nat. | |
+ * The second one can be used as a fallback for the first one. | |
+ * This function returns 0 when everything's OK, 1 if it could not bind, to the | |
+ * local address, 2 if it could not bind to the foreign address. | |
+ */ | |
+int dnsv4_bind_socket(int fd, int flags, struct sockaddr_in *local, struct sockaddr_in *remote) | |
+{ | |
+ struct sockaddr_in bind_addr; | |
+ int foreign_ok = 0; | |
+ int ret; | |
+ | |
+ if (flags) { | |
+ memset(&bind_addr, 0, sizeof(bind_addr)); | |
+ bind_addr.sin_family = AF_INET; | |
+ if (flags & 1) | |
+ bind_addr.sin_addr = remote->sin_addr; | |
+ if (flags & 2) | |
+ bind_addr.sin_port = remote->sin_port; | |
+ } | |
+ | |
+ setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(one)); | |
+ if (foreign_ok) { | |
+ ret = bind(fd, (struct sockaddr *)&bind_addr, sizeof(bind_addr)); | |
+ if (ret < 0) | |
+ return 2; | |
+ } | |
+ else { | |
+ ret = bind(fd, (struct sockaddr *)local, sizeof(*local)); | |
+ if (ret < 0) | |
+ return 1; | |
+ } | |
+ | |
+ if (!flags) | |
+ return 0; | |
+ | |
+ if (!foreign_ok) | |
+ /* we could not bind to a foreign address */ | |
+ return 2; | |
+ | |
+ return 0; | |
+} | |
+ | |
+/* This function tries to bind a UDPv4/v6 listener. It may return a warning or | |
+ * an error message in <err> if the message is at most <errlen> bytes long | |
+ * (including '\0'). The return value is composed from ERR_ABORT, ERR_WARN, | |
+ * ERR_ALERT, ERR_RETRYABLE and ERR_FATAL. ERR_NONE indicates that everything | |
+ * was alright and that no message was returned. ERR_RETRYABLE means that an | |
+ * error occurred but that it may vanish after a retry (eg: port in use), and | |
+ * ERR_FATAL indicates a non-fixable error.ERR_WARN and ERR_ALERT do not alter | |
+ * the meaning of the error, but just indicate that a message is present which | |
+ * should be displayed with the respective level. Last, ERR_ABORT indicates | |
+ * that it's pointless to try to start other listeners. No error message is | |
+ * returned if errlen is NULL. | |
+ */ | |
+int dns_bind_listener(struct listener *listener, char *errmsg, int errlen) | |
+{ | |
+ __label__ dns_return, dns_close_return; | |
+ int fd, err; | |
+ const char *msg = NULL; | |
+ int on = 1; | |
+ listener->accept = dns_event_accept; | |
+ | |
+ /* ensure we never return garbage */ | |
+ if (errmsg && errlen) | |
+ *errmsg = 0; | |
+ | |
+ if (listener->state != LI_ASSIGNED) | |
+ return ERR_NONE; /* already bound */ | |
+ | |
+ err = ERR_NONE; | |
+ | |
+ if ((fd = socket(listener->addr.ss_family, SOCK_DGRAM, IPPROTO_UDP)) == -1) { | |
+ err |= ERR_RETRYABLE | ERR_ALERT; | |
+ msg = "cannot create listening socket"; | |
+ goto dns_return; | |
+ } | |
+ | |
+ if (fd >= global.maxsock) { | |
+ err |= ERR_FATAL | ERR_ABORT | ERR_ALERT; | |
+ msg = "not enough free sockets (raise '-n' parameter)"; | |
+ goto dns_close_return; | |
+ } | |
+ | |
+ if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) { | |
+ err |= ERR_FATAL | ERR_ALERT; | |
+ msg = "cannot make socket non-blocking"; | |
+ goto dns_close_return; | |
+ } | |
+ | |
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(one)) == -1) { | |
+ /* not fatal but should be reported */ | |
+ msg = "cannot do so_reuseaddr"; | |
+ err |= ERR_ALERT; | |
+ } | |
+ | |
+ if (listener->options & LI_O_NOLINGER) | |
+ setsockopt(fd, SOL_SOCKET, SO_LINGER, (struct linger *) &nolinger, sizeof(struct linger)); | |
+ | |
+#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 | |
+#ifdef CONFIG_HAP_LINUX_TPROXY | |
+ if ((listener->options & LI_O_FOREIGN) | |
+ && (setsockopt(fd, SOL_IP, IP_TRANSPARENT, (char *) &one, sizeof(one)) == -1) | |
+ && (setsockopt(fd, SOL_IP, IP_FREEBIND, (char *) &one, sizeof(one)) == -1)) { | |
+ msg = "cannot make listening socket transparent"; | |
+ err |= ERR_ALERT; | |
+ } | |
+#endif | |
+#ifdef SO_BINDTODEVICE | |
+ /* Note: this might fail if not CAP_NET_RAW */ | |
+ if (listener->interface) { | |
+ if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, | |
+ listener->interface, strlen(listener->interface) + 1) == -1) { | |
+ msg = "cannot bind listener to device"; | |
+ err |= ERR_WARN; | |
+ } | |
+ } | |
+#endif | |
+ setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &on, sizeof(on)); | |
+ if (bind(fd, (struct sockaddr *)&listener->addr, listener->proto->sock_addrlen) == -1) { | |
+ err |= ERR_RETRYABLE | ERR_ALERT; | |
+ msg = "cannot bind socket"; | |
+ goto dns_close_return; | |
+ } | |
+ | |
+#if 0 | |
+ if (listen(fd, listener->backlog ? listener->backlog : listener->maxconn) == -1) { | |
+ err |= ERR_RETRYABLE | ERR_ALERT; | |
+ msg = "cannot listen to socket"; | |
+ goto dns_close_return; | |
+ } | |
+#endif | |
+ | |
+ /* the socket is ready */ | |
+ listener->fd = fd; | |
+ listener->state = LI_LISTEN; | |
+ | |
+ /* the function for the accept() event */ | |
+ fd_insert(fd); | |
+ fdtab[fd].cb[DIR_RD].f = listener->accept; | |
+ fdtab[fd].cb[DIR_WR].f = NULL; /* never called */ | |
+ fdtab[fd].cb[DIR_RD].b = fdtab[fd].cb[DIR_WR].b = NULL; | |
+ fdtab[fd].owner = listener; /* reference the listener instead of a task */ | |
+ fdtab[fd].state = FD_STLISTEN; | |
+ fdtab[fd].flags = FD_FL_UDP; | |
+ | |
+ fdinfo[fd].peeraddr = NULL; | |
+ fdinfo[fd].peerlen = 0; | |
+ dns_return: | |
+ if (msg && errlen) | |
+ strlcpy2(errmsg, msg, errlen); | |
+ return err; | |
+ | |
+ dns_close_return: | |
+ close(fd); | |
+ goto dns_return; | |
+} | |
+ | |
+/* This function creates all UDP sockets bound to the protocol entry <proto>. | |
+ * It is intended to be used as the protocol's bind_all() function. | |
+ * The sockets will be registered but not added to any fd_set, in order not to | |
+ * loose them across the fork(). A call to enable_all_listeners() is needed | |
+ * to complete initialization. The return value is composed from ERR_*. | |
+ */ | |
+static int dns_bind_listeners(struct protocol *proto) | |
+{ | |
+ struct listener *listener; | |
+ int err = ERR_NONE; | |
+ | |
+ list_for_each_entry(listener, &proto->listeners, proto_list) { | |
+ err |= dns_bind_listener(listener, NULL, 0); | |
+ if ((err & ERR_CODE) == ERR_ABORT) | |
+ break; | |
+ } | |
+ | |
+ return err; | |
+} | |
+ | |
+/* Add listener to the list of dnsv4 listeners. The listener's state | |
+ * is automatically updated from LI_INIT to LI_ASSIGNED. The number of | |
+ * listeners is updated. This is the function to use to add a new listener. | |
+ */ | |
+void dnsv4_add_listener(struct listener *listener) | |
+{ | |
+ if (listener->state != LI_INIT) | |
+ return; | |
+ listener->state = LI_ASSIGNED; | |
+ listener->proto = &proto_dnsv4; | |
+ LIST_ADDQ(&proto_dnsv4.listeners, &listener->proto_list); | |
+ proto_dnsv4.nb_listeners++; | |
+} | |
+ | |
+/* Add listener to the list of dnsv6 listeners. The listener's state | |
+ * is automatically updated from LI_INIT to LI_ASSIGNED. The number of | |
+ * listeners is updated. This is the function to use to add a new listener. | |
+ */ | |
+void dnsv6_add_listener(struct listener *listener) | |
+{ | |
+ if (listener->state != LI_INIT) | |
+ return; | |
+ listener->state = LI_ASSIGNED; | |
+ listener->proto = &proto_dnsv6; | |
+ LIST_ADDQ(&proto_dnsv6.listeners, &listener->proto_list); | |
+ proto_dnsv6.nb_listeners++; | |
+} | |
+ | |
+int dns_event_accept(int fd) { | |
+ __label__ answer_srvfail, answer_nxdomain, answer; | |
+ struct listener *l = fdtab[fd].owner; | |
+ struct proxy *px, *p = (struct proxy *)l->private; /* attached frontend */ | |
+ struct session *s = NULL; | |
+ char *name = NULL; | |
+ char id[256]; | |
+ int err, invalid_qtype=0; | |
+ | |
+ ssize_t len, total_len, namelen; | |
+ | |
+ struct sockaddr_in src_addr; | |
+ struct in_addr dst_addr; | |
+ char buf[1501]; | |
+ char control_buf[1024]; | |
+ struct cmsghdr *cmsg; | |
+ struct iovec iov = { (void*)buf, 1500 }; | |
+ | |
+ struct msghdr msg = { | |
+ .msg_name = (void*)&src_addr, | |
+ .msg_namelen = sizeof(struct sockaddr_in), | |
+ .msg_iov = &iov, | |
+ .msg_iovlen = 1, | |
+ .msg_control = control_buf, | |
+ .msg_controllen = 1024, | |
+ .msg_flags = 0, | |
+ }; | |
+ | |
+ len = recvmsg(fd, &msg, 0); | |
+ /* Ignore corrupt packets */ | |
+ if(len == -1) | |
+ return 0; | |
+ if(msg.msg_flags & (MSG_TRUNC|MSG_CTRUNC)) | |
+ return 0; | |
+ | |
+#if 0 | |
+ /* Debugging aid */ | |
+ for(cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) { | |
+ send_log(p, LOG_EMERG, "PKT\n"); | |
+ if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) { | |
+ dst_addr = ((struct in_pktinfo*)CMSG_DATA(cmsg))->ipi_addr; | |
+ } | |
+ } | |
+ send_log(p, LOG_EMERG, "Received UDP packet (%d or %d bytes) from %s", len, msg.msg_iov->iov_len, inet_ntoa(src_addr.sin_addr)); | |
+ send_log(p, LOG_EMERG, " on %s\n", inet_ntoa(dst_addr)); | |
+# endif | |
+ | |
+ /* Packet to small to even contain a dns header */ | |
+ if(len < 17) | |
+ return 0; | |
+ /* Ignore anything not containing one and only one query */ | |
+ if(QR(buf) || TC(buf) || OPCODE(buf) != 0 || QDCOUNT(buf) != 1) | |
+ return 0; | |
+ | |
+ /* Check for a valid question */ | |
+ total_len = 12; /* Bytes processed so far */ | |
+ while(1) { | |
+ namelen = (ssize_t)buf[total_len]; | |
+ /* Record doesn't fit in received packet. Corrupt */ | |
+ if(len < total_len + namelen + 5) | |
+ return 0; | |
+ /* No name specified */ | |
+ if(!namelen && !name) | |
+ return 0; | |
+ if(!name) { | |
+ name = &(buf[total_len + 1]); | |
+ snprintf(id, MIN(namelen+1, 256), "%s\0", name); | |
+ } | |
+ total_len += namelen + 1; | |
+ if(!namelen) | |
+ break; | |
+ } | |
+ | |
+ /* Now check the Qtype/Qclass */ | |
+ if(ntohs(*(uint16_t*)(buf + total_len)) != 1 /* QTYPE != A */ || | |
+ ntohs(*(uint16_t*)(buf + total_len + 2)) != 1 /* QCLASS != IN */) { | |
+ invalid_qtype = 1; | |
+ } | |
+ total_len += 4; | |
+ | |
+ /* Prepare for answer */ | |
+ SET_ANCOUNT(buf, invalid_qtype ? 0 : 1); | |
+ SET_RCODE(buf, 0); | |
+ | |
+ memcpy(buf + total_len, buf+12, total_len-12); | |
+ total_len += total_len - 12; | |
+ | |
+ *((uint32_t*)(buf + total_len)) = htonl(6); /* XXX Don't hardcode 6 second TTL */ | |
+ *((uint16_t*)(buf + total_len + 4)) = htons(4); | |
+ | |
+ if ((s = pool_alloc2(pool2_session)) == NULL) { /* disable this proxy for a while */ | |
+ Alert("out of memory in event_accept().\n"); | |
+ disable_listener(l); | |
+ p->state = PR_STIDLE; | |
+ goto answer_srvfail; | |
+ } | |
+ bzero(s, sizeof(struct session)); | |
+ | |
+ s->listener = l; | |
+ for(px = proxy; px; px = px->next) { | |
+ if(!strcmp(id, px->id)) { | |
+ s->be = s->fe = px; | |
+ break; | |
+ } | |
+ } | |
+ /* No such frontend */ | |
+ if(!s->fe) | |
+ goto answer_nxdomain; | |
+ /* Empty response if the requested QTYPE wasn't A */ | |
+ if(invalid_qtype) | |
+ goto answer; | |
+ s->conn_retries = s->be->conn_retries; | |
+ if(err = assign_server(s) != SRV_STATUS_OK) | |
+ goto answer_srvfail; | |
+ assign_server_address(s); | |
+ memcpy(buf + total_len + 6, (char*)&(s->srv->addr.sin_addr), sizeof(struct in_addr)); | |
+ total_len += 10; | |
+ len = total_len; | |
+ goto answer; | |
+ | |
+answer_nxdomain: | |
+ SET_ANCOUNT(buf, 0); | |
+ SET_RCODE(buf, 3); /* NXDOMAIN */ | |
+ goto answer; | |
+ | |
+answer_srvfail: | |
+ SET_ANCOUNT(buf, 0); | |
+ SET_RCODE(buf, 2); /* SRVFAIL */ | |
+ goto answer; | |
+ | |
+answer: | |
+ SET_QR(buf, 1); | |
+ SET_RD(buf, 0); | |
+ SET_RA(buf, 1); | |
+ SET_AA(buf, 1); | |
+ SET_NSCOUNT(buf, 0); | |
+ SET_ARCOUNT(buf, 0); | |
+ | |
+ msg.msg_iov->iov_len = len; | |
+ sendmsg(fd, &msg, 0); | |
+ if(s) | |
+ pool_free2(pool2_session, s); | |
+ | |
+ return 0; | |
+} | |
+ | |
+__attribute__((constructor)) | |
+static void __dns_protocol_init(void) | |
+{ | |
+ protocol_register(&proto_dnsv4); | |
+ protocol_register(&proto_dnsv6); | |
+} | |
+ | |
+ | |
+/* | |
+ * Local variables: | |
+ * c-indent-level: 8 | |
+ * c-basic-offset: 8 | |
+ * End: | |
+ */ | |
diff --git a/src/proxy.c b/src/proxy.c | |
index cd38855..e8c147a 100644 | |
--- a/src/proxy.c | |
+++ b/src/proxy.c | |
@@ -428,7 +428,12 @@ int start_proxies(int verbose) | |
if (listener->state != LI_ASSIGNED) | |
continue; /* already started */ | |
- lerr = tcp_bind_listener(listener, msg, sizeof(msg)); | |
+ if(curproxy->mode == PR_MODE_DNS) { | |
+ lerr = dns_bind_listener(listener, msg, sizeof(msg)); | |
+ } | |
+ else { | |
+ lerr = tcp_bind_listener(listener, msg, sizeof(msg)); | |
+ } | |
/* errors are reported if <verbose> is set or if they are fatal */ | |
if (verbose || (lerr & (ERR_FATAL | ERR_ABORT))) { | |
-- | |
1.8.2.4.g940421e | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment