contrib/package/freifunk-watchdog:
[project/luci.git] / contrib / package / freifunk-watchdog / src / watchdog.c
1 /*
2  *   This program is free software; you can redistribute it and/or modify
3  *   it under the terms of the GNU General Public License as published by
4  *   the Free Software Foundation; either version 2 of the License, or
5  *   (at your option) any later version.
6  *
7  *   This program is distributed in the hope that it will be useful,
8  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  *   GNU General Public License for more details.
11  *
12  *   You should have received a copy of the GNU General Public License
13  *   along with this program; if not, write to the Free Software
14  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
15  *
16  *   Copyright (C) 2009 Jo-Philipp Wich <xm@subsignal.org>
17  */
18
19 #include "watchdog.h"
20
21 /* Get BSSID of given interface */
22 static int iw_get_bssid(int iwfd, const char *ifname, char *bssid)
23 {
24         struct iwreq iwrq;
25
26         if( iw_ioctl(iwfd, ifname, SIOCGIWAP, &iwrq) >= 0 )
27         {
28                 unsigned char *addr = (unsigned char *)iwrq.u.ap_addr.sa_data;
29
30                 sprintf(bssid, "%02X:%02X:%02X:%02X:%02X:%02X",
31                         addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
32
33                 return 0;
34         }
35
36         return -1;
37 }
38
39 /* Get channel of given interface */
40 static int iw_get_channel(int iwfd, const char *ifname, int *channel)
41 {
42         int i;
43         char buffer[sizeof(struct iw_range)];
44         double cur_freq, cmp_freq;
45         struct iwreq iwrq;
46         struct iw_range *range;
47
48         memset(buffer, 0, sizeof(buffer));
49
50         iwrq.u.data.pointer = (char *)buffer;
51         iwrq.u.data.length = sizeof(buffer);
52         iwrq.u.data.flags = 0;
53
54         if( iw_ioctl(iwfd, ifname, SIOCGIWRANGE, &iwrq) < 0)
55         {
56                 *channel = -1;
57                 return -1;
58         }
59
60         range = (struct iw_range *)buffer;
61
62         if( iw_ioctl(iwfd, ifname, SIOCGIWFREQ, &iwrq) >= 0 )
63         {
64                 cur_freq = ((double)iwrq.u.freq.m) * pow(10, iwrq.u.freq.e);
65                 if( cur_freq < 1000.00 )
66                 {
67                         *channel = (int)cur_freq;
68                         return 0;
69                 }
70
71                 for(i = 0; i < range->num_frequency; i++)
72                 {
73                         cmp_freq = ((double)range->freq[i].m) * pow(10, range->freq[i].e);
74                         if( cmp_freq == cur_freq )
75                         {
76                                 *channel = (int)range->freq[i].i;
77                                 return 0;
78                         }
79                 }
80         }
81
82         *channel = -1;
83         return -1;
84 }
85
86 /* Get the (first) pid of given process name */
87 static int find_process(const char *name)
88 {
89         int pid = -1;
90         int file;
91         char buffer[128];
92         char cmpname[128];
93         DIR *dir;
94         struct dirent *entry;
95
96         if( (dir = opendir("/proc")) != NULL )
97         {
98                 snprintf(cmpname, sizeof(cmpname), "Name:\t%s\n", name);
99
100                 while( (entry = readdir(dir)) != NULL )
101                 {
102                         if( !strcmp(entry->d_name, "..") || !isdigit(*entry->d_name) )
103                                 continue;
104
105                         sprintf(buffer, "/proc/%s/status", entry->d_name);
106                         if( (file = open(buffer, O_RDONLY)) > -1 )
107                         {
108                                 read(file, buffer, sizeof(buffer));
109                                 close(file);
110
111                                 if( strstr(buffer, cmpname) == buffer )
112                                 {
113                                         pid = atoi(entry->d_name);
114
115                                         /* Skip myself ... */
116                                         if( pid == getpid() )
117                                                 pid = -1;
118                                         else
119                                                 break;
120                                 }
121                         }
122                 }
123
124                 closedir(dir);
125                 return pid;
126         }
127
128         syslog(LOG_CRIT, "Unable to open /proc: %s",
129                 strerror(errno));
130
131         return -1;
132 }
133
134 /* Get the 5 minute load average */
135 static double find_loadavg(void)
136 {
137         int fd;
138         char buffer[10];
139         double load = 0.00;
140
141         if( (fd = open("/proc/loadavg", O_RDONLY)) > -1 )
142         {
143                 if( read(fd, buffer, sizeof(buffer)) == sizeof(buffer) )
144                         load = atof(&buffer[5]);
145
146                 close(fd);
147         }
148
149         return load;
150 }
151
152 /* Check if given uci file was updated */
153 static int check_uci_update(const char *config, time_t *mtime)
154 {
155         struct stat s;
156         char path[128];
157
158         snprintf(path, sizeof(path), "/var/state/%s", config);
159         if( stat(path, &s) > -1 )
160         {
161                 if( (*mtime == 0) || (s.st_mtime > *mtime) )
162                 {
163                         *mtime = s.st_mtime;
164                         return 1;
165                 }
166
167                 return 0;
168         }
169
170         return -1;
171 }
172
173 /* Add tuple */
174 static void load_wifi_uci_add_iface(const char *section, struct uci_itr_ctx *itr)
175 {
176         wifi_tuple_t *t;
177         const char *ucitmp;
178         int val = 0;
179
180         if( (t = (wifi_tuple_t *)malloc(sizeof(wifi_tuple_t))) != NULL )
181         {
182                 ucitmp = ucix_get_option(itr->ctx, "wireless", section, "ifname");
183                 if(ucitmp)
184                 {
185                         strncpy(t->ifname, ucitmp, sizeof(t->ifname));
186                         val++;
187                 }
188
189                 ucitmp = ucix_get_option(itr->ctx, "wireless", section, "bssid");
190                 if(ucitmp)
191                 {
192                         strncpy(t->bssid, ucitmp, sizeof(t->bssid));
193                         val++;
194                 }
195
196                 ucitmp = ucix_get_option(itr->ctx, "wireless", section, "device");
197                 if(ucitmp)
198                 {
199                         ucitmp = ucix_get_option(itr->ctx, "wireless", ucitmp, "channel");
200                         if(ucitmp)
201                         {
202                                 t->channel = atoi(ucitmp);
203                                 val++;
204                         }
205                 }
206
207                 if( val == 3 )
208                 {
209                         syslog(LOG_INFO, "Monitoring %s: bssid=%s channel=%d",
210                                 t->ifname, t->bssid, t->channel);
211
212                         t->next = itr->list;
213                         itr->list = t;
214                 }
215                 else
216                 {
217                         free(t);
218                 }
219         }
220 }
221
222 /* Load config */
223 static wifi_tuple_t * load_wifi_uci(wifi_tuple_t *ifs, time_t *modtime)
224 {
225         struct uci_context *ctx;
226         struct uci_itr_ctx itr;
227         wifi_tuple_t *cur, *next;
228
229         if( check_uci_update("wireless", modtime) )
230         {
231                 syslog(LOG_INFO, "Config changed, reloading");
232
233                 if( (ctx = ucix_init("wireless")) != NULL )
234                 {
235                         if( ifs != NULL )
236                         {
237                                 for(cur = ifs; cur; cur = next)
238                                 {
239                                         next = cur->next;
240                                         free(cur);
241                                 }
242                         }
243
244                         itr.list = NULL;
245                         itr.ctx = ctx;
246
247                         ucix_for_each_section_type(ctx, "wireless", "wifi-iface",
248                                 (void *)load_wifi_uci_add_iface, &itr);
249
250                         return itr.list;
251                 }
252         }
253
254         return ifs;
255 }
256
257 /* Daemon implementation */
258 static int do_daemon(void)
259 {
260         int iwfd;
261         int wdfd;
262         int wdtrigger = 1;
263         int channel;
264         char bssid[18];
265
266         wifi_tuple_t *ifs = NULL, *curif;
267         time_t modtime = 0;
268
269         int restart_wifi = 0;
270         int restart_cron = 0;
271         int restart_sshd = 0;
272         int loadavg_panic = 0;
273
274         openlog(SYSLOG_IDENT, 0, LOG_DAEMON);
275         //daemon(1, 1);
276
277         if( (iwfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1 )
278         {
279                 syslog(LOG_ERR, "Can not open wireless control socket: %s",
280                         strerror(errno));
281
282                 return 1;
283         }
284
285         if( (wdfd = open(WATCH_DEVICE, O_WRONLY)) > -1 )
286         {
287                 syslog(LOG_INFO, "Opened %s - polling each %i seconds",
288                         WATCH_DEVICE, INTERVAL);
289         }
290
291         while( 1 )
292         {
293                 /* Check average load */
294                 if( find_loadavg() >= LOAD_TRESHOLD )
295                         loadavg_panic++;
296                 else
297                         loadavg_panic = 0;
298
299                 /* Check crond */
300                 if( find_process("crond") < 0 )
301                         restart_cron++;
302
303                 /* Check SSHd */
304                 if( find_process("dropbear") < 0 )
305                         restart_sshd++;
306
307                 /* Check wireless interfaces */
308                 ifs = load_wifi_uci(ifs, &modtime);
309                 for( curif = ifs; curif; curif = curif->next )
310                 {
311                         /* Get current channel and bssid */
312                         if( (iw_get_bssid(iwfd, curif->ifname, bssid) == 0) &&
313                             (iw_get_channel(iwfd, curif->ifname, &channel) == 0) )
314                         {
315                                 /* Check BSSID */
316                                 if( strcasecmp(bssid, curif->bssid) != 0 )
317                                 {
318                                         syslog(LOG_WARNING, "BSSID mismatch on %s: current=%s wanted=%s",
319                                                 curif->ifname, bssid, curif->bssid);
320
321                                         restart_wifi++;
322                                 }
323
324                                 /* Check channel */
325                                 else if( channel != curif->channel )
326                                 {
327                                         syslog(LOG_WARNING, "Channel mismatch on %s: current=%d wanted=%d",
328                                                 curif->ifname, channel, curif->channel);
329
330                                         restart_wifi++;
331                                 }
332                         }
333                         else
334                         {
335                                 syslog(LOG_WARNING, "Requested interface %s not present", curif->ifname);
336                         }
337                 }
338
339
340                 /* Wifi restart required? */
341                 if( restart_wifi >= HYSTERESIS )
342                 {
343                         restart_wifi = 0;
344                         syslog(LOG_WARNING, "Channel or BSSID mismatch on wireless interface, restarting");
345                         EXEC(WIFI_ACTION);
346                 }
347
348                 /* Cron restart required? */
349                 if( restart_cron >= HYSTERESIS )
350                 {
351                         restart_cron = 0;
352                         syslog(LOG_WARNING, "The cron process died, restarting");
353                         EXEC(CRON_ACTION);
354                 }
355
356                 /* SSHd restart required? */
357                 if( restart_sshd >= HYSTERESIS )
358                 {
359                         restart_sshd = 0;
360                         syslog(LOG_WARNING, "The ssh process died, restarting");
361                         EXEC(SSHD_ACTION);
362                 }
363
364                 /* Is there a load problem? */
365                 if( loadavg_panic >= HYSTERESIS )
366                 {
367                         syslog(LOG_EMERG, "Critical system load level, triggering reset!");
368
369                         /* Try watchdog, fall back to reboot */
370                         if( wdfd > -1 )
371                                 ioctl(wdfd, WDIOC_SETTIMEOUT, &wdtrigger);
372                         else
373                                 EXEC(LOAD_ACTION);
374                 }
375
376                 /* Reset watchdog timer */
377                 if( wdfd > -1 )
378                         write(wdfd, '\0', 1);
379
380                 sleep(INTERVAL);
381         }
382
383         if( wdfd > -1 )
384         {
385                 syslog(LOG_INFO, "Stopping watchdog timer");
386                 write(wdfd, WATCH_SHUTDOWN, 1);
387                 close(wdfd);
388         }
389
390         closelog();
391         return 0;
392 }
393
394
395 int main(int argc, char *argv[])
396 {
397         /* Check if watchdog is running ... */
398         if( (argc > 1) && (strcmp(argv[1], "running") == 0) )
399         {
400                 return (find_process(BINARY) == -1);
401         }
402
403         /* Start daemon */
404         return do_daemon();
405 }