summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile10
-rw-r--r--README.md23
-rw-r--r--statsd-proxy.c310
3 files changed, 343 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..d45cee3
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,10 @@
+PROJECT = statsd-proxy
+OPTIMIZE = -O3
+CFLAGS += -Wall -Wextra
+
+all: ${PROJECT}
+
+${PROJECT}: statsd-proxy.c
+
+clean:
+ rm -f ${PROJECT} *.o
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..4cad533
--- /dev/null
+++ b/README.md
@@ -0,0 +1,23 @@
+# statsd-proxy
+
+A simple proxy for statsd
+
+## Why ?
+
+## Run it
+
+## License
+
+Copyright (c) 2014, Franck Cuny <franckcuny@gmail.com>
+
+Permission to use, copy, modify, and/or distribute this software for any purpose
+with or without fee is hereby granted, provided that the above copyright notice
+and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+THIS SOFTWARE.
diff --git a/statsd-proxy.c b/statsd-proxy.c
new file mode 100644
index 0000000..baab0ed
--- /dev/null
+++ b/statsd-proxy.c
@@ -0,0 +1,310 @@
+/*
+ * Copyright (c) 2014, Franck Cuny <franckcuny@gmail.com>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any purpose
+ * with or without fee is hereby granted, provided that the above copyright notice
+ * and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ * THIS SOFTWARE.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <getopt.h>
+
+struct settings {
+ int verbose;
+ int localport;
+ int daemon;
+ char *remoteport;
+ char *tag;
+ char *remotehost;
+ char *template;
+};
+
+struct peersock {
+ int socket;
+ socklen_t salen;
+ struct sockaddr *sa;
+};
+
+struct settings settings;
+
+static void settings_init() {
+ settings.verbose = 0;
+ settings.daemon = 0;
+ settings.localport = 8126;
+ settings.remoteport = "8125";
+ settings.remotehost = NULL;
+ settings.tag = "PROD";
+ settings.template = "%s.hosts.%s.";
+}
+
+static void usage(char *prog) {
+ printf("usage: %s [option]\n\n", prog);
+ printf("-p <num> local UDP port to listen on (default: %d)\n", settings.localport);
+ printf("-P <num> remote UDP port to send to (default: %s)\n", settings.remoteport);
+ printf("-H <string> remote host to send to (required)\n");
+ printf("-t tag to use (default: %s)\n", settings.tag);
+ printf("-T template to apply to the received line (default :%s)\n", settings.template);
+ printf("-h print this help and exit\n");
+ printf("-d run as a daemon\n");
+ printf("--verbose verbose\n");
+ return;
+}
+
+static int localsock(int port) {
+ int sock;
+ struct sockaddr_in servaddr;
+
+ if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
+ perror("socket");
+ return -1;
+ }
+
+ memset((char *)&servaddr, 0, sizeof(servaddr));
+ servaddr.sin_family = AF_INET;
+ servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
+ servaddr.sin_port = htons(port);
+
+ if(bind(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
+ perror("bind");
+ return -1;
+ }
+ return sock;
+}
+
+static int get_peersock(struct peersock *remote, char *host, char *port) {
+ int sock, err;
+ struct addrinfo hints, *result, *rp;
+ struct sockaddr *sa;
+ socklen_t salen;
+
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_protocol = IPPROTO_UDP;
+
+ if ((err = getaddrinfo(host, port, &hints, &result)) != 0) {
+ perror("getaddrinfo");
+ freeaddrinfo(result);
+ return -1;
+ }
+
+ for (rp = result; rp != NULL; rp = rp->ai_next) {
+ sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if (sock >= 0) {
+ break;
+ }
+ }
+
+ sa = malloc(result->ai_addrlen);
+ memcpy(sa, result->ai_addr, result->ai_addrlen);
+ salen = result->ai_addrlen;
+
+ remote->socket = sock;
+ remote->salen = salen;
+ remote->sa = sa;
+
+ freeaddrinfo(result);
+ return 0;
+}
+
+static int daemonize() {
+ switch(fork()) {
+ case -1:
+ return -1;
+ case 0:
+ break;
+ default:
+ exit(EXIT_SUCCESS);
+ }
+
+ if (settings.verbose == 1) {
+ fprintf(stdout, "Daemon is starting ...\n");
+ }
+
+ if (setsid() == -1) {
+ return -1;
+ }
+
+ if(chdir("/") != 0) {
+ perror("chdir");
+ return -1;
+ }
+
+ int fd;
+ if ((fd = open("/dev/null", O_RDWR, 0)) != -1) {
+ if(dup2(fd, STDIN_FILENO) < 0) {
+ perror("dup2 stdin");
+ return (-1);
+ }
+ if(dup2(fd, STDOUT_FILENO) < 0) {
+ perror("dup2 stdout");
+ return (-1);
+ }
+ if(dup2(fd, STDERR_FILENO) < 0) {
+ perror("dup2 stderr");
+ return (-1);
+ }
+
+ if (fd > STDERR_FILENO) {
+ if(close(fd) < 0) {
+ perror("close");
+ return (-1);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static char *getlinecontent(char mesg[], char template[], int ii, int start) {
+ char *line;
+ size_t len;
+
+ len = strlen(template) + (ii - start) + 1;
+ line = (char *)malloc(len);
+ memcpy(line, template, strlen(template) + 1);
+ strncat(line, &mesg[start], (ii - start));
+ line[len - 1] = '\0';
+
+ return (char *)line;
+}
+
+static void proxy_line(struct peersock *p_socket, char mesg[], char template[], int ii, int start) {
+ char *line;
+ line = getlinecontent(mesg, template, ii, start);
+ if (settings.verbose == 1) {
+ fprintf(stdout, "Forwarding %s\n", line);
+ }
+ sendto(p_socket->socket, line, strlen(line), 0, p_socket->sa, p_socket->salen);
+ free(line);
+ return;
+}
+
+int main(int argc, char *argv[]) {
+
+ static struct option long_options[] = {
+ {"local-port", required_argument, NULL, 'p'},
+ {"remote-port", required_argument, NULL, 'P'},
+ {"remote-host", required_argument, NULL, 'H'},
+ {"tag", required_argument, NULL, 't'},
+ {"template", required_argument, NULL, 'T'},
+ {"help", no_argument, NULL, 'h'},
+ {"verbose", no_argument, NULL, 'V'},
+ {"daemon", no_argument, NULL, 'd'},
+ { NULL, 0, NULL, 0 }
+ };
+
+ settings_init();
+
+ while (1) {
+ int option_index = 0;
+ int c = getopt_long(argc, argv, "p:P:H:t:T:hvd", long_options, &option_index);
+
+ if (c == -1)
+ break;
+
+ switch(c) {
+ case 'h':
+ usage(argv[0]);
+ exit(EXIT_SUCCESS);
+ case 'd':
+ settings.daemon = 1;
+ break;
+ case 'p':
+ settings.localport = atoi(optarg);
+ break;
+ case 't':
+ settings.tag = optarg;
+ break;
+ case 'H':
+ settings.remotehost = optarg;
+ break;
+ case 'P':
+ settings.remoteport = optarg;
+ break;
+ case 'T':
+ settings.template = optarg;
+ break;
+ case 'V':
+ settings.verbose = 1;
+ break;
+ default:
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (settings.remotehost == NULL) {
+ usage(argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ char hostname[255];
+ int l_socket;
+ struct peersock *p_socket = malloc(sizeof(struct peersock));
+
+ if (gethostname(hostname, sizeof(hostname) - 1) == -1) {
+ if (settings.verbose) {
+ fprintf(stderr, "Error while discovering hostname.\n");
+ }
+ hostname[0] = '\0';
+ }
+
+ if ((l_socket = localsock(settings.localport)) == -1) {
+ exit(EXIT_FAILURE);
+ }
+
+ if ((get_peersock(p_socket, settings.remotehost, settings.remoteport)) == -1) {
+ fprintf(stderr, "Failed to created a socket to remote host.\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if (settings.daemon) {
+ if ((daemonize()) == -1) {
+ fprintf(stderr, "Failed to daemonize()\n");
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ char mesg[64 * 1024];
+ char template[255];
+ int n;
+ sprintf(template, settings.template, settings.tag, hostname);
+
+ while((n = recvfrom(l_socket, mesg, sizeof(mesg), 0, NULL, NULL))) {
+ mesg[n] = '\0';
+ size_t start = 0;
+ int ii;
+ for (ii = 0; ii < n; ii++) {
+ if (mesg[ii] == '\n') {
+ proxy_line(p_socket, mesg, template, ii, start);
+ start = ii + 1;
+ }
+ }
+ if ((ii - start) > 0) {
+ proxy_line(p_socket, mesg, template, ii, start);
+ }
+ }
+
+ close(l_socket);
+ close(p_socket->socket);
+ free(p_socket);
+
+ return EXIT_SUCCESS;
+}