separate mega-module htmlandlogs.c
[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-2012 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, 20121011
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) defaultkeyword=keyword;
236 keywordcount++;
237
238 kwd=NULL;
239 }
240 else
241 {
242 for_each(keyword,keywords)
243 {
244 int l=strlen(keyword->key);
245
246 if(!strncmp(keyword->key,_,l) && strlen(_)>l+2)
247 {
248 char *tmptr=_; /* <---- l+1 ----> */
249 _+=l+1; /* via-prometheus-asymetry-ratio, etc. */
250 ioption("asymetry-ratio",keyword->asymetry_ratio);
251 ioption("asymetry-treshold",keyword->asymetry_fixed);
252 ioption("magic-relative-limit",keyword->data_limit);
253 ioption("magic-relative-prio",keyword->data_prio);
254 loption("magic-fixed-limit",keyword->fixed_limit);
255 loption("magic-fixed-prio",keyword->fixed_prio);
256 ioption("htb-default-prio",keyword->default_prio);
257 ioption("htb-rate-bonus",keyword->reserve_min);
258 ioption("htb-ceil-malus",keyword->reserve_max);
259 option("leaf-discipline",keyword->leaf_discipline);
260 option("html-color",keyword->html_color);
261 _=tmptr;
262
263 if(keyword->data_limit || keyword->fixed_limit ||
264 keyword->data_prio || keyword->fixed_prio)
265 {
266 use_credit=1;
267 }
268 }
269 }
270 }
271
272 option("tc",tc);
273 option("iptables",iptables);
274 option("iptables-save",iptablessave); /* new */
275 option("iptables-restore",iptablesrestore); /* new */
276 option("iptables-in-filename",iptablesfile); /* new */
277 option("hosts",hosts);
278 option("lan-interface",lan);
279 option("wan-interface",wan);
280 option("lan-medium",lan_medium);
281 option("wan-medium",wan_medium);
282 lloption("wan-download",line);
283 lloption("wan-upload",up);
284 ioption("hall-of-fame-enable",hall_of_fame);
285 option("hall-of-fame-title",title);
286 option("hall-of-fame-filename",html);
287 option("json-filename",json_traffic);
288 option("hall-of-fame-preview",preview);
289 option("json-preview",json_preview);
290 option("log-filename",cmdlog);
291 option("credit-filename",credit);
292 option("classmap-filename",classmap);
293 ioption("credit-enable",enable_credit);
294 option("log-traffic-directory",log_dir);
295 option("log-traffic-html-directory",html_log_dir);
296 option("log-traffic-url-path",log_url);
297 option("jquery-url",jquery_url);
298 option("lms-url",lms_url);
299 ioption("use-jquery-popups",use_jquery_popups);
300 option("qos-free-zone",qos_free_zone);
301 ioption("qos-free-delay",qos_free_delay);
302 ioption("qos-proxy-enable",qos_proxy);
303 option("qos-proxy-ip",proxy_ip);
304 option("htb-leaf-discipline",qos_leaf);
305 ioption("qos-proxy-port",proxy_port);
306 ioption("free-rate",free_min);
307 ioption("free-ceil",free_max);
308 ioption("htb-burst",burst);
309 ioption("htb-burst-main",burst_main);
310 ioption("htb-burst-group",burst_group);
311 ioption("htb-nesting-limit",max_nesting);
312 ioption("htb-r2q",htb_r2q);
313 ioption("magic-include-upload",include_upload);
314 ioption("magic-treshold",magic_treshold);
315 option("filter-type", cnf);
316 /* not yet implemented:
317 ioption("magic-fixed-packets",fixed_packets);
318 ioption("magic-relative-packets",packet_limit);
319 */
320 }
321 fail
322 {
323 perror(config_filename);
324 puts("Warning - using built-in defaults instead ...");
325 }
326 done; /* ugly macro end */
327 printf("\n");
328
329 /* leaf discipline for keywords */
330 for_each(keyword,keywords)
331 {
332 if(!strcmpi(keyword->leaf_discipline, ""))
333 {
334 keyword->leaf_discipline = qos_leaf;
335 }
336 }
337
338 if(strcmpi(cnf, "mark"))
339 {
340 filter_type = 2;
341 mark = "CLASSIFY";
342 mark_iptables = "CLASSIFY --set-class 1:";
343 }
344 else
345 {
346 filter_type = 1;
347 mark = "MARK";
348 mark_iptables = "MARK --set-mark ";
349 }
350
351 /* are supplied values meaningful ?*/
352 if(line<=0 || up<=0)
353 {
354 puts("Illegal value of LAN or WAN bandwidth: 0 kbps.");
355 reject_config_and_exit(config_filename);
356 }
357 }
358
359 /* ===================== traffic analyser - uses iptables ================ */
360
361 void get_traffic_statistics(void)
362 {
363 char *str,*cmd;
364 int downloadflag=0;
365
366 textfile(Pipe,str) *line,*lines=NULL;
367 string(str,STRLEN);
368 string(cmd,STRLEN);
369
370 sprintf(cmd,"%s -L -v -x -n -t mangle",iptables);
371 shell(cmd);
372 input(str,STRLEN)
373 {
374 create(line,Pipe);
375 line->str=str;
376 string(str,STRLEN);
377 append(line,lines);
378 }
379
380 for_each(line,lines)
381 {
382 int col, accept=0,proxyflag=0,valid=1,setchainname=0,commonflag=0;
383 unsigned long long traffic=0;
384 unsigned long pkts=0;
385 char *ipaddr=NULL,*ptr;
386
387 /* debug puts(line->str); */
388 valid_columns(ptr,line->str,' ',col)
389 if(valid) switch(col)
390 {
391 case 1: if(eq(ptr,"Chain"))
392 {
393 setchainname=1;
394 }
395 else if(eq(ptr,"pkts"))
396 {
397 valid=0;
398 }
399 else
400 {
401 sscanf(ptr,"%lu",&pkts);
402 }
403 break;
404 case 2: if(setchainname)
405 {
406 if(!strncmp(ptr,"post_",5) || eq(ptr,"POSTROUTING"))
407 {
408 downloadflag = 1;
409 }
410 else
411 {
412 if(!strncmp(ptr,"forw_",5) || eq(ptr,"FORWARD"))
413 {
414 downloadflag = 0;
415 }
416 }
417 if(eq(ptr,"post_common") || eq(ptr,"forw_common"))
418 {
419 commonflag = 1;
420 }
421 }
422 else
423 {
424 sscanf(ptr,"%Lu",&traffic);
425 traffic += (1<<19);
426 traffic >>= 20;
427 }
428 break;
429 case 3: if((strncmp(ptr,"post_",5) && strncmp(ptr,"forw_",5)) || commonflag)
430 {
431 accept=eq(ptr,mark);
432 }
433 /*if(filter_type==1) accept=eq(ptr,"MARK"); else accept=eq(ptr,"CLASSIFY");*/
434 break;
435 case 8: if(downloadflag)
436 {
437 if(strstr(proxy_ip,ptr))
438 {
439 proxyflag=1;
440 }
441 }
442 else
443 {
444 ipaddr=ptr;
445 }
446 break;
447 case 9: if(downloadflag)ipaddr=ptr;break;
448 }
449
450 if(accept && traffic>0 && ipaddr)
451 {
452 if(proxyflag)
453 {
454 printf("(proxy) ");
455 }
456 else if(!downloadflag)
457 {
458 printf("(upload) ");
459 }
460 printf("IP %s: %Lu MB (%ld pkts)\n", ipaddr, traffic, pkts);
461
462 if_exists(ip,ips,eq(ip->addr,ipaddr));
463 else
464 {
465 TheIP();
466 ip->addr=ipaddr;
467 if(eq(ip->addr,"0.0.0.0/0"))
468 {
469 ip->name="(unregistered)";
470 ip->min=free_min;
471 ip->max=ip->desired=free_max;
472 }
473 }
474
475 if(downloadflag)
476 {
477 if(proxyflag)
478 {
479 ip->proxy=traffic;
480 }
481 else
482 {
483 ip->traffic+=traffic;
484 }
485 ip->direct=ip->traffic-ip->upload-ip->proxy;
486 ip->pktsdown=pkts;
487 }
488 else
489 {
490 ip->upload=traffic;
491 ip->pktsup=pkts;
492 if(include_upload)
493 {
494 ip->traffic+=traffic;
495 }
496 else
497 {
498 if(traffic>ip->traffic)
499 {
500 ip->traffic=traffic;
501 }
502 }
503 }
504 }
505 }
506 free(cmd);
507 }
508
509 /* ========== This function executes, logs OR ALSO prints command ========== */
510
511 void safe_run(char *cmd)
512 {
513 if(dry_run)
514 {
515 printf("\n=>%s\n",cmd);
516 }
517 else
518 {
519 system(cmd);
520 }
521 if(log_file)
522 {
523 fprintf(log_file,"%s\n",cmd);
524 }
525 }
526
527 void save_line(char *line)
528 {
529 fprintf(iptables_file,"%s\n",line);
530 }
531
532 void run_restore(void)
533 {
534 char *restor;
535 string(restor,STRLEN);
536
537 /*-----------------------------------------------------------------*/
538 printf("Running %s <%s ...\n", iptablesrestore, iptablesfile);
539 /*-----------------------------------------------------------------*/
540
541 save_line("COMMIT");
542 fclose(iptables_file);
543 if(dry_run)
544 {
545 parse(iptablesfile)
546 {
547 printf("%s\n",_);
548 }
549 done; /* ugly macro end */
550 }
551
552 sprintf(restor,"%s <%s",iptablesrestore, iptablesfile);
553 safe_run(restor);
554
555 free(restor);
556 }
557
558 char *parse_datafile_line(char *str)
559 {
560 char *ptr=strchr(str,' ');
561
562 if(ptr)
563 {
564 *ptr=0;
565 ptr++;
566 return ptr;
567 }
568 else
569 {
570 return NULL;
571 }
572 }
573
574
575 /*-----------------------------------------------------------------*/
576 /* Are you looking for int main(int argc, char **argv) ? :-)) */
577 /*-----------------------------------------------------------------*/
578
579 program
580 {
581 int i=0; /* just plain old Fortran style integer :-) */
582 FILE *f=NULL; /* everything is just stream of bytes... */
583 char *str, *ptr, *d; /* LET A$=B$ :-) */
584 char *substring;
585
586 int parent = 1;
587 int just_flush = FALSE; /* deactivates all previous actions */
588 int nodelay = FALSE;
589 int just_preview = FALSE; /* preview - generate just stats */
590 int start_shaping = FALSE; /* apply FUP - requires classmap file */
591 int just_logs = FALSE; /* just parse logs */
592 int run = FALSE;
593 int total = 0;
594
595 char *chain_forward, *chain_postrouting;
596 char *althosts=NULL;
597
598 printf("\n\
599 Prometheus QoS - \"fair-per-IP\" Quality of Service setup utility.\n\
600 Version %s - Copyright (C)2005-2012 Michael Polak, Arachne Labs\n\
601 iptables-restore & burst tunning & classify modification by Ludva\n\
602 Credit: CZFree.Net, Martin Devera, Netdave, Aquarius, Gandalf\n\n",version);
603
604 /*----- Boring... we have to check command line options first: ----*/
605 arguments
606 {
607 argument("-c") { nextargument(config); }
608 argument("-h") { nextargument(althosts);}
609 argument("-d") { run=TRUE; dry_run=TRUE; }
610 argument("-f") { run=TRUE; just_flush=TRUE; }
611 argument("-9") { run=TRUE; just_flush=9; }
612 argument("-p") { run=TRUE; just_preview=TRUE; }
613 argument("-s") { run=TRUE; just_preview=TRUE; start_shaping=TRUE; }
614 argument("-r") { run=TRUE; }
615 argument("-n") { run=TRUE; nodelay=TRUE; }
616 argument("-l") { just_logs=TRUE; }
617 argument("-m") { just_logs=TRUE; }
618 argument("-y") { just_logs=TRUE; }
619 argument("-?") { help(); exit(0); }
620 argument("--help") { help(); exit(0); }
621 argument("-v") { exit(0); }
622 argument("--version") { exit(0); }
623 }
624
625 if(dry_run)
626 {
627 puts("*** THIS IS JUST DRY RUN ! ***\n");
628 }
629
630 date(d); /* this is typical cll1.h macro - prints current date */
631
632 /*-----------------------------------------------------------------*/
633 printf("Parsing configuration file %s ...\n", config);
634 /*-----------------------------------------------------------------*/
635 get_config(config);
636
637 if(just_logs)
638 {
639 parse_ip_log(argc,argv);
640 exit(0);
641 }
642 else if(not run)
643 {
644 help();
645 exit(0);
646 }
647
648 if(althosts)
649 {
650 hosts=althosts;
651 }
652
653 if(just_flush<9)
654 {
655 /*-----------------------------------------------------------------*/
656 puts("Parsing iptables verbose output ...");
657 /*-----------------------------------------------------------------*/
658 get_traffic_statistics();
659 }
660
661 /*-----------------------------------------------------------------*/
662 printf("Parsing class defintion file %s ...\n", hosts);
663 /*-----------------------------------------------------------------*/
664 parse_hosts(hosts);
665
666 /*-----------------------------------------------------------------*/
667 /* cll1.h - let's allocate brand new character buffer... */
668 /*-----------------------------------------------------------------*/
669 string(str,STRLEN);
670
671 /*-----------------------------------------------------------------*/
672 puts("Resolving shared connections ...");
673 /*-----------------------------------------------------------------*/
674 for_each(ip,ips) if(ip->sharing)
675 {
676 for_each(sharedip,ips) if(eq(sharedip->name,ip->sharing))
677 {
678 sharedip->traffic+=ip->traffic;
679 ip->traffic=0;
680 ip->mark=sharedip->mark;
681 ip->lmsid=sharedip->lmsid;
682 break;
683 }
684 if(not sharedip)
685 {
686 printf("Unresolved shared connection: %s %s sharing-%s\n",
687 ip->addr, ip->name, ip->sharing);
688 }
689 }
690
691 if(enable_credit && just_flush<9)
692 {
693 /*-----------------------------------------------------------------*/
694 printf("Parsing credit file %s ...\n", credit);
695 /*-----------------------------------------------------------------*/
696 parse(credit)
697 {
698 ptr=parse_datafile_line(_);
699 if(ptr)
700 {
701 if_exists(ip,ips,eq(ip->addr,_))
702 {
703 sscanf(ptr,"%Lu",&(ip->credit));
704 }
705 }
706 }
707 done; /* ugly macro end */
708 }
709
710 if(!just_preview)
711 {
712 /*-----------------------------------------------------------------*/
713 puts("Initializing iptables and tc classes ...");
714 /*-----------------------------------------------------------------*/
715
716 iptables_file=fopen(iptablesfile,"w");
717 if(iptables_file == NULL)
718 {
719 puts("Cannot open iptablesfile!");
720 exit(-1);
721 }
722
723 log_file=fopen(cmdlog,"w");
724 if(log_file == NULL)
725 {
726 puts("Cannot open logfile!");
727 exit(-1);
728 }
729
730 save_line(iptablespreamble);
731 run_restore();
732
733 sprintf(str,"%s qdisc del dev %s root 2>/dev/null",tc,lan);
734 safe_run(str);
735
736 sprintf(str,"%s qdisc del dev %s root 2>/dev/null",tc,wan);
737 safe_run(str);
738
739 iptables_file=fopen(iptablesfile,"w");
740 save_line(iptablespreamble);
741
742 if(qos_free_zone && *qos_free_zone!='0')
743 {
744 char *chain;
745
746 sprintf(str,"-A FORWARD -d %s -o %s -j ACCEPT", qos_free_zone, wan);
747 save_line(str);
748
749 if(qos_proxy)
750 {
751 save_line(":post_noproxy - [0:0]");
752 sprintf(str,"-A POSTROUTING ! -p tcp -o %s -j post_noproxy", lan);
753 save_line(str);
754 sprintf(str,"-A POSTROUTING ! -s %s -o %s -j post_noproxy", proxy_ip, lan);
755 save_line(str);
756 sprintf(str,"-A POSTROUTING -s %s -p tcp ! --sport %d -o %s -j post_noproxy", proxy_ip, proxy_port, lan);
757 save_line(str);
758
759 chain="post_noproxy";
760 }
761 else
762 {
763 chain="POSTROUTING";
764 }
765
766 sprintf(str,"-A %s -s %s -o %s -j ACCEPT", chain, qos_free_zone, lan);
767 save_line(str);
768 }
769
770 if(ip_count>idxtable_treshold1 && !just_flush)
771 {
772 int idxcount=0, bitmask=32-idxtable_bitmask1; /* default net mask: 255.255.255.240 */
773 char *subnet, *buf;
774 /*-----------------------------------------------------------------*/
775 printf("Detected %d addresses - indexing iptables rules to improve performance...\n",ip_count);
776 /*-----------------------------------------------------------------*/
777
778 save_line(":post_common - [0:0]");
779 save_line(":forw_common - [0:0]");
780
781 for_each(ip,ips) if(ip->addr && *(ip->addr) && !eq(ip->addr,"0.0.0.0/0"))
782 {
783 buf=index_id(ip->addr,bitmask);
784 if_exists(idx,idxs,eq(idx->id,buf))
785 {
786 idx->children++;
787 }
788 else
789 {
790 create(idx,Index);
791 idx->addr=ip->addr;
792 idx->id=buf;
793 idx->bitmask=bitmask;
794 idx->parent=NULL;
795 idx->children=0;
796 idxcount++;
797 push(idx,idxs);
798 }
799 }
800
801 /* brutal perfomance optimalization */
802 while(idxcount>idxtable_treshold2 && bitmask>2*idxtable_bitmask2)
803 {
804 bitmask-=idxtable_bitmask2;
805 idxcount=0;
806
807 for_each(idx,idxs) if(idx->parent == NULL)
808 {
809 buf=index_id(idx->addr,bitmask);
810 if_exists(metaindex,idxs,eq(metaindex->id,buf))
811 {
812 metaindex->children++;
813 }
814 else
815 {
816 create(metaindex,Index);
817 metaindex->addr=idx->addr;
818 metaindex->id=buf;
819 metaindex->bitmask=bitmask;
820 metaindex->parent=NULL;
821 metaindex->children=0;
822 idxcount++;
823 push(metaindex,idxs);
824 }
825 idx->parent=metaindex;
826 }
827 }
828
829 /* this should slightly optimize throughout ... */
830 sort(idx,idxs,desc_order_by,children);
831 sort(idx,idxs,order_by,bitmask);
832
833 i=0;
834 for_each(idx,idxs)
835 {
836 subnet=subnet_id(idx->addr,idx->bitmask);
837 printf("%d: %s/%d\n",
838 ++i, subnet, idx->bitmask);
839
840 sprintf(str,":post_%s - [0:0]", idx->id);
841 save_line(str);
842
843 sprintf(str,":forw_%s - [0:0]", idx->id);
844 save_line(str);
845
846 if(idx->parent)
847 {
848 string(buf,strlen(idx->parent->id)+6);
849 sprintf(buf,"post_%s",idx->parent->id);
850 }
851 else
852 {
853 buf="POSTROUTING";
854 }
855
856 sprintf(str,"-A %s -d %s/%d -o %s -j post_%s", buf, subnet, idx->bitmask, lan, idx->id);
857 save_line(str);
858
859 sprintf(str,"-A %s -d %s/%d -o %s -j post_common", buf, subnet, idx->bitmask, lan);
860 save_line(str);
861
862 if(idx->parent)
863 {
864 string(buf,strlen(idx->parent->id)+6);
865 sprintf(buf,"forw_%s",idx->parent->id);
866 }
867 else
868 {
869 buf="FORWARD";
870 }
871
872 sprintf(str,"-A %s -s %s/%d -o %s -j forw_%s", buf, subnet, idx->bitmask, wan, idx->id);
873 save_line(str);
874
875 sprintf(str,"-A %s -s %s/%d -o %s -j forw_common", buf, subnet, idx->bitmask, wan);
876 save_line(str);
877 }
878 printf("Total indexed iptables chains created: %d\n", i);
879
880 sprintf(str,"-A FORWARD -o %s -j forw_common", wan);
881 save_line(str);
882
883 sprintf(str,"-A POSTROUTING -o %s -j post_common", lan);
884 save_line(str);
885 }
886
887 }
888
889 if(just_flush)
890 {
891 fclose(iptables_file);
892 if(log_file)
893 {
894 fclose(log_file);
895 }
896 puts("Just flushed iptables and tc classes - now exiting ...");
897 exit(0);
898 }
899
900 if(!just_preview)
901 {
902 if(!dry_run && !nodelay && qos_free_delay)
903 {
904 printf("Flushed iptables and tc classes - now sleeping for %d seconds...\n",qos_free_delay);
905 sleep(qos_free_delay);
906 }
907
908 sprintf(str,"%s qdisc add dev %s root handle 1: htb r2q %d default 1",
909 tc,lan,htb_r2q);
910 safe_run(str);
911
912 sprintf(str, "%s class add dev %s parent 1: classid 1:2 htb rate %s ceil %s burst %dk prio %d",
913 tc,lan,lan_medium,lan_medium,burst_main,highest_priority);
914 safe_run(str);
915
916 sprintf(str, "%s class add dev %s parent 1:2 classid 1:1 htb rate %Ldkbit ceil %Ldkbit burst %dk prio %d",
917 tc,lan,line,line,burst_main,highest_priority);
918 safe_run(str);
919
920 sprintf(str,"%s qdisc add dev %s root handle 1: htb r2q %d default 1",tc,wan,htb_r2q);
921 safe_run(str);
922
923 sprintf(str, "%s class add dev %s parent 1: classid 1:2 htb rate %s ceil %s burst %dk prio %d",
924 tc,wan,wan_medium,wan_medium,burst_main,highest_priority);
925 safe_run(str);
926
927 sprintf(str, "%s class add dev %s parent 1:2 classid 1:1 htb rate %Ldkbit ceil %Ldkbit burst %dk prio %d",
928 tc,wan,up,up,burst_main,highest_priority);
929 safe_run(str);
930 }
931
932 /*-----------------------------------------------------------------*/
933 puts("Locating heavy downloaders and generating root classes ...");
934 /*-----------------------------------------------------------------*/
935 sort(ip,ips,desc_order_by,traffic);
936
937 /*-----------------------------------------------------------------*/
938 /* sub-scope - local variables */
939 {
940 long long int rate = line;
941 long long int max = line;
942 int group_count = 0;
943 FILE *credit_file = NULL;
944
945 if(!just_preview && !dry_run && enable_credit)
946 {
947 credit_file = fopen(credit,"w");
948 }
949
950 for_each(group,groups)
951 {
952 if(!just_preview)
953 {
954 //download
955 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",
956 tc, lan, parent, group->id, rate, max, burst_group, highest_priority+1, group->desired);
957 safe_run(str);
958
959 //upload
960 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",
961 tc, wan, parent, group->id, rate*up/line, max*up/line, burst_group, highest_priority+1, group->desired);
962 safe_run(str);
963 }
964
965 if(group_count++ < max_nesting)
966 {
967 parent = group->id;
968 }
969
970 rate -= digital_divide*group->min;
971 if(rate < group->min)
972 {
973 rate = group->min;
974 }
975
976 /*shaping of aggresive downloaders, with credit file support */
977 if(use_credit)
978 {
979 int group_rate = group->min, priority_sequence = lowest_priority;
980
981 for_each(ip, ips) if(ip->min == group->min && ip->max > ip->min)
982 {
983 if( ip->keyword->data_limit && !ip->fixedprio
984 && ( ip->traffic>ip->credit
985 + (ip->min*ip->keyword->data_limit+(ip->keyword->fixed_limit<<20))) )
986 {
987 if(group_rate<ip->max)
988 {
989 ip->max=group_rate;
990 }
991 group_rate+=magic_treshold;
992 ip->prio=lowest_priority;
993 if(ip->prio<highest_priority+2)
994 {
995 ip->prio=highest_priority+2;
996 }
997 }
998 else
999 {
1000 if( ip->keyword->data_prio
1001 && !ip->fixedprio
1002 && ( ip->traffic>ip->credit
1003 + (ip->min*ip->keyword->data_prio+(ip->keyword->fixed_prio<<20))) )
1004 {
1005 ip->prio=priority_sequence--;
1006 if(ip->prio<highest_priority+1)
1007 {
1008 ip->prio=highest_priority+1;
1009 }
1010 }
1011
1012 if(credit_file)
1013 {
1014 unsigned long long lcredit=0;
1015
1016 if((ip->min*ip->keyword->data_limit+(ip->keyword->fixed_limit<<20))>ip->traffic)
1017 {
1018 lcredit=(ip->min*ip->keyword->data_limit+(ip->keyword->fixed_limit<<20))-ip->traffic;
1019 }
1020 fprintf(credit_file,"%s %Lu\n",ip->addr,lcredit);
1021 }
1022 }
1023 }
1024 }
1025 }
1026 if(credit_file)
1027 {
1028 fclose(credit_file);
1029 }
1030 }
1031
1032 if(just_preview)
1033 {
1034 if(start_shaping)
1035 {
1036 printf("Reading %s and applying Fair Use Policy rules ... \n", classmap);
1037 parse(classmap)
1038 {
1039 ptr=strchr(_,' ');
1040 if(ptr)
1041 {
1042 *ptr=0;
1043 ptr++;
1044 if_exists(ip,ips,eq(ip->addr,_))
1045 {
1046 ip->mark=atoi(ptr);
1047 if(ip->max < ip->desired) /* apply FUP limit immediately.... */
1048 {
1049 printf("Applying limit for %-22s %-16s %04d ", ip->name, ip->addr, ip->mark);
1050 printf("(down: %dk-%dk ", ip->min, ip->max);
1051 sprintf(str, "%s class change dev %s parent 1:%d classid 1:%d htb rate %dkbit ceil %dkbit burst %dk prio %d",
1052 tc, lan, ip->group, ip->mark,ip->min,ip->max, burst, ip->prio);
1053 safe_run(str);
1054 printf("up: %dk-%dk)\n", (int)((ip->min/ip->keyword->asymetry_ratio)-ip->keyword->asymetry_fixed),
1055 (int)((ip->max/ip->keyword->asymetry_ratio)-ip->keyword->asymetry_fixed));
1056 sprintf(str,"%s class change dev %s parent 1:%d classid 1:%d htb rate %dkbit ceil %dkbit burst %dk prio %d",
1057 tc, wan, ip->group, ip->mark,
1058 (int)((ip->min/ip->keyword->asymetry_ratio)-ip->keyword->asymetry_fixed),
1059 (int)((ip->max/ip->keyword->asymetry_ratio)-ip->keyword->asymetry_fixed), burst, ip->prio);
1060 safe_run(str);
1061 }
1062 }
1063 }
1064 }
1065 fail
1066 {
1067 perror(classmap);
1068 puts("Warning - classmap file not fund, just generating preview ...");
1069 start_shaping=FALSE;
1070 }
1071 done; /* ugly macro end */
1072 }
1073 html=preview;
1074 json_traffic=json_preview;
1075 }
1076
1077 if(!dry_run && !just_flush)
1078 {
1079 /*-----------------------------------------------------------------*/
1080 printf("Writing json traffic overview %s ... ", json_traffic);
1081 /*-----------------------------------------------------------------*/
1082 write_json_traffic(json_traffic);
1083 }
1084
1085 /*-----------------------------------------------------------------*/
1086 printf("Writing statistics into HTML page %s ...\n", html);
1087 /*-----------------------------------------------------------------*/
1088 write_htmlandlogs(json_traffic,d,total, just_preview);
1089
1090 if(just_preview)
1091 {
1092 char swchar='p';
1093 if(start_shaping)
1094 {
1095 swchar='s';
1096 }
1097 printf("Statistics preview generated (-%c switch) - now exiting ...\n", swchar);
1098 exit(0);
1099 }
1100
1101 i=0;
1102 #ifdef DEBUG
1103 printf("%-22s %-15s mark\n","name","ip");
1104 #endif
1105
1106 printf("Writing %s ... ", classmap);
1107 f = fopen(classmap, "w");
1108 if(f < 0)
1109 {
1110 perror(classmap);
1111 }
1112
1113 /*-----------------------------------------------------------------*/
1114 puts("Generating iptables and tc classes ... ");
1115 /*-----------------------------------------------------------------*/
1116
1117 for_each(ip, ips) if(ip->mark > 0)
1118 {
1119 if(idxs)
1120 {
1121 char *buf;
1122 duplicate(ip->addr,buf);
1123 buf=index_id(ip->addr,32-idxtable_bitmask1);
1124
1125 string(chain_forward,6+strlen(buf));
1126 strcpy(chain_forward,"forw_");
1127 strcat(chain_forward,buf);
1128
1129 string(chain_postrouting,6+strlen(buf));
1130 strcpy(chain_postrouting,"post_");
1131 strcat(chain_postrouting,buf);
1132
1133 free(buf);
1134 }
1135 else
1136 {
1137 chain_forward="FORWARD";
1138 chain_postrouting="POSTROUTING";
1139 }
1140
1141 #ifdef DEBUG
1142 printf("%-22s %-16s %04d ", ip->name, ip->addr, ip->mark);
1143 #endif
1144
1145 /* -------------------------------------------------------- mark download */
1146
1147 sprintf(str, "-A %s -d %s/32 -o %s -j %s%d",
1148 chain_postrouting, ip->addr, lan, mark_iptables, ip->mark);
1149 /*sprintf(str,"-A %s -d %s/32 -o %s -j MARK --set-mark %d",chain_postrouting,ip->addr,lan,ip->mark);*/
1150 /* -m limit --limit 1/s */
1151 save_line(str);
1152
1153 if(qos_proxy)
1154 {
1155 sprintf(str, "-A %s -s %s -p tcp --sport %d -d %s/32 -o %s -j %s%d",
1156 chain_postrouting, proxy_ip, proxy_port, ip->addr, lan, mark_iptables, ip->mark);
1157 /*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);*/
1158 save_line(str);
1159 }
1160
1161 sprintf(str, "-A %s -d %s/32 -o %s -j ACCEPT",
1162 chain_postrouting, ip->addr, lan);
1163 save_line(str);
1164
1165 /* -------------------------------------------------------- mark upload */
1166 sprintf(str, "-A %s -s %s/32 -o %s -j %s%d",
1167 chain_forward, ip->addr, wan, mark_iptables, ip->mark);
1168 /* sprintf(str,"-A %s -s %s/32 -o %s -j MARK --set-mark %d",chain_forward,ip->addr,wan,ip->mark);*/
1169 save_line(str);
1170
1171 sprintf(str, "-A %s -s %s/32 -o %s -j ACCEPT",
1172 chain_forward, ip->addr, wan);
1173 save_line(str);
1174
1175 if(ip->min)
1176 {
1177 /* -------------------------------------------------------- download class */
1178 #ifdef DEBUG
1179 printf("(down: %dk-%dk ", ip->min, ip->max);
1180 #endif
1181
1182 sprintf(str, "%s class add dev %s parent 1:%d classid 1:%d htb rate %dkbit ceil %dkbit burst %dk prio %d",
1183 tc, lan, ip->group, ip->mark,ip->min,ip->max, burst, ip->prio);
1184 safe_run(str);
1185
1186 if(strcmpi(ip->keyword->leaf_discipline, "none"))
1187 {
1188 sprintf(str, "%s qdisc add dev %s parent 1:%d handle %d %s",
1189 tc, lan, ip->mark, ip->mark, ip->keyword->leaf_discipline); /*qos_leaf*/
1190 safe_run(str);
1191 }
1192
1193 if(filter_type == 1)
1194 {
1195 sprintf(str, "%s filter add dev %s parent 1:0 protocol ip handle %d fw flowid 1:%d",
1196 tc, lan, ip->mark, ip->mark);
1197 safe_run(str);
1198 }
1199
1200 /* -------------------------------------------------------- upload class */
1201 #ifdef DEBUG
1202 printf("up: %dk-%dk)\n", (int)((ip->min/ip->keyword->asymetry_ratio)-ip->keyword->asymetry_fixed),
1203 (int)((ip->max/ip->keyword->asymetry_ratio)-ip->keyword->asymetry_fixed));
1204 #endif
1205
1206 sprintf(str,"%s class add dev %s parent 1:%d classid 1:%d htb rate %dkbit ceil %dkbit burst %dk prio %d",
1207 tc, wan, ip->group, ip->mark,
1208 (int)((ip->min/ip->keyword->asymetry_ratio)-ip->keyword->asymetry_fixed),
1209 (int)((ip->max/ip->keyword->asymetry_ratio)-ip->keyword->asymetry_fixed), burst, ip->prio);
1210 safe_run(str);
1211
1212 if(strcmpi(ip->keyword->leaf_discipline, "none"))
1213 {
1214 sprintf(str, "%s qdisc add dev %s parent 1:%d handle %d %s",
1215 tc, wan, ip->mark, ip->mark, ip->keyword->leaf_discipline); /*qos_leaf*/
1216 safe_run(str);
1217 }
1218
1219 if(filter_type == 1)
1220 {
1221 sprintf(str, "%s filter add dev %s parent 1:0 protocol ip handle %d fw flowid 1:%d",
1222 tc, wan, ip->mark, ip->mark);
1223 safe_run(str);
1224 }
1225
1226 if(f > 0)
1227 {
1228 fprintf(f, "%s %d\n", ip->addr, ip->mark);
1229 }
1230 }
1231 else
1232 {
1233 #ifdef DEBUG
1234 printf("(sharing %s)\n", ip->sharing);
1235 #endif
1236 }
1237 i++;
1238 }
1239 if(f > 0)
1240 {
1241 puts("done.");
1242 fclose(f);
1243 }
1244
1245 if(idxs)
1246 {
1247 chain_forward = "forw_common";
1248 chain_postrouting = "post_common";
1249 }
1250 else
1251 {
1252 chain_forward = "FORWARD";
1253 chain_postrouting = "POSTROUTING";
1254 }
1255 /* -------------------------------- classify or reject free download */
1256 {
1257 char *final_chain = "DROP"; /* REJECT would be better, but it is impossible in mangle */
1258 if(free_min)
1259 {
1260 final_chain = "ACCEPT";
1261 }
1262 if(qos_proxy)
1263 {
1264 if(free_min)
1265 {
1266 sprintf(str,"-A %s -s %s -p tcp --sport %d -o %s -j %s%d",
1267 chain_postrouting,proxy_ip,proxy_port,lan,mark_iptables,3);
1268 save_line(str);
1269 }
1270 sprintf(str,"-A %s -s %s -p tcp --sport %d -o %s -j %s",
1271 chain_postrouting,proxy_ip,proxy_port,lan,final_chain);
1272 save_line(str);
1273 }
1274 if(free_min)
1275 {
1276 sprintf(str,"-A %s -o %s -j %s%d", chain_postrouting, lan, mark_iptables, 3);
1277 save_line(str);
1278 }
1279 sprintf(str,"-A %s -o %s -j %s", chain_postrouting, lan, final_chain);
1280 save_line(str);
1281 /* ------------------------------- classify or reject free upload */
1282 if(free_min)
1283 {
1284 sprintf(str,"-A %s -o %s -j %s%d", chain_forward, wan, mark_iptables, 3);
1285 save_line(str);
1286 }
1287 sprintf(str,"-A %s -o %s -j %s", chain_forward, wan, final_chain);
1288 save_line(str);
1289 }
1290
1291 if(free_min) /* allocate free bandwith if it is not zero... */
1292 {
1293 /*-----------------------------------------------------------------*/
1294 puts("Generating free bandwith classes ...");
1295 /*-----------------------------------------------------------------*/
1296 sprintf(str, "%s class add dev %s parent 1:%d classid 1:3 htb rate %dkbit ceil %dkbit burst %dk prio %d",
1297 tc, lan, parent, free_min, free_max,burst, lowest_priority);
1298 safe_run(str);
1299 sprintf(str, "%s class add dev %s parent 1:%d classid 1:3 htb rate %dkbit ceil %dkbit burst %dk prio %d",
1300 tc, wan, parent, free_min, free_max, burst, lowest_priority);
1301 safe_run(str);
1302 /* tc SFQ */
1303 if(strcmpi(qos_leaf, "none"))
1304 {
1305 sprintf(str,"%s qdisc add dev %s parent 1:3 handle 3 %s", tc, lan, qos_leaf);
1306 safe_run(str);
1307
1308 sprintf(str,"%s qdisc add dev %s parent 1:3 handle 3 %s", tc, wan, qos_leaf);
1309 safe_run(str);
1310 }
1311 /* tc handle 1 fw flowid */
1312 sprintf(str,"%s filter add dev %s parent 1:0 protocol ip handle 3 fw flowid 1:3", tc, lan);
1313 safe_run(str);
1314
1315 sprintf(str,"%s filter add dev %s parent 1:0 protocol ip handle 3 fw flowid 1:3", tc, wan);
1316 safe_run(str);
1317 }
1318 printf("Total IP count: %d\n", i);
1319 run_restore();
1320 if(log_file)
1321 {
1322 fclose(log_file);
1323 }
1324 return 0;
1325 /* that's all folks, thank you for reading it all the way up to this point ;-) */
1326 /* bad luck C<<1 is not yet finished, I promise no sprintf() next time... */
1327 }
This page took 1.031194 seconds and 5 git commands to generate.