diff options
Diffstat (limited to '')
-rw-r--r-- | tools/src/qscheck.c | 413 |
1 files changed, 413 insertions, 0 deletions
diff --git a/tools/src/qscheck.c b/tools/src/qscheck.c new file mode 100644 index 0000000..4b75c36 --- /dev/null +++ b/tools/src/qscheck.c @@ -0,0 +1,413 @@ +/** + * Utilities for the quality of service module mod_qos. + * + * qscheck.c: Monitor testing tcp connectivity to servers used by mod_proxy. + * + * See http://mod-qos.sourceforge.net/ for further + * details. + * + * Copyright (C) 2023 Pascal Buchbinder + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +static const char revision[] = "$Id: qscheck.c 2654 2022-05-13 09:12:42Z pbuchbinder $"; + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <netdb.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <unistd.h> +#include <fcntl.h> +#include <ctype.h> +#include <arpa/inet.h> + +#include "qs_util.h" + +//#include <config.h> + +#define CR 13 +#define LF 10 +#define QS_TIMEOUT 2 +#define QS_PROXYP "proxypass " +#define QS_PROXYP_TAB "proxypass\t" +#define QS_PROXYPR "proxypassreverse " +#define QS_PROXYPR_TAB "proxypassreverse\t" +#define QS_PROXYR "proxyremote " +#define QS_PROXYR_TAB "proxyremote\t" +#define QS_INCLUDE "nclude " +#define QS_INCLUDE_TAB "nclude\t" +#define QS_SERVERROOT "ServerRoot " +#define QS_SERVERROOT_TAB "ServerRoot\t" + +static int m_verbose = 0; +static char ServerRoot[1024]; +static char *checkedHosts = NULL; + +/** + * Prints usage text + */ +static void usage(char *cmd) { + printf("\n"); + printf("Monitor program testing the TCP connectivity to servers.\n"); + printf("\n"); + printf("Usage: %s -c <httpd.conf> [-v]\n", cmd); + printf("\n"); + printf("Verifies the connectivity to the server referred either\n"); + printf("by the ProxyPass, ProxyPassReverse, or ProxyReverse\n"); + printf("directive used by mod_proxy.\n"); + printf("\n"); + printf("You may alternatively use \"%s -i <hostname>:<port>\" if\n", cmd); + printf("you want to check the TCP connectivity to a single host.\n"); + printf("\n"); + printf("See http://mod-qos.sourceforge.net/ for further details.\n"); + exit(1); +} + +/** + * Opens a tcp connection + */ +static int ping(unsigned long address, int port) { + int status = 0; + struct sockaddr_in addr; + int skt; + addr.sin_addr.s_addr = address; + addr.sin_port = htons(port); + addr.sin_family = PF_INET; + skt = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + if(skt != -1) { + int sflags = fcntl(skt,F_GETFL,0); + if(sflags >=0) { + /* set non blocking socket */ + if(fcntl(skt,F_SETFL,sflags|O_NONBLOCK) >=0) { + /* this connect returns immediately */ + int ret = connect(skt, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)); + if(fcntl(skt,F_SETFL,sflags) >=0) { + socklen_t lon = sizeof(int); + int valopt; + fd_set fd_w; + struct timeval tme; + tme.tv_sec = QS_TIMEOUT; + tme.tv_usec = 0; + FD_ZERO(&fd_w); + FD_SET(skt, &fd_w); + /* select returns -1 on timeout, else 1 (connected or refused) */ + if(select(FD_SETSIZE, NULL, &fd_w, NULL, &tme) > 0) { + /* check the status of the socket in order to distinguish between + connected or refused */ + if(getsockopt(skt, SOL_SOCKET, SO_ERROR, (void*)(&valopt), &lon) >= 0) { + if(!valopt) { + /* UP ! */ + status = 1; + } + } + } + } + } + } + } + return status; +} + +/** + * resolves host address + */ +static unsigned long getAddress(const char *hostname) { + int ip = 1; + int i = 0; + unsigned long address = 0L; + struct hostent *hoste; + for(i = 0; i < (int) strlen(hostname); i++) { + if((!isdigit((int) hostname[i])) && (hostname[i] != '.')) { + ip = 0; + break; + } + } + if (ip) { + address = inet_addr(hostname); + if(address == -1) { + return 0L; + } + } else { + hoste = gethostbyname(hostname); + if (!hoste || !hoste->h_addr_list[ 0 ]) { + /* can't resolve host name */ + return 0L; + } + address = ((struct in_addr*)hoste->h_addr_list[ 0 ])->s_addr; + } + return address; +} + +/* + * Checks a single host (parse host string, resolve address, ping). + */ +static int checkHost(const char *cmd, const char *filename, int ln, char *abs_url) { + int status = 1; + char *schema = abs_url; + char *host = NULL; + char *ports = NULL; + int port = 0; + char hp[1024]; + unsigned long address; + char *x = strstr(abs_url, "://"); + if(x == NULL) { + if(m_verbose) { + fprintf(stderr,"[%s]: ERROR, wrong syntax <%s> in %s on line %d\n", + cmd, abs_url, filename, ln); + } + return 0; + } + x[0] = '\0'; x = x + strlen("://"); + host = x; + ports = strchr(x, ':'); + if(ports != NULL) { + ports[0] = '\0'; ports++; + x = strchr(ports, '/'); + if(x == NULL) { + int i; + x = ports; + for(i=0;(x[i] != ' ') && (x[i] != '\t') && (x[i] != '\0'); i++); + x[i] = '\0'; + } else { + x[0] = '\0'; + } + port = atoi(ports); + } else { + ports = strchr(x, '/'); + if(ports == NULL) { + int i; + for(i=0;(x[i] != ' ') && (x[i] != '\t') && (x[i] != '\0'); i++); + x[i] = '\0'; + } else { + ports[0] = '\0'; + } + if(strcmp(schema, "http") == 0) { + port = 80; + } else { + port = 443; + } + } + /* check each host only once */ + snprintf(hp, sizeof(hp), "#%s:%d#", host, port); + if(checkedHosts && strstr(checkedHosts, hp) != NULL) { + /* already checked */ + return 1; + } + if(checkedHosts == NULL) { + checkedHosts = calloc(1, strlen(hp) + 1); + strcpy(checkedHosts, hp); + } else { + int pl = strlen(checkedHosts) +strlen(hp) + 1; + char *p = calloc(1, pl); + snprintf(p, pl, "%s%s", checkedHosts, hp); + free(checkedHosts); + checkedHosts = p; + } + /* resolve address */ + address = getAddress(host); + if(address == 0L) { + fprintf(stderr,"[%s]: ERROR, could not resolve hostname %s\n", cmd, host); + return -1; + } + /* check connection */ + if(ping(address, port)) { + if(m_verbose) { + printf("[%s]: %s:%d Up\n", cmd, host, port); + } + return 1; + } else { + printf("[%s]: %s:%d Down\n", cmd, host, port); + return 0; + } +} + +/** + * Open file and check every ProxyPass* or ProxyR* entry. + * - follows include ... directive + * - determines serverroot + */ +static int checkFile(const char *cmd, const char *filename) { + int status = 1; + int ln = 0; + char line[1024]; + FILE *f = fopen(filename, "r"); + if(f == NULL) { + if(ServerRoot[0] != '\0') { + char fqfile[2048]; + snprintf(fqfile, sizeof(fqfile), "%s/%s", ServerRoot, filename); + f = fopen(fqfile, "r"); + } + } + if(f == NULL) { + fprintf(stderr,"[%s]: ERROR, could not open file %s\n", cmd, filename); + return 0; + } + + while(!qs_getLinef(line, sizeof(line), f)) { + char *command = NULL; + int cmd_len = 0; + int to = 0; + while(line[to]) { + line[to] = tolower(line[to]); + to++; + } + ln++; + command = strstr(line, QS_PROXYP); + cmd_len = strlen(QS_PROXYP); + if(command == NULL) command = strstr(line, QS_PROXYP_TAB); + if(command == NULL) { + command = strstr(line, QS_PROXYPR); + cmd_len = strlen(QS_PROXYPR); + } + if(command == NULL) command = strstr(line, QS_PROXYPR_TAB); + if(command == NULL) { + command = strstr(line, QS_PROXYR); + cmd_len = strlen(QS_PROXYR); + } + if(command == NULL) command = strstr(line, QS_PROXYR_TAB); + if(command && strchr(line, '#') == 0) { + /* command = cmd url schema://host[:port]/url */ + char *abs_url = &command[cmd_len]; + int i, j; + + /* get the url */ + for(i=0;(abs_url[i] == ' ') || (abs_url[i] == '\t'); i++); + abs_url = &abs_url[i]; + + /* skip url */ + for(i=0;(abs_url[i] != ' ') && (abs_url[i] != '\t') && (abs_url[i] != '\0'); i++); + abs_url = &abs_url[i]; + + /* get schema://host[:port]/url */ + for(i=0;(abs_url[i] == ' ') || (abs_url[i] == '\t'); i++); + abs_url = &abs_url[i]; + + /* ping */ + if(abs_url && abs_url[0] != '\0' && abs_url[0] != '!') { + status = status & checkHost(cmd, filename, ln, abs_url); + } + } else { + /* include commands */ + command = strstr(line, QS_INCLUDE); + if(command == NULL) command = strstr(line, QS_INCLUDE_TAB); + if(command && strchr(line, '#') == 0) { + char *file = &command[strlen(QS_INCLUDE)]; + int i, j; + /* get the value */ + for(i=0;(file[i] == ' ') || (file[i] == '\t'); i++); + /* delete spaces at the end of the value */ + if(&file[i] != '\0') { + for(j=i+1;(file[j] != ' ') && (file[j] != '\t') && (file[j] != '\0'); j++); + file[j] = '\0'; + } + file = &file[i]; + status = status & checkFile(cmd, file); + } else { + /* server root */ + command = strstr(line, QS_SERVERROOT); + if(command == NULL) command = strstr(line, QS_SERVERROOT_TAB); + if(command && strchr(line, '#') == 0) { + char *sr = &command[strlen(QS_SERVERROOT)]; + int i, j; + /* get the value */ + for(i=0;(sr[i] == ' ') || (sr[i] == '\t'); i++); + /* delete spaces at the end of the value */ + if(&sr[i] != '\0') { + for(j=i+1;(sr[j] != ' ') && (sr[j] != '\t') && (sr[j] != '\0'); j++); + sr[j] = '\0'; + } + strcpy(ServerRoot, &sr[i]); + } + } + } + } + fclose(f); + return status; +} + +int main(int argc, char **argv) { + char *config = NULL; + char *cmd = strrchr(argv[0], '/'); + char *single = NULL; + int status = 1; + if(cmd == NULL) { + cmd = argv[0]; + } else { + cmd++; + } + ServerRoot[0] = '\0'; + while(argc >= 1) { + if(strcmp(*argv,"-c") == 0) { + if (--argc >= 1) { + config = *(++argv); + } + } else if(strcmp(*argv,"-i") == 0) { + if (--argc >= 1) { + single = *(++argv); + } + } else if(strcmp(*argv,"-v") == 0) { + m_verbose = 1; + } + argc--; + argv++; + } + if(single) { + char *hostName = single; + char *portNumber = strchr(single, ':'); + if(portNumber) { + unsigned long addr; + int prt; + portNumber[0] = '\0'; + portNumber++; + addr = getAddress(hostName); + prt = atoi(portNumber); + if(addr && prt) { + if(ping(addr, prt)) { + if(m_verbose) { + printf("[%s]: %s:%d Up\n", cmd, hostName, prt); + } + status = 1; + } else { + printf("[%s]: %s:%d Down\n", cmd, hostName, prt); + status = 0; + } + } else { + // could not resolve + fprintf(stderr,"[%s]: ERROR, unknown host/port\n", cmd); + status = 0; + } + } else { + // invalid input + fprintf(stderr,"[%s]: ERROR, invalid format\n", cmd); + status = 0; + } + } else { + if(config == NULL) { + usage(cmd); + } + status = checkFile(cmd, config); + } + if(status == 0) { + fprintf(stderr,"[%s]: ERROR, check failed\n", cmd); + exit(1); + } + printf("[%s]: OK, check successful\n", cmd); + return 0; +} |