Browse Source

Starts UDP stats/status server implementation

Yann Weber 4 years ago
parent
commit
23ca05c04a
6 changed files with 339 additions and 5 deletions
  1. 17
    2
      include/conf.h
  2. 98
    0
      include/monitor.h
  3. 1
    1
      include/responder.h
  4. 2
    2
      src/Makefile.am
  5. 8
    0
      src/conf.c
  6. 213
    0
      src/monitor.c

+ 17
- 2
include/conf.h View File

@@ -56,6 +56,7 @@ pyfcgi_conf_t PyFCGI_conf;
56 56
 #include "logger.h"
57 57
 #include "pyutils.h"
58 58
 #include "ipc.h"
59
+#include "monitor.h"
59 60
 
60 61
 /**@defgroup ret_status Function & process return status
61 62
  */
@@ -74,7 +75,7 @@ pyfcgi_conf_t PyFCGI_conf;
74 75
 
75 76
 #define PYFCGI_NAME "spawn-fcgi [OPTIONS] -- pyfcgi"
76 77
 
77
-#define PYFCGI_SHORT_OPT "Ce:E:Aw:W:m:ft:L:SvVh"
78
+#define PYFCGI_SHORT_OPT "Ce:E:Aw:W:m:ft:L:SPsvVh"
78 79
 
79 80
 #define PYFCGI_LONG_OPT { \
80 81
 	{"config", required_argument, 0, 'C'},\
@@ -89,6 +90,7 @@ pyfcgi_conf_t PyFCGI_conf;
89 90
 	{"log", required_argument, 0, 'L'},\
90 91
 	{"syslog", no_argument, 0, 'S'},\
91 92
 	{"pid-file", required_argument, 0, 'P'},\
93
+	{"socket-server", required_argument, 0, 's'},\
92 94
 	{"verbose", no_argument, 0, 'v'},\
93 95
 	{"version", no_argument, 0, 'V'},\
94 96
 	{"help", no_argument, 0, 'h' },\
@@ -108,6 +110,7 @@ pyfcgi_conf_t PyFCGI_conf;
108 110
 	{"Add a logfile using syntax : 'LOGFILE[;FILT][;FMT]'", "LOGGER_SPEC"},\
109 111
 	{"Use syslog for logging", NULL},\
110 112
 	{"Create a PID file", "FILENAME"},\
113
+	{"Indicate a socket (for example 'udp://localhost:8765' ) to listen on, replying stats & status", "SOCKET"},\
111 114
 	{"Send all loglines on stderr", NULL},\
112 115
 	{"Print PyFCGI and Python version and exit", NULL},\
113 116
 	{"Display this help and exit", NULL},\
@@ -128,7 +131,12 @@ Logger specification format 'LOGFILE[;FILT][;FMT]' with :\n\
128 131
 \t\t- {ident} the defined ident (set by process)\n\
129 132
 \t\t- {msg} the log message (can only appear once)\n\
130 133
 You can escape { and } by using {{ and }} and all field names can by\n\
131
-abbreviated to one character.\n"
134
+abbreviated to one character.\n\n\
135
+Socket URL specification format : PROT://HOST[:PORT] with PROT on of \n\
136
+\t- tcp for tcp sockets and HOST a valid INET address\n\
137
+\t- udp for udp sockets and HOST a valid INET address\n\
138
+\t- unix for file sockets and HOST a valid path\n\
139
+"
132 140
 
133 141
 #define PYENTRY_DEFAULT_FUN "application"
134 142
 
@@ -241,6 +249,13 @@ struct pyfcgi_conf_s
241 249
 	 * - sems[2] is the stats/status SHM semaphore
242 250
 	 */
243 251
 	pyfcgi_semdata_t sems[PYFCGI_NSEM];
252
+
253
+	/**@brief Stores a copy of the specified socket URL to listen to. */
254
+	char *mon_socket;
255
+	/**@brief If 1 force ipv4 */
256
+	short ipv4;
257
+	/**@brief If 1 force ipv6 */
258
+	short ipv6;
244 259
 };
245 260
 
246 261
 

+ 98
- 0
include/monitor.h View File

