nifty hack - showing ip address instead of hostname for unregistered data transfers
[svn/Prometheus-QoS/.git] / prometheus.c
1 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2 /* Prometheus QoS - you can "steal fire" from your ISP */
3 /* "fair-per-IP" quality of service (QoS) utility */
4 /* requires Linux 2.4.x or 2.6.x with HTB support */
5 /* Copyright(C) 2005-2013 Michael Polak, Arachne Labs */
6 /* iptables-restore support Copyright(C) 2007-2008 ludva */
7 /* Credit: CZFree.Net,Martin Devera,Netdave,Aquarius,Gandalf */
8 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
9
10 /* Modified by: xChaos, 20130107
11 ludva, 20080415
12
13 Prometheus QoS is free software; you can redistribute it and/or
14 modify it under the terms of the GNU General Public License as
15 published by the Free Software Foundation; either version 2.1 of
16 the License, or (at your option) any later version.
17
18 Prometheus QoS is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 General Public License for more details.
22
23 You should have received a copy of the GNU General Public License
24 along with Prometheus Qos; if not, write to the Free Software
25 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
26
27 GNU General Public License is located in file COPYING */
28
29 #define STRLEN 512
30 #undef DEBUG
31
32 #include "cll1-0.6.2.h"
33 #include "ipstruct.h"
34
35 const char *version = "0.8.3-g";
36
37 /* Version numbers: 0.8.3 is development releases ("beta"), 0.8.4 will be "stable" */
38 /* Debian(RPM) package versions/patchlevels: 0.7.9-2, 0.8.0-1, 0.8.0-2, etc. */
39 /* C source code development versions ("beta"): 0.7.9-a, 0.8.1-b, etc. */
40 /* C source code release versions: 0.8.0, 0.8.2, 0.8.4, etc. */
41
42 const char *stats_html_signature = "<span class=\"small\">Statistics generated by Prometheus QoS version %s<br />GPL+Copyright(C)2005-2012 Michael Polak, <a target=\"_blank\" href=\"http://www.arachne.cz/\">Arachne Labs</a></span>\n";
43
44 /* ======= Help screen is hopefuly self-documenting part of code :-) ======= */
45
46 void help(void)
47 {
48 puts("Command line switches:\n\
49 \n\
50 -d Dry run (preview tc and iptables commands on stdout)\n\
51 -r Run (reset all statistics and start shaping - daily usage)\n\
52 -p just generate Preview of data transfer statistics and exit (after -r)\n\
53 -s start Shaping FUP limits (keeps data transfer stat like -p) (after -r)\n\
54 -n run Now (like -r delay - overrides qos-free-delay keyword, after boot)\n\
55 -f just Flush iptables and tc classes and exit (stop shaping, no QiS)\n\
56 -9 emergency iptables flush (like -f, but dumps data transfer statistics)\n\
57 \n\
58 -c filename force alternative /etc/prometheus/prometheus.conf filename\n\
59 -h filename force alternative /etc/hosts filename (overrides hosts keyword)\n\
60 -l Mmm YYYY generate HTML summary of Logged traffic (Mmm=Jan-Dec) (and exit)\n\
61 -m generate HTML summary of traffic for yesterday's Month (and exit)\n\
62 -y generate HTML summary of traffic for yesterday's Year (and exit)\n\
63 -? --help show this help scree (and exit)\n\
64 -v --version show Version number of this utility (and exit)\n");
65 }
66
67 /* ======= All path names are defined here (for RPM patch) ======= */
68
69 const char *tc = "/sbin/tc"; /* requires tc with HTB support */
70 const char *iptables = "/sbin/iptables"; /* requires iptables utility */
71 const char *iptablessave = "/sbin/iptables-save"; /* not yet required */
72 const char *iptablesrestore = "/sbin/iptables-restore"; /* requires iptables-restore */
73 const char *ls = "/bin/ls"; /* this is not user configurable :-) */
74
75 char *config = "/etc/prometheus/prometheus.conf"; /* main configuration file */
76 char *hosts = "/etc/prometheus/hosts"; /* per-IP bandwidth definition file */
77
78 char *iptablesfile = "/var/spool/prometheus.iptables"; /* temporary file for iptables-restore*/
79 char *credit = "/var/lib/misc/prometheus.credit"; /* credit log file */
80 char *classmap = "/var/lib/misc/prometheus.classes"; /* credit log file */
81 char *html = "/var/www/traffic.html"; /* hall of fame - html version */
82 char *preview = "/var/www/preview.html"; /* hall of fame preview - html version */
83 char *json_traffic = "/var/www/logs/traffic.json"; /* hall of fame - json version */
84 char *json_preview = "/var/www/logs/preview.json"; /* hall of fame preview - json version */
85 char *cmdlog = "/var/log/prometheuslog"; /* command log filename */
86 char *log_dir = "/var/www/logs/"; /* log directory pathname, ended with slash */
87 char *log_url = "/logs/"; /* log directory relative URI prefix (partial URL) */
88 char *html_log_dir = "/var/www/logs/html/";
89
90 char *jquery_url = "http://code.jquery.com/jquery-latest.js";
91 char *lms_url = "/lms/?m=customerinfo&amp;id=";
92 int use_jquery_popups = TRUE;
93 int row_odd_even = 0; /*<tr class="odd/even"> */
94
95 /* === Configuraration file values defaults - stored in global variables ==== */
96
97 int filter_type = 1; /*1 mark, 2 classify*/
98 char *mark = "MARK";
99 char *mark_iptables = "MARK --set-mark ";
100 int dry_run = FALSE; /* preview - use puts() instead of system() */
101 char *iptablespreamble = "*mangle\n:PREROUTING ACCEPT [0:0]\n:POSTROUTING ACCEPT [0:0]\n:INPUT ACCEPT [0:0]\n:OUTPUT ACCEPT [0:0]\n:FORWARD ACCEPT [0:0]";
102 FILE *iptables_file = NULL;
103 int enable_credit = TRUE; /* enable credit file */
104 int use_credit = FALSE; /* use credit file (if enabled)*/
105 char *title = "Hall of Fame - Greatest Suckers"; /* hall of fame title */
106 int hall_of_fame = TRUE; /* enable hall of fame */
107 char *lan = "eth0"; /* LAN interface */
108 char *lan_medium = "100Mbit"; /* 10Mbit/100Mbit ethernet */
109 char *wan = "eth1"; /* WAN/ISP interface */
110 char *wan_medium = "100Mbit"; /* 10Mbit/100Mbit ethernet */
111 char *qos_leaf = "sfq perturb 5"; /* leaf discipline */
112 char *qos_free_zone = NULL; /* QoS free zone */
113 int qos_proxy = TRUE; /* include proxy port to QoS */
114 int found_lmsid = FALSE; /* show links to users in LMS information system */
115 int include_upload = TRUE; /* upload+download=total traffic */
116 char *proxy_ip = "192.168.1.1/32"; /* our IP with proxy port */
117 int proxy_port = 3128; /* proxy port number */
118 long long int line = 1024; /* WAN/ISP download in kbps */
119 long long int up = 1024; /* WAN/ISP upload in kbps */
120 int free_min = 32; /* minimum guaranted bandwidth for all undefined hosts */
121 int free_max = 64; /* maximum allowed bandwidth for all undefined hosts */
122 int qos_free_delay = 0; /* seconds to sleep before applying new QoS rules */
123 int digital_divide = 2; /* controls digital divide weirdness ratio, 1...3 */
124 int max_nesting = 3; /* maximum nesting of HTB clases, built-in maximum seems to be 4 */
125 int htb_r2q = 256; /* should work for leaf values 512 kbps to 8 Mbps */
126 int burst = 8; /* HTB burst (in kbits) */
127 int burst_main = 64;
128 int burst_group = 32;
129 int magic_treshold = 8; /* reduce ceil by X*magic_treshhold kbps (hard shaping) */
130 int keywordcount = 0;
131 int class_count = 0;
132 int ip_count = 0;
133 /* not yet implemented:
134 int fixed_packets = 0; maximum number of pps per IP address (not class!)
135 int packet_limit = 5; maximum number of pps to htn CEIL, not rate !!!
136 */
137 FILE *log_file = NULL;
138 char *kwd = "via-prometheus"; /* /etc/hosts comment, eg. #qos-64-128 */
139
140 const int highest_priority = 0; /* highest HTB priority (HTB built-in value is 0) */
141 const int lowest_priority = 7; /* lowest HTB priority (HTB built-in value is 7) */
142 const int idxtable_treshold1 = 24; /* this is no longer configurable */
143 const int idxtable_treshold2 = 12; /* this is no longer configurable */
144 const int idxtable_bitmask1 = 3; /* this is no longer configurable */
145 const int idxtable_bitmask2 = 3; /* this is no longer configurable */
146
147 struct IP *ips = NULL, *ip, *sharedip;
148 struct Group *groups = NULL, *group;
149 struct Keyword *keyword, *defaultkeyword=NULL, *keywords=NULL;
150
151 void parse_ip_log(int argc, char **argv);
152 /* implemented in parselog.c */
153
154 void parse_hosts(char *hosts);
155 /* implemented in parsehosts.c */
156
157 void write_json_traffic(char *json);
158 /* implemented in json.c */
159
160 void write_htmlandlogs(char *html, char *d, int total, int just_preview);
161 /* implemented in htmlandlogs.c */
162
163 const char *tr_odd_even(void)
164 {
165 row_odd_even = 1 - row_odd_even;
166 if(row_odd_even)
167 {
168 return "<tr class=\"even\">\n";
169 }
170 else
171 {
172 return "<tr class=\"odd\">\n";
173 }
174 }
175
176 /* ==== This is C<<1 stuff - learn C<<1 first! https://dev.arachne.cz/svn/cll1h ==== */
177
178 struct Index
179 {
180 char *addr;
181 char *id;
182 struct Index *parent;
183 int bitmask;
184 int children;
185 list(Index);
186 } *idxs=NULL, *idx, *metaindex;
187
188 void TheIP(void);
189 /* function implemented in parsehosts.c */
190
191 /* ====== iptables indexes are used to reduce complexity to log8(N) ===== */
192
193 char *index_id(char *ip, int bitmask);
194 /* function implemented in ipv4subnets.c */
195
196 char *subnet_id(char *ip, int bitmask);
197 /* function implemented in ipv4subnets.c */
198
199 /* ================= Let's parse configuration file here ================ */
200
201 void reject_config_and_exit(char *filename)
202 {
203 printf("Configuration file %s rejected - abnormal exit.",filename);
204 exit(-1);
205 }
206
207 void get_config(char *config_filename)
208 {
209 char *cnf="mark";
210
211 printf("Configured keywords: ");
212 parse(config_filename)
213 {
214 option("keyword",kwd);
215 if(kwd)
216 {
217 printf("%s ",kwd);
218
219 create(keyword,Keyword);
220 keyword->key=kwd;
221 keyword->asymetry_ratio=1; /* ratio for ADSL-like upload */
222 keyword->asymetry_fixed=0; /* fixed treshold for ADSL-like upload */
223 keyword->data_limit=8; /* hard shaping: apply magic_treshold if max*data_limit MB exceeded */
224 keyword->data_prio=4; /* soft shaping (qos): reduce HTB prio if max*data_prio MB exceeded */
225 keyword->fixed_limit=0; /* fixed data limit for setting lower HTB ceil */
226 keyword->fixed_prio=0; /* fixed data limit for setting lower HTB prio */
227 keyword->reserve_min=8; /* bonus for nominal HTB rate bandwidth (in kbps) */
228 keyword->reserve_max=0; /* malus for nominal HTB ceil (in kbps) */
229 keyword->default_prio=highest_priority+1;
230 keyword->html_color="000000";
231 keyword->ip_count=0;
232 keyword->leaf_discipline="";
233
234 push(keyword,keywords);
235 if(!defaultkeyword)
236 {
237 defaultkeyword=keyword;
238 }
239 keywordcount++;
240
241 kwd=NULL;
242 }
243 else
244 {
245 for_each(keyword,keywords)
246 {
247 int l=strlen(keyword->key);
248
249 if(!strncmp(keyword->key,_,l) && strlen(_)>l+2)
250 {
251 char *tmptr=_; /* <---- l+1 ----> */
252 _+=l+1; /* via-prometheus-asymetry-ratio, etc. */
253 ioption("asymetry-ratio",keyword->asymetry_ratio);
254 ioption("asymetry-treshold",keyword->asymetry_fixed);
255 ioption("magic-relative-limit",keyword->data_limit);
256 ioption("magic-relative-prio",keyword->data_prio);
257 loption("magic-fixed-limit",keyword->fixed_limit);
258 loption("magic-fixed-prio",keyword->fixed_prio);
259 ioption("htb-default-prio",keyword->default_prio);
260 ioption("htb-rate-bonus",keyword->reserve_min);
261 ioption("htb-ceil-malus",keyword->reserve_max);
262 option("leaf-discipline",keyword->leaf_discipline);
263 option("html-color",keyword->html_color);
264 _=tmptr;
265
266 if(keyword->data_limit || keyword->fixed_limit ||
267 keyword->data_prio || keyword->fixed_prio)
268 {
269 use_credit=1;
270 }
271 }
272 }
273 }
274
275 option("tc",tc);
276 option("iptables",iptables);
277 option("iptables-save",iptablessave); /* new */
278 option("iptables-restore",iptablesrestore); /* new */
279 option("iptables-in-filename",iptablesfile); /* new */
280 option("hosts",hosts);
281 option("lan-interface",lan);
282 option("wan-interface",wan);
283 option("lan-medium",lan_medium);
284 option("wan-medium",wan_medium);
285 lloption("wan-download",line);
286 lloption("wan-upload",up);
287 ioption("hall-of-fame-enable",hall_of_fame);
288 option("hall-of-fame-title",title);
289 option("hall-of-fame-filename",html);
290 option("json-filename",json_traffic);
291 option("hall-of-fame-preview",preview);
292 option("json-preview",json_preview);
293 option("log-filename",cmdlog);
294 option("credit-filename",credit);
295 option("classmap-filename",classmap);
296 ioption("credit-enable",enable_credit);
297 option("log-traffic-directory",log_dir);
298 option("log-traffic-html-directory",html_log_dir);
299 option("log-traffic-url-path",log_url);
300 option("jquery-url",jquery_url);
301 option("lms-url",lms_url);
302 ioption("use-jquery-popups",use_jquery_popups);
303 option("qos-free-zone",qos_free_zone);
304 ioption("qos-free-delay",qos_free_delay);
305 ioption("qos-proxy-enable",qos_proxy);
306 option("qos-proxy-ip",proxy_ip);
307 option("htb-leaf-discipline",qos_leaf);
308 ioption("qos-proxy-port",proxy_port);
309 ioption("free-rate",free_min);
310 ioption("free-ceil",free_max);
311 ioption("htb-burst",burst);
312 ioption("htb-burst-main",burst_main);
313 ioption("htb-burst-group",burst_group);
314 ioption("htb-nesting-limit",max_nesting);
315 ioption("htb-r2q",htb_r2q);
316 ioption("magic-include-upload",include_upload);
317 ioption("magic-treshold",magic_treshold);
318 option("filter-type", cnf);
319 /* not yet implemented:
320 ioption("magic-fixed-packets",fixed_packets);
321 ioption("magic-relative-packets",packet_limit);
322 */
323 }
324 fail
325 {
326 perror(config_filename);
327 puts("Warning - using built-in defaults instead ...");
328 }
329 done; /* ugly macro end */
330 printf("\n");
331
332 /* leaf discipline for keywords */
333 for_each(keyword,keywords)
334 {
335 if(!strcmpi(keyword->leaf_discipline, ""))
336 {
337 keyword->leaf_discipline = qos_leaf;
338 }
339 }
340
341 if(strcmpi(cnf, "mark"))
342 {
343 filter_type = 2;
344 mark = "CLASSIFY";
345 mark_iptables = "CLASSIFY --set-class 1:";
346 }
347 else
348 {
349 filter_type = 1;
350 mark = "MARK";
351 mark_iptables = "MARK --set-mark ";
352 }
353
354 /* are supplied values meaningful ?*/
355 if(line<=0 || up<=0)
356 {
357 puts("Illegal value of LAN or WAN bandwidth: 0 kbps.");
358 reject_config_and_exit(config_filename);
359 }
360 }
361
362 /* ===================== traffic analyser - uses iptables ================ */
363
364 void get_traffic_statistics(void)
365 {
366 char *str,*cmd;
367 int downloadflag=0;
368
369 textfile(Pipe,str) *line,*lines=NULL;
370 string(str,STRLEN);
371 string(cmd,STRLEN);
372
373 sprintf(cmd,"%s -L -v -x -n -t mangle",iptables);
374 shell(cmd);
375 input(str,STRLEN)
376 {
377 create(line,Pipe);
378 line->str=str;
379 string(str,STRLEN);
380 append(line,lines);
381 }
382
383 for_each(line,lines)
384 {
385 int col, accept=0,proxyflag=0,valid=1,setchainname=0,commonflag=0;
386 unsigned long long traffic=0;
387 unsigned long pkts=0;
388 char *ipaddr=NULL,*ptr;
389
390 /* debug puts(line->str); */
391 valid_columns(ptr,line->str,' ',col)
392 if(valid) switch(col)
393 {
394 case 1: if(eq(ptr,"Chain"))
395 {
396 setchainname=1;
397 }
398 else if(eq(ptr,"pkts"))
399 {
400 valid=0;
401 }
402 else
403 {
404 sscanf(ptr,"%lu",&pkts);
405 }
406 break;
407 case 2: if(setchainname)
408 {
409 if(!strncmp(ptr,"post_",5) || eq(ptr,"POSTROUTING"))
410 {
411 downloadflag = 1;
412 }
413 else
414 {
415 if(!strncmp(ptr,"forw_",5) || eq(ptr,"FORWARD"))
416 {
417 downloadflag = 0;
418 }
419 }
420 if(eq(ptr,"post_common") || eq(ptr,"forw_common"))
421 {
422 commonflag = 1;
423 }
424 }
425 else
426 {
427 sscanf(ptr,"%Lu",&traffic);
428 traffic += (1<<19);
429 traffic >>= 20;
430 }
431 break;
432 case 3: if((strncmp(ptr,"post_",5) && strncmp(ptr,"forw_",5)) || commonflag)
433 {
434 accept=eq(ptr,mark);
435 }
436 /*if(filter_type==1) accept=eq(ptr,"MARK"); else accept=eq(ptr,"CLASSIFY");*/
437 break;
438 case 8: if(downloadflag)
439 {
440 if(strstr(proxy_ip,ptr))
441 {
442 proxyflag=1;
443 }
444 }
445 else
446 {
447 ipaddr=ptr;
448 }
449 break;
450 case 9: if(downloadflag)ipaddr=ptr;break;
451 }
452
453 if(accept && traffic>0 && ipaddr)
454 {
455 if(proxyflag)
456 {
457 printf("(proxy) ");
458 }
459 else if(!downloadflag)
460 {
461 printf("(upload) ");
462 }
463 printf("IP %s: %Lu MB (%ld pkts)\n", ipaddr, traffic, pkts);
464
465 if_exists(ip,ips,eq(ip->addr,ipaddr));
466 else
467 {
468 TheIP();
469 ip->addr = ipaddr;
470 if(eq(ip->addr,"0.0.0.0/0"))
471 {
472 ip->name = "(unregistered)";
473 ip->min = free_min;
474 ip->max = ip->desired=free_max;
475 }
476 else
477 {
478 ip->name = ipaddr;
479 }
480 }
481
482 if(downloadflag)
483 {
484 if(proxyflag)
485 {
486 ip->proxy=traffic;
487 }
488 else
489 {
490 ip->traffic+=traffic;
491 }
492 ip->direct=ip->traffic-ip->upload-ip->proxy;
493 ip->pktsdown=pkts;
494 }
495 else
496 {
497 ip->upload=traffic;
498 ip->pktsup=pkts;
499 if(include_upload)
500 {
501 ip->traffic+=traffic;
502 }
503 else
504 {
505 if(traffic>ip->traffic)
506 {
507 ip->traffic=traffic;
508 }
509 }
510 }
511 }
512 }
513 free(cmd);
514 }
515
516 /* ========== This function executes, logs OR ALSO prints command ========== */
517
518 void safe_run(char *cmd)
519 {
520 if(dry_run)
521 {
522 printf("\n=>%s\n",cmd);
523 }
524 else
525 {
526 system(cmd);
527 }
528 if(log_file)
529 {
530 fprintf(log_file,"%s\n",cmd);
531 }
532 }
533
534 void save_line(char *line)
535 {
536 fprintf(iptables_file,"%s\n",line);
537 }
538
539 void run_restore(void)
540 {
541 char *restor;
542 string(restor,STRLEN);
543
544 /*-----------------------------------------------------------------*/
545 printf("Running %s <%s ...\n", iptablesrestore, iptablesfile);
546 /*-----------------------------------------------------------------*/
547
548 save_line("COMMIT");
549 fclose(iptables_file);
550 if(dry_run)
551 {
552 parse(iptablesfile)
553 {
554 printf("%s\n",_);
555 }
556 done; /* ugly macro end */
557 }
558
559 sprintf(restor,"%s <%s",iptablesrestore, iptablesfile);
560 safe_run(restor);
561
562 free(restor);
563 }
564
565 char *parse_datafile_line(char *str)
566 {
567 char *ptr=strchr(str,' ');
568
569 if(ptr)
570 {
571 *ptr=0;
572 ptr++;
573 return ptr;
574 }
575 else
576 {
577 return NULL;
578 }
579 }
580
581
582 /*-----------------------------------------------------------------*/
583 /* Are you looking for int main(int argc, char **argv) ? :-)) */
584 /*-----------------------------------------------------------------*/
585
586 program
587 {
588 int i=0; /* just plain old Fortran style integer :-) */
589 FILE *f=NULL; /* everything is just stream of bytes... */
590 char *str, *ptr, *d; /* LET A$=B$ :-) */
591 char *substring;
592
593 int parent = 1;
594 int just_flush = FALSE; /* deactivates all previous actions */
595 int nodelay = FALSE;
596 int just_preview = FALSE; /* preview - generate just stats */
597 int start_shaping = FALSE; /* apply FUP - requires classmap file */
598 int just_logs = FALSE; /* just parse logs */
599 int run = FALSE;
600 int total = 0;
601
602 char *chain_forward, *chain_postrouting;
603 char *althosts=NULL;
604
605 printf("\n\
606 Prometheus QoS - \"fair-per-IP\" Quality of Service setup utility.\n\
607 Version %s - Copyright (C)2005-2012 Michael Polak, Arachne Labs\n\
608 iptables-restore & burst tunning & classify modification by Ludva\n\
609 Credit: CZFree.Net, Martin Devera, Netdave, Aquarius, Gandalf\n\n",version);
610
611 /*----- Boring... we have to check command line options first: ----*/
612 arguments
613 {
614 argument("-c") { nextargument(config); }
615 argument("-h") { nextargument(althosts);}
616 argument("-d") { run=TRUE; dry_run=TRUE; }
617 argument("-f") { run=TRUE; just_flush=TRUE; }
618 argument("-9") { run=TRUE; just_flush=9; }
619 argument("-p") { run=TRUE; just_preview=TRUE; }
620 argument("-s") { run=TRUE; just_preview=TRUE; start_shaping=TRUE; }
621 argument("-r") { run=TRUE; }
622 argument("-n") { run=TRUE; nodelay=TRUE; }
623 argument("-l") { just_logs=TRUE; }
624 argument("-m") { just_logs=TRUE; }
625 argument("-y") { just_logs=TRUE; }
626 argument("-?") { help(); exit(0); }
627 argument("--help") { help(); exit(0); }
628 argument("-v") { exit(0); }
629 argument("--version") { exit(0); }
630 }
631
632 if(dry_run)
633 {
634 puts("*** THIS IS JUST DRY RUN ! ***\n");
635 }
636
637 date(d); /* this is typical cll1.h macro - prints current date */
638
639 /*-----------------------------------------------------------------*/
640 printf("Parsing configuration file %s ...\n", config);
641 /*-----------------------------------------------------------------*/
642 get_config(config);
643
644 if(just_logs)
645 {
646 parse_ip_log(argc,argv);
647 exit(0);
648 }
649 else if(not run)
650 {
651 help();
652 exit(0);
653 }
654
655 if(althosts)
656 {
657 hosts=althosts;
658 }
659
660 if(just_flush<9)
661 {
662 /*-----------------------------------------------------------------*/
663 puts("Parsing iptables verbose output ...");
664 /*-----------------------------------------------------------------*/
665 get_traffic_statistics();
666 }
667
668 /*-----------------------------------------------------------------*/
669 printf("Parsing class defintion file %s ...\n", hosts);
670 /*-----------------------------------------------------------------*/
671 parse_hosts(hosts);
672
673 /*-----------------------------------------------------------------*/
674 /* cll1.h - let's allocate brand new character buffer... */
675 /*-----------------------------------------------------------------*/
676 string(str,STRLEN);
677
678 /*-----------------------------------------------------------------*/
679 puts("Resolving shared connections ...");
680 /*-----------------------------------------------------------------*/
681 for_each(ip,ips) if(ip->sharing)
682 {
683 for_each(sharedip,ips) if(eq(sharedip->name,ip->sharing))
684 {
685 sharedip->traffic+=ip->traffic;
686 ip->traffic=0;
687 ip->mark=sharedip->mark;
688 ip->lmsid=sharedip->lmsid;
689 break;
690 }
691 if(not sharedip)
692 {
693 printf("Unresolved shared connection: %s %s sharing-%s\n",
694 ip->addr, ip->name, ip->sharing);
695 }
696 }
697
698 if(enable_credit && just_flush<9)
699 {
700 /*-----------------------------------------------------------------*/
701 printf("Parsing credit file %s ...\n", credit);
702 /*-----------------------------------------------------------------*/
703 parse(credit)
704 {
705 ptr=parse_datafile_line(_);
706 if(ptr)
707 {
708 if_exists(ip,ips,eq(ip->addr,_))
709 {
710 sscanf(ptr,"%Lu",&(ip->credit));
711 }
712 }
713 }
714 done; /* ugly macro end */
715 }
716
717 if(!just_preview)
718 {
719 /*-----------------------------------------------------------------*/
720 puts("Initializing iptables and tc classes ...");
721 /*-----------------------------------------------------------------*/
722
723 iptables_file=fopen(iptablesfile,"w");
724 if(iptables_file == NULL)
725 {
726 puts("Cannot open iptablesfile!");
727 exit(-1);
728 }
729
730 log_file=fopen(cmdlog,"w");
731 if(log_file == NULL)
732 {
733 puts("Cannot open logfile!");
734 exit(-1);
735 }
736
737 save_line(iptablespreamble);
738 run_restore();
739
740 sprintf(str,"%s qdisc del dev %s root 2>/dev/null",tc,lan);
741 safe_run(str);
742
743 sprintf(str,"%s qdisc del dev %s root 2>/dev/null",tc,wan);
744 safe_run(str);
745
746 iptables_file=fopen(iptablesfile,"w");
747 save_line(iptablespreamble);
748
749 if(qos_free_zone && *qos_free_zone!='0')
750 {
751 char *chain;
752
753 sprintf(str,"-A FORWARD -d %s -o %s -j ACCEPT", qos_free_zone, wan);
754 save_line(str);
755
756 if(qos_proxy)
757 {
758 save_line(":post_noproxy - [0:0]");
759 sprintf(str,"-A POSTROUTING ! -p tcp -o %s -j post_noproxy", lan);
760 save_line(str);
761 sprintf(str,"-A POSTROUTING ! -s %s -o %s -j post_noproxy", proxy_ip, lan);
762 save_line(str);
763 sprintf(str,"-A POSTROUTING -s %s -p tcp ! --sport %d -o %s -j post_noproxy", proxy_ip, proxy_port, lan);
764 save_line(str);
765
766 chain="post_noproxy";
767 }
768 else
769 {
770 chain="POSTROUTING";
771 }
772
773 sprintf(str,"-A %s -s %s -o %s -j ACCEPT", chain, qos_free_zone, lan);
774 save_line(str);
775 }
776
777 if(ip_count>idxtable_treshold1 && !just_flush)
778 {
779 int idxcount=0, bitmask=32-idxtable_bitmask1; /* default net mask: 255.255.255.240 */
780 char *subnet, *buf;
781 /*-----------------------------------------------------------------*/
782 printf("Detected %d addresses - indexing iptables rules to improve performance...\n",ip_count);
783 /*-----------------------------------------------------------------*/
784
785 save_line(":post_common - [0:0]");
786 save_line(":forw_common - [0:0]");
787
788 for_each(ip,ips) if(ip->addr && *(ip->addr) && !eq(ip->addr,"0.0.0.0/0"))
789 {
790 buf=index_id(ip->addr,bitmask);
791 if_exists(idx,idxs,eq(idx->id,buf))
792 {
793 idx->children++;
794 }
795 else
796 {
797 create(idx,Index);
798 idx->addr=ip->addr;
799 idx->id=buf;
800 idx->bitmask=bitmask;
801 idx->parent=NULL;
802 idx->children=0;
803 idxcount++;
804 push(idx,idxs);
805 }
806 }
807
808 /* brutal perfomance optimalization */
809 while(idxcount>idxtable_treshold2 && bitmask>2*idxtable_bitmask2)
810 {
811 bitmask-=idxtable_bitmask2;
812 idxcount=0;
813
814 for_each(idx,idxs) if(idx->parent == NULL)
815 {
816 buf=index_id(idx->addr,bitmask);
817 if_exists(metaindex,idxs,eq(metaindex->id,buf))
818 {
819 metaindex->children++;
820 }
821 else
822 {
823 create(metaindex,Index);
824 metaindex->addr=idx->addr;
825 metaindex->id=buf;
826 metaindex->bitmask=bitmask;
827 metaindex->parent=NULL;
828 metaindex->children=0;
829 idxcount++;
830 push(metaindex,idxs);
831 }
832 idx->parent=metaindex;
833 }
834 }
835
836 /* this should slightly optimize throughout ... */
837 sort(idx,idxs,desc_order_by,children);
838 sort(idx,idxs,order_by,bitmask);
839
840 i=0;
841 for_each(idx,idxs)
842 {
843 subnet=subnet_id(idx->addr,idx->bitmask);
844 printf("%d: %s/%d\n",
845 ++i, subnet, idx->bitmask);
846
847 sprintf(str,":post_%s - [0:0]", idx->id);
848 save_line(str);
849
850 sprintf(str,":forw_%s - [0:0]", idx->id);
851 save_line(str);
852
853 if(idx->parent)
854 {
855 string(buf,strlen(idx->parent->id)+6);
856 sprintf(buf,"post_%s",idx->parent->id);
857 }
858 else
859 {
860 buf="POSTROUTING";
861 }
862
863 sprintf(str,"-A %s -d %s/%d -o %s -j post_%s", buf, subnet, idx->bitmask, lan, idx->id);
864 save_line(str);
865
866 sprintf(str,"-A %s -d %s/%d -o %s -j post_common", buf, subnet, idx->bitmask, lan);
867 save_line(str);
868
869 if(idx->parent)
870 {
871 string(buf,strlen(idx->parent->id)+6);
872 sprintf(buf,"forw_%s",idx->parent->id);
873 }
874 else
875 {
876 buf="FORWARD";
877 }
878
879 sprintf(str,"-A %s -s %s/%d -o %s -j forw_%s", buf, subnet, idx->bitmask, wan, idx->id);
880 save_line(str);
881
882 sprintf(str,"-A %s -s %s/%d -o %s -j forw_common", buf, subnet, idx->bitmask, wan);
883 save_line(str);
884 }
885 printf("Total indexed iptables chains created: %d\n", i);
886
887 sprintf(str,"-A FORWARD -o %s -j forw_common", wan);
888 save_line(str);
889
890 sprintf(str,"-A POSTROUTING -o %s -j post_common", lan);
891 save_line(str);
892 }
893
894 }
895
896 if(just_flush)
897 {
898 fclose(iptables_file);
899 if(log_file)
900 {
901 fclose(log_file);
902 }
903 puts("Just flushed iptables and tc classes - now exiting ...");
904 exit(0);
905 }
906
907 if(!just_preview)
908 {
909 if(!dry_run && !nodelay && qos_free_delay)
910 {
911 printf("Flushed iptables and tc classes - now sleeping for %d seconds...\n",qos_free_delay);
912 sleep(qos_free_delay);
913 }
914
915 sprintf(str,"%s qdisc add dev %s root handle 1: htb r2q %d default 1",
916 tc,lan,htb_r2q);
917 safe_run(str);
918
919 sprintf(str, "%s class add dev %s parent 1: classid 1:2 htb rate %s ceil %s burst %dk prio %d",
920 tc,lan,lan_medium,lan_medium,burst_main,highest_priority);
921 safe_run(str);
922
923 sprintf(str, "%s class add dev %s parent 1:2 classid 1:1 htb rate %Ldkbit ceil %Ldkbit burst %dk prio %d",
924 tc,lan,line,line,burst_main,highest_priority);
925 safe_run(str);
926
927 sprintf(str,"%s qdisc add dev %s root handle 1: htb r2q %d default 1",tc,wan,htb_r2q);
928 safe_run(str);
929
930 sprintf(str, "%s class add dev %s parent 1: classid 1:2 htb rate %s ceil %s burst %dk prio %d",
931 tc,wan,wan_medium,wan_medium,burst_main,highest_priority);
932 safe_run(str);
933
934 sprintf(str, "%s class add dev %s parent 1:2 classid 1:1 htb rate %Ldkbit ceil %Ldkbit burst %dk prio %d",
935 tc,wan,up,up,burst_main,highest_priority);
936 safe_run(str);
937 }
938
939 /*-----------------------------------------------------------------*/
940 puts("Locating heavy downloaders and generating root classes ...");
941 /*-----------------------------------------------------------------*/
942 sort(ip,ips,desc_order_by,traffic);
943
944 /*-----------------------------------------------------------------*/
945 /* sub-scope - local variables */
946 {
947 long long int rate = line;
948 long long int max = line;
949 int group_count = 0;
950 FILE *credit_file = NULL;
951
952 if(!just_preview && !dry_run && enable_credit)
953 {
954 credit_file = fopen(credit,"w");
955 }
956
957 for_each(group,groups)
958 {
959 if(!just_preview)
960 {
961 //download
962 sprintf(str,"%s class add dev %s parent 1:%d classid 1:%d htb rate %Ldkbit ceil %Ldkbit burst %dk prio %d #down desired %d",
963 tc, lan, parent, group->id, rate, max, burst_group, highest_priority+1, group->desired);
964 safe_run(str);
965
966 //upload
967 sprintf(str,"%s class add dev %s parent 1:%d classid 1:%d htb rate %Ldkbit ceil %Ldkbit burst %dk prio %d #up desired %d",
968 tc, wan, parent, group->id, rate*up/line, max*up/line, burst_group, highest_priority+1, group->desired);
969 safe_run(str);
970 }
971
972 if(group_count++ < max_nesting)
973 {
974 parent = group->id;
975 }
976
977 rate -= digital_divide*group->min;
978 if(rate < group->min)
979 {
980 rate = group->min;
981 }
982
983 /*shaping of aggresive downloaders, with credit file support */
984 if(use_credit)
985 {
986 int group_rate = group->min, priority_sequence = lowest_priority;
987
988 for_each(ip, ips) if(ip->min == group->min && ip->max > ip->min)
989 {
990 ip->realquota=ip->credit+(ip->min*ip->keyword->data_limit+(ip->keyword->fixed_limit<<20));
991 if( ip->keyword->data_limit
992 and not ip->fixedprio
993 and ip->traffic > ip->realquota )
994 {
995 if(group_rate < ip->max)
996 {
997 ip->max = group_rate;
998 }
999 group_rate+=magic_treshold;
1000 ip->prio=lowest_priority;
1001 if(ip->prio<highest_priority+2)
1002 {
1003 ip->prio=highest_priority+2;
1004 }
1005 }
1006 else
1007 {
1008 if( ip->keyword->data_prio
1009 && !ip->fixedprio
1010 && ( ip->traffic>ip->credit
1011 + (ip->min*ip->keyword->data_prio+(ip->keyword->fixed_prio<<20))) )
1012 {
1013 ip->prio=priority_sequence--;
1014 if(ip->prio<highest_priority+1)
1015 {
1016 ip->prio=highest_priority+1;
1017 }
1018 }
1019
1020 if(credit_file)
1021 {
1022 unsigned long long lcredit=0;
1023
1024 if((ip->min*ip->keyword->data_limit+(ip->keyword->fixed_limit<<20))>ip->traffic)
1025 {
1026 lcredit=(ip->min*ip->keyword->data_limit+(ip->keyword->fixed_limit<<20))-ip->traffic;
1027 }
1028 fprintf(credit_file,"%s %Lu\n",ip->addr,lcredit);
1029 }
1030 }
1031 }
1032 }
1033 }
1034 if(credit_file)
1035 {
1036 fclose(credit_file);
1037 }
1038 }
1039
1040 if(just_preview)
1041 {
1042 if(start_shaping)
1043 {
1044 printf("Reading %s and applying Fair Use Policy rules ... \n", classmap);
1045 parse(classmap)
1046 {
1047 ptr=strchr(_,' ');
1048 if(ptr)
1049 {
1050 *ptr=0;
1051 ptr++;
1052 if_exists(ip,ips,eq(ip->addr,_))
1053 {
1054 ip->mark=atoi(ptr);
1055 if(ip->max < ip->desired) /* apply FUP limit immediately.... */
1056 {
1057 printf("Applying limit for %-22s %-16s %04d ", ip->name, ip->addr, ip->mark);
1058 printf("(down: %dk-%dk ", ip->min, ip->max);
1059 sprintf(str, "%s class change dev %s parent 1:%d classid 1:%d htb rate %dkbit ceil %dkbit burst %dk prio %d",
1060 tc, lan, ip->group, ip->mark,ip->min,ip->max, burst, ip->prio);
1061 safe_run(str);
1062 printf("up: %dk-%dk)\n", (int)((ip->min/ip->keyword->asymetry_ratio)-ip->keyword->asymetry_fixed),
1063 (int)((ip->max/ip->keyword->asymetry_ratio)-ip->keyword->asymetry_fixed));
1064 sprintf(str,"%s class change dev %s parent 1:%d classid 1:%d htb rate %dkbit ceil %dkbit burst %dk prio %d",
1065 tc, wan, ip->group, ip->mark,
1066 (int)((ip->min/ip->keyword->asymetry_ratio)-ip->keyword->asymetry_fixed),
1067 (int)((ip->max/ip->keyword->asymetry_ratio)-ip->keyword->asymetry_fixed), burst, ip->prio);
1068 safe_run(str);
1069 }
1070 }
1071 }
1072 }
1073 fail
1074 {
1075 perror(classmap);
1076 puts("Warning - classmap file not fund, just generating preview ...");
1077 start_shaping=FALSE;
1078 }
1079 done; /* ugly macro end */
1080 }
1081 html=preview;
1082 json_traffic=json_preview;
1083 }
1084
1085 if(!dry_run && !just_flush)
1086 {
1087 /*-----------------------------------------------------------------*/
1088 printf("Writing json traffic overview %s ... ", json_traffic);
1089 /*-----------------------------------------------------------------*/
1090 write_json_traffic(json_traffic);
1091 }
1092
1093 /*-----------------------------------------------------------------*/
1094 printf("Writing statistics into HTML page %s ...\n", html);
1095 /*-----------------------------------------------------------------*/
1096 write_htmlandlogs(html, d,total, just_preview);
1097
1098 if(just_preview)
1099 {
1100 char swchar='p';
1101 if(start_shaping)
1102 {
1103 swchar='s';
1104 }
1105 printf("Statistics preview generated (-%c switch) - now exiting ...\n", swchar);
1106 exit(0);
1107 }
1108
1109 i=0;
1110 #ifdef DEBUG
1111 printf("%-22s %-15s mark\n","name","ip");
1112 #endif
1113
1114 printf("Writing %s ... ", classmap);
1115 f = fopen(classmap, "w");
1116 if(f < 0)
1117 {
1118 perror(classmap);
1119 }
1120
1121 /*-----------------------------------------------------------------*/
1122 puts("Generating iptables and tc classes ... ");
1123 /*-----------------------------------------------------------------*/
1124
1125 for_each(ip, ips) if(ip->mark > 0)
1126 {
1127 if(idxs)
1128 {
1129 char *buf;
1130 duplicate(ip->addr,buf);
1131 buf=index_id(ip->addr,32-idxtable_bitmask1);
1132
1133 string(chain_forward,6+strlen(buf));
1134 strcpy(chain_forward,"forw_");
1135 strcat(chain_forward,buf);
1136
1137 string(chain_postrouting,6+strlen(buf));
1138 strcpy(chain_postrouting,"post_");
1139 strcat(chain_postrouting,buf);
1140
1141 free(buf);
1142 }
1143 else
1144 {
1145 chain_forward="FORWARD";
1146 chain_postrouting="POSTROUTING";
1147 }
1148
1149 #ifdef DEBUG
1150 printf("%-22s %-16s %04d ", ip->name, ip->addr, ip->mark);
1151 #endif
1152
1153 /* -------------------------------------------------------- mark download */
1154
1155 sprintf(str, "-A %s -d %s/32 -o %s -j %s%d",
1156 chain_postrouting, ip->addr, lan, mark_iptables, ip->mark);
1157 /*sprintf(str,"-A %s -d %s/32 -o %s -j MARK --set-mark %d",chain_postrouting,ip->addr,lan,ip->mark);*/
1158 /* -m limit --limit 1/s */
1159 save_line(str);
1160
1161 if(qos_proxy)
1162 {
1163 sprintf(str, "-A %s -s %s -p tcp --sport %d -d %s/32 -o %s -j %s%d",
1164 chain_postrouting, proxy_ip, proxy_port, ip->addr, lan, mark_iptables, ip->mark);
1165 /*sprintf(str,"-A %s -s %s -p tcp --sport %d -d %s/32 -o %s -j MARK --set-mark %d",chain_postrouting,proxy_ip,proxy_port,ip->addr,lan,ip->mark);*/
1166 save_line(str);
1167 }
1168
1169 sprintf(str, "-A %s -d %s/32 -o %s -j ACCEPT",
1170 chain_postrouting, ip->addr, lan);
1171 save_line(str);
1172
1173 /* -------------------------------------------------------- mark upload */
1174 sprintf(str, "-A %s -s %s/32 -o %s -j %s%d",
1175 chain_forward, ip->addr, wan, mark_iptables, ip->mark);
1176 /* sprintf(str,"-A %s -s %s/32 -o %s -j MARK --set-mark %d",chain_forward,ip->addr,wan,ip->mark);*/
1177 save_line(str);
1178
1179 sprintf(str, "-A %s -s %s/32 -o %s -j ACCEPT",
1180 chain_forward, ip->addr, wan);
1181 save_line(str);
1182
1183 if(ip->min)
1184 {
1185 /* -------------------------------------------------------- download class */
1186 #ifdef DEBUG
1187 printf("(down: %dk-%dk ", ip->min, ip->max);
1188 #endif
1189
1190 sprintf(str, "%s class add dev %s parent 1:%d classid 1:%d htb rate %dkbit ceil %dkbit burst %dk prio %d",
1191 tc, lan, ip->group, ip->mark,ip->min,ip->max, burst, ip->prio);
1192 safe_run(str);
1193
1194 if(strcmpi(ip->keyword->leaf_discipline, "none"))
1195 {
1196 sprintf(str, "%s qdisc add dev %s parent 1:%d handle %d %s",
1197 tc, lan, ip->mark, ip->mark, ip->keyword->leaf_discipline); /*qos_leaf*/
1198 safe_run(str);
1199 }
1200
1201 if(filter_type == 1)
1202 {
1203 sprintf(str, "%s filter add dev %s parent 1:0 protocol ip handle %d fw flowid 1:%d",
1204 tc, lan, ip->mark, ip->mark);
1205 safe_run(str);
1206 }
1207
1208 /* -------------------------------------------------------- upload class */
1209 #ifdef DEBUG
1210 printf("up: %dk-%dk)\n", (int)((ip->min/ip->keyword->asymetry_ratio)-ip->keyword->asymetry_fixed),
1211 (int)((ip->max/ip->keyword->asymetry_ratio)-ip->keyword->asymetry_fixed));
1212 #endif
1213
1214 sprintf(str,"%s class add dev %s parent 1:%d classid 1:%d htb rate %dkbit ceil %dkbit burst %dk prio %d",
1215 tc, wan, ip->group, ip->mark,
1216 (int)((ip->min/ip->keyword->asymetry_ratio)-ip->keyword->asymetry_fixed),
1217 (int)((ip->max/ip->keyword->asymetry_ratio)-ip->keyword->asymetry_fixed), burst, ip->prio);
1218 safe_run(str);
1219
1220 if(strcmpi(ip->keyword->leaf_discipline, "none"))
1221 {
1222 sprintf(str, "%s qdisc add dev %s parent 1:%d handle %d %s",
1223 tc, wan, ip->mark, ip->mark, ip->keyword->leaf_discipline); /*qos_leaf*/
1224 safe_run(str);
1225 }
1226
1227 if(filter_type == 1)
1228 {
1229 sprintf(str, "%s filter add dev %s parent 1:0 protocol ip handle %d fw flowid 1:%d",
1230 tc, wan, ip->mark, ip->mark);
1231 safe_run(str);
1232 }
1233
1234 if(f > 0)
1235 {
1236 fprintf(f, "%s %d\n", ip->addr, ip->mark);
1237 }
1238 }
1239 else
1240 {
1241 #ifdef DEBUG
1242 printf("(sharing %s)\n", ip->sharing);
1243 #endif
1244 }
1245 i++;
1246 }
1247 if(f > 0)
1248 {
1249 puts("done.");
1250 fclose(f);
1251 }
1252
1253 if(idxs)
1254 {
1255 chain_forward = "forw_common";
1256 chain_postrouting = "post_common";
1257 }
1258 else
1259 {
1260 chain_forward = "FORWARD";
1261 chain_postrouting = "POSTROUTING";
1262 }
1263 /* -------------------------------- classify or reject free download */
1264 {
1265 char *final_chain = "DROP"; /* REJECT would be better, but it is impossible in mangle */
1266 if(free_min)
1267 {
1268 final_chain = "ACCEPT";
1269 }
1270 if(qos_proxy)
1271 {
1272 if(free_min)
1273 {
1274 sprintf(str,"-A %s -s %s -p tcp --sport %d -o %s -j %s%d",
1275 chain_postrouting,proxy_ip,proxy_port,lan,mark_iptables,3);
1276 save_line(str);
1277 }
1278 sprintf(str,"-A %s -s %s -p tcp --sport %d -o %s -j %s",
1279 chain_postrouting,proxy_ip,proxy_port,lan,final_chain);
1280 save_line(str);
1281 }
1282 if(free_min)
1283 {
1284 sprintf(str,"-A %s -o %s -j %s%d", chain_postrouting, lan, mark_iptables, 3);
1285 save_line(str);
1286 }
1287 sprintf(str,"-A %s -o %s -j %s", chain_postrouting, lan, final_chain);
1288 save_line(str);
1289 /* ------------------------------- classify or reject free upload */
1290 if(free_min)
1291 {
1292 sprintf(str,"-A %s -o %s -j %s%d", chain_forward, wan, mark_iptables, 3);
1293 save_line(str);
1294 }
1295 sprintf(str,"-A %s -o %s -j %s", chain_forward, wan, final_chain);
1296 save_line(str);
1297 }
1298
1299 if(free_min) /* allocate free bandwith if it is not zero... */
1300 {
1301 /*-----------------------------------------------------------------*/
1302 puts("Generating free bandwith classes ...");
1303 /*-----------------------------------------------------------------*/
1304 sprintf(str, "%s class add dev %s parent 1:%d classid 1:3 htb rate %dkbit ceil %dkbit burst %dk prio %d",
1305 tc, lan, parent, free_min, free_max,burst, lowest_priority);
1306 safe_run(str);
1307 sprintf(str, "%s class add dev %s parent 1:%d classid 1:3 htb rate %dkbit ceil %dkbit burst %dk prio %d",
1308 tc, wan, parent, free_min, free_max, burst, lowest_priority);
1309 safe_run(str);
1310 /* tc SFQ */
1311 if(strcmpi(qos_leaf, "none"))
1312 {
1313 sprintf(str,"%s qdisc add dev %s parent 1:3 handle 3 %s", tc, lan, qos_leaf);
1314 safe_run(str);
1315
1316 sprintf(str,"%s qdisc add dev %s parent 1:3 handle 3 %s", tc, wan, qos_leaf);
1317 safe_run(str);
1318 }
1319 /* tc handle 1 fw flowid */
1320 sprintf(str,"%s filter add dev %s parent 1:0 protocol ip handle 3 fw flowid 1:3", tc, lan);
1321 safe_run(str);
1322
1323 sprintf(str,"%s filter add dev %s parent 1:0 protocol ip handle 3 fw flowid 1:3", tc, wan);
1324 safe_run(str);
1325 }
1326 printf("Total IP count: %d\n", i);
1327 run_restore();
1328 if(log_file)
1329 {
1330 fclose(log_file);
1331 }
1332 return 0;
1333 /* that's all folks, thank you for reading it all the way up to this point ;-) */
1334 /* bad luck C<<1 is not yet finished, I promise no sprintf() next time... */
1335 }
This page took 1.619623 seconds and 4 git commands to generate.