@@ -0,0 +1,98 @@
1
+/*
2
+ * Copyright (C) 2019 Weber Yann
3
+ * 
4
+ * This file is part of PyFCGI.
5
+ * 
6
+ * PyFCGI is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU Affero General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * any later version.
10
+ * 
11
+ * PyFCGI is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU Affero General Public License for more details.
15
+ * 
16
+ * You should have received a copy of the GNU Affero General Public License
17
+ * along with PyFCGI.  If not, see <http://www.gnu.org/licenses/>.
18
+ */
19
+
20
+/**@defgroup monitoring Monitoring a running PyFCGI
21
+ *
22
+ * PyFCGI have the ability to listen on a UDP socket, replying to simple
23
+ * queries (or simple stats).
24
+ */
25
+#ifndef _MONITOR__H___
26
+#define _MONITOR__H___
27
+#include "config.h"
28
+
29
+#include <fcgiapp.h> /* fcgi library; put it first*/
30
+
31
+#include <netdb.h>
32
+#include <strings.h>
33
+#include <sys/socket.h>
34
+#include <sys/un.h>
35
+#include <netinet/in.h>
36
+#include <arpa/inet.h>
37
+
38
+#define UNIX_SOCKPATH_MAX 108
39
+
40
+typedef struct pyfcgi_monitor_s pyfcgi_monitor_t;
41
+typedef union pyfcgi_monitor_addr_u pyfcgi_monitor_addr_t;
42
+
43
+#include "conf.h"
44
+#include "logger.h"
45
+
46
+union pyfcgi_monitor_addr_u
47
+{
48
+	struct sockaddr_in in;
49
+	struct sockaddr_in6 in6;
50
+	struct sockaddr_un un;
51
+};
52
+
53
+/**@brief Local structure storing monitor process informations */
54
+struct pyfcgi_monitor_s
55
+{
56
+	/**@brief Socket fd */
57
+	int sock_serv;
58
+	/**@brief domain type & protocol */
59
+	int sock_args[3];
60
+	/**@brief Address to bind to */
61
+	pyfcgi_monitor_addr_t addr;
62
+};
63
+
64
+
65
+/**@brief Start the stats server monitoring server 
66
+ * @return PID of the child process and -1 on error
67
+ * @note When called the configuration has to be parsed */
68
+pid_t pyfcgi_spawn_monitor();
69
+
70
+void pyfcgi_monitor_init();
71
+void pyfcgi_monitor_loop();
72
+
73
+/**@brief Check socket URL validity
74
+ * @param const char* the URL to check
75
+ * @return -1 if error else 0
76
+ * @note Logs error using dprintf(2, ...) because this function will
77
+ * be called when checking configuration
78
+ */
79
+int pyfcgi_monitor_check_sock(const char*);
80
+
81
+/**@brief Parse stored socket URL
82
+ * @param const char* the URL to parse
83
+ * @param int[3] socket creation arguments
84
+ * @param pyfcgi_monitor_addr_t the addr to bind to
85
+ * @return 0 if no error else -1
86
+ */
87
+int pyfcgi_monitor_parse_sock(const char*, int[3], pyfcgi_monitor_addr_t*);
88
+
89
+/**@brief Parse an addres:port string in a sockaddr_in
90
+ * @param const char* the hostname:port string
91
+ * @param int socket family
92
+ * @param pyfcgi_monitor_addr_t* the addr pointer
93
+ * @param int* if not NULL will be set to choosen domain
94
+ * @return 0 if no erro else -1
95
+ */
96
+int pyfcgi_monitor_parse_inet_addr(const char*, int, pyfcgi_monitor_addr_t*, int*);
97
+
98
+#endif

+ 1
- 1
include/responder.h View File

@@ -42,7 +42,7 @@
42 42
 #define _RESPONDER__H___
43 43
 #include "config.h"
44 44
 
45
-#include <fcgi_stdio.h> /* fcgi library; put it first*/
45
+#include <fcgiapp.h> /* fcgi library; put it first*/
46 46
 
47 47
 #include <stdlib.h>
48 48
 #include <unistd.h>

+ 2
- 2
src/Makefile.am View File

@@ -6,13 +6,13 @@ pyfcgi_LDADD = $(PYTHON_LDFLAGS)
6 6
 
7 7
 # libpyfcgi python module
8 8
 lib_LTLIBRARIES = libpyfcgi.la
9
-libpyfcgi_la_SOURCES = python_pyfcgi.c python_ioin.c ipc.c
9
+libpyfcgi_la_SOURCES = python_pyfcgi.c python_ioin.c ipc.c monitor.c
10 10
 libpyfcgi_la_CFLAGS = $(PYTHON_SO_CFLAGS)
11 11
 libpyfcgi_la_LDFLAGS = $(PYTHON_SO_LDFLAGS)
12 12
 
13 13
 # static librarie for check
14 14
 noinst_LIBRARIES = libpyfcgi.a
15
-libpyfcgi_a_SOURCES = logger.c pyworker.c responder.c conf.c pyutils.c python_pyfcgi.c python_ioin.c ipc.c
15
+libpyfcgi_a_SOURCES = logger.c pyworker.c responder.c conf.c pyutils.c python_pyfcgi.c python_ioin.c ipc.c monitor.c
16 16
 libpyfcgi_a_CFLAGS = $(PYTHON_CFLAGS)
17 17
 
18 18
 

+ 8
- 0
src/conf.c View File

@@ -121,6 +121,14 @@ int parse_args(int argc, char *argv[])
121 121
 				PyFCGI_conf.pidfile = strdup(optarg);
122 122
 				/**@todo create pidfile and put master pid in it */
123 123
 				break;
124
+			case 's':
125
+				if(pyfcgi_monitor_check_sock(optarg) < 0)
126
+				{
127
+					exit(1);
128
+				}
129
+				PyFCGI_conf.mon_socket = strdup(optarg);
130
+				/**@todo check strdup returned value */
131
+				break;
124 132
 			case 'h':
125 133
 				usage();
126 134
 				exit(0);

+ 213
- 0
src/monitor.c View File

@@ -0,0 +1,213 @@
1
+#include "monitor.h"
2
+
3
+
4
+pid_t pyfcgi_spawn_monitor()
5
+{
6
+	pid_t res;
7
+
8
+	res = fork();
9
+	if(res == -1)
10
+	{
11
+		pyfcgi_log(LOG_ALERT, "Unable to fork into monitoring server process : %s",
12
+			strerror(errno));
13
+		sleep(1);
14
+	}
15
+	if(!res)
16
+	{
17
+		pyfcgi_logger_set_ident("StatServ");
18
+		pyfcgi_monitor_init();
19
+		pyfcgi_monitor_loop();
20
+		pyfcgi_log(LOG_ALERT, "Monitor loop should never return but just did it...");
21
+		exit(PYFCGI_FATAL);
22
+	}
23
+	return res;
24
+}
25
+
26
+void pyfcgi_monitor_init()
27
+{
28
+
29
+}
30
+
31
+void pyfcgi_monitor_loop()
32
+{
33
+
34
+}
35
+
36
+int pyfcgi_monitor_check_sock(const char* sockurl)
37
+{
38
+	const char *port;
39
+	short tcp;
40
+	const char *urlorig;
41
+
42
+	urlorig = sockurl;
43
+
44
+	if(!(tcp = strncasecmp("tcp://", sockurl, 6)) ||
45
+		!strncasecmp("udp://", sockurl, 6))
46
+	{
47
+		sockurl += 6; //first addr chr
48
+	}
49
+	else if(!strncasecmp("unix://", sockurl, 7))
50
+	{
51
+		sockurl += 7;
52
+		if(strlen(sockurl) > UNIX_SOCKPATH_MAX)
53
+		{
54
+			dprintf(2, "UNIX socket support only path with length <= %d but given path is %ld bytes long : '%s'",
55
+				UNIX_SOCKPATH_MAX, strlen(sockurl), urlorig);
56
+			return -1;
57
+		}
58
+		return 0;
59
+	}
60
+	else
61
+	{
62
+		dprintf(2, "Invalid protocol in '%s'\n", sockurl);
63
+		return -1;
64
+	}
65
+	do { sockurl++; } while(*sockurl && *sockurl != ':');
66
+	if(!sockurl)
67
+	{
68
+		dprintf(2, "%s protocol choosen but not port given : '%s'\n",
69
+			tcp?"TCP":"UDP", sockurl);
70
+		return -1;
71
+	}
72
+	sockurl++;
73
+	port = sockurl;
74
+	while(*sockurl && *sockurl >= '0' && *sockurl <= '9')
75
+	{
76
+		sockurl++;
77
+	}
78
+	if(sockurl)
79
+	{
80
+		dprintf(2, "Invalid port '%s' in socket URL '%s'\n",
81
+			port, urlorig);
82
+		return -1;
83
+	}
84
+	return 0;
85
+}
86
+
87
+int pyfcgi_monitor_parse_sock(const char *sockurl, int sockargs[3],
88
+	pyfcgi_monitor_addr_t *listen_addr)
89
+{
90
+	const char *addr_ptr;
91
+	short tcp;
92
+	int *domain, *type, *protocol;
93
+	struct sockaddr_un *addr_un;
94
+
95
+	domain = &sockargs[0];
96
+	type = &sockargs[1];
97
+	protocol = &sockargs[2];
98
+
99
+	addr_un = &(listen_addr->un);
100
+
101
+	sockurl = PyFCGI_conf.mon_socket;
102
+	if(strncasecmp("unix://", sockurl, 7))
103
+	{
104
+		addr_ptr = sockurl + 7;
105
+		*domain = AF_UNIX;
106
+		*type = SOCK_STREAM;
107
+		*protocol = 0;
108
+		addr_un->sun_family = AF_UNIX;
109
+		strncpy(addr_un->sun_path, addr_ptr, UNIX_SOCKPATH_MAX);
110
+		return 0;
111
+	}
112
+
113
+	if((tcp = strncasecmp("tcp://", sockurl, 6)) &&
114
+		strncasecmp("udp://", sockurl, 6))
115
+	{ //Unchecked URL??!!
116
+		pyfcgi_log(LOG_ERR, "Invalid protocol in URL : '%s'",
117
+			sockurl);
118
+		return -1;
119
+	}
120
+	addr_ptr = sockurl + 6;
121
+
122
+	if(pyfcgi_monitor_parse_inet_addr(addr_ptr, *type, listen_addr, domain))
123
+	{
124
+		return -1;
125
+	}
126
+	*type = tcp ? SOCK_DGRAM : SOCK_STREAM;
127
+	*protocol = 0;
128
+	return 0;
129
+}
130
+
131
+int pyfcgi_monitor_parse_inet_addr(const char* addr_str, int socktype,
132
+	pyfcgi_monitor_addr_t *listen_addr, int* domain)
133
+{
134
+	char *addr, *port, *ptr, *ipstr;
135
+	char v6str[64];
136
+	struct addrinfo *infos, hints, *info;
137
+	short v4, v6, i;
138
+	int ret;
139
+
140
+	// initialize temporary address & port pointers
141
+	addr = strdup(addr_str);
142
+	if(!addr)
143
+	{
144
+		pyfcgi_log(LOG_ALERT, "strdup() failed to copy socket addr : %s",
145
+			strerror(errno));
146
+		return -1;
147
+	}
148
+	ptr = addr;
149
+	do { ptr++; }while(*ptr && *ptr != ':');
150
+	if(!ptr)
151
+	{
152
+		pyfcgi_log(LOG_ERR, "No port found in INET url : %s",
153
+			addr_str);
154
+		goto free_err;
155
+	}
156
+	*ptr = '\0';
157
+	port = ptr+1;
158
+
159
+	memset(&hints, 0, sizeof(struct addrinfo));
160
+	v4 = PyFCGI_conf.ipv4;
161
+	v6 = PyFCGI_conf.ipv6;
162
+	hints.ai_family = (v4?AF_INET:(v6?AF_INET6:PF_UNSPEC));
163
+	hints.ai_socktype = socktype;
164
+	hints.ai_flags = AI_CANONNAME;
165
+
166
+	if((ret = getaddrinfo(addr, port, &hints, &infos)))
167
+	{
168
+		pyfcgi_log(LOG_ALERT, "getaddrinfo fails on '%s' : %s",
169
+			gai_strerror(ret));
170
+		goto free_err;
171
+	}
172
+
173
+	for(info = infos; info != NULL; info = info->ai_next)
174
+	{
175
+		if(info->ai_family == AF_INET)
176
+		{
177
+			memcpy(&listen_addr->in, info->ai_addr,
178
+				info->ai_addrlen);
179
+			ipstr = inet_ntoa(listen_addr->in.sin_addr);
180
+		}
181
+		else if(info->ai_family == AF_INET6)
182
+		{
183
+			memcpy(&listen_addr->in6, info->ai_addr,
184
+				info->ai_addrlen);
185
+			ptr = v6str;
186
+			for(i=0; i<16; i++)
187
+			{
188
+				ptr += snprintf(ptr, 4,
189
+					"%s%02X", ((i==0)?"":":"),
190
+					listen_addr->in6.sin6_addr.s6_addr[i]);
191
+			}
192
+			ipstr = v6str;
193
+		}
194
+		else
195
+		{
196
+			continue;
197
+		}
198
+		pyfcgi_log(LOG_DEBUG, "Listen addr resolved to %s(%s)",
199
+			info->ai_canonname,  ipstr);
200
+
201
+		freeaddrinfo(infos);
202
+		free(addr);
203
+		return 0;
204
+	}
205
+
206
+	pyfcgi_log(LOG_ERR, "Unable to resolve to a valid AF_INET[6] address");
207
+	freeaddrinfo(infos);
208
+
209
+free_err:
210
+	free(addr);
211
+	return -1;
212
+}
213
+

Loading…
Cancel
Save