1#
2# sockd-stat.awk for the DANTE socks 4/5 daemon
3# Copyleft 2001 Stephan Eisvogel <eisvogel@hawo.stw.uni-erlangen.de>
4# This code is licensed under GPL v2
5#
6# If you use this regularly, help boost my ego ;) and do a:
7# uname -a | mail -s "I use sockd-stat!" eisvogel@hawo.stw.uni-erlangen.de
8#
9# If you make any fixes or improvements, please do send do them to me!
10# All testing was done with DANTE version 1.1.6 on RedHat(tm) Linux.
11# If there are updates you can probably grab them from:
12# http://www.hawo.stw.uni-erlangen.de/~eisvogel/sockd
13#
14#
15# HOW TO USE
16#
17# Find out how your system rotates its logs and run this script
18# post-rotating, e.g. by doing a
19#
20#    zcat /var/log/sockd.1.gz | awk -f /root/bin/sockd-stat.awk | \
21#    mail -s "Weekly SOCKD usage" root
22#
23# If you have a directory called /etc/logrotate.d then take a look
24# at the files in there for some run-post-rotate examples. Note that
25# this script uses some basic GNU-tools to get things done, namely
26# "sort", "head", and "rm", if you want reverse IP lookups you need
27# the "nslookup" utility as well.
28#
29# This is my last big AWK script, I am going Perl.
30#
31#
32# RELEASE NOTES
33#
34# v1.0 @ 01.01.2001
35#   NEC's socks daemon keeps crashing on Linux with 200+ users no
36#   matter what I do, so we switched to DANTE for the time being.
37#   Didn't like the last remote exploit too much either, maybe NEC
38#   needs more DJB-type developers?
39# v1.1 @ 02.01.2001
40#   + fixed issue with different syslog daemons (Michael Shuldman)
41#   + added request count output for the port statistics
42#   + blocked UDP requests were not counted
43#   + changed regexp that matches lines with statistics
44#   + rmfile() function
45#   + code cleanups, renamed some variables for clarity
46#   + beautified output IMO
47#   + added request types to general statistics
48
49BEGIN {
50	#
51	# Configurable items
52	# (0 means all)
53	#
54	SHOW_PORTS=30;
55	SHOW_CLIENTS=0;
56	SHOW_DESTINATIONS=50;
57	if (nodns == 1)
58		LOOKUP_IPS=0;
59	else
60		LOOKUP_IPS=1;
61
62	#
63	# no need to change anything below
64	#
65	IGNORECASE=1;
66	MEG=1024*1024;
67	lastdns="(unknown)";
68
69	lines=0;
70	passed=0;
71	denied=0;
72
73	total_to_client_bytes=0;
74	total_from_client_bytes=0;
75	total_to_target_bytes=0;
76	total_from_target_bytes=0;
77}
78
79#
80# Sort 'filename' contents numerically in 'field'
81# and display the 'count' largest lines, optionally
82# treat first field as IP address and append it
83# resolved into a hostname to the end of the line
84#
85function sorted_output (filename, field, count, rev_dns,	curhost,a,b)
86{
87	# sort and stuff all lines it into a temp file
88	if (count==0)
89	system("sort -nr +"field" "filename"  >"filename".1");
90	else system("sort -nr +"field" "filename" | head -n "count" >"filename".1");
91
92	# read temp file back in, print it out and rev-dns if needed
93	b=filename".1";
94	while ((getline curhost < b)>0) {
95		printf "%s",curhost;
96		if (rev_dns==1) {
97			split(curhost,a);
98			dns_lookup(a[1],filename".2");
99			printf "%s",lastdns;
100		}
101		printf "\n";
102	}
103	close(b);
104	rmfile(b);
105}
106
107#
108# Wade through /etc/services and grab the descriptive service name of a port
109#
110function get_service (port,	myline,a,s)
111{
112	split(port,s,/\//);
113	curr_service=s[1];
114	while ((getline myline < "/etc/services")>0) {
115		split(myline,a);
116		if (a[2]==port) {
117			curr_service=a[1];
118			break;
119		}
120	}
121	close ("/etc/services");
122}
123
124#
125# Somewhere we just have to draw a line
126#
127function draw_line (n,	i)
128{
129	for (i=0; i<n; i++) printf "-";
130	printf "\n";
131}
132
133#
134# Simple DNS lookup using the nslookup command
135#
136function dns_lookup (ip,tmpfile, name,host,a)
137{
138	lastdns=ip; # unsuccessful lookup -> show IP
139	system("nslookup "ip" 2>/dev/null >"tmpfile);
140	while ((getline name < tmpfile)>0) {
141		split(name,host);
142		if (index(host[1],"Name:")!=0) {
143			lastdns=host[2];
144			break;
145		}
146	}
147	close(tmpfile)
148	rmfile(tmpfile);
149}
150
151#
152# Remove file sans output
153#
154function rmfile (filename)
155{
156	system("rm "filename" 2>/dev/null >/dev/null");
157}
158
159#
160# Matches all lines, count them
161#
162/.*/ {
163	lines++;
164}
165
166#
167# Count blocked requests
168#
169/ sockd\[[0-9]+\]: block\([0-9]+\): / {
170	denied++;
171	#
172	# compensate for different syslog daemons
173	#
174	field_ofs=0;
175	split($0,curr_line);
176	for (i=1; i<=NF; i++)
177		if (match(curr_line[i],/sockd\[[0-9]+\]:/)) {
178			field_ofs = i + 1;
179			break;
180		}
181	#
182	# count BLOCK item
183	#
184	m_block[curr_line[field_ofs+2]] += 1;
185	next;
186}
187
188#
189# Matches lines with statistical information
190#
191/ sockd\[[0-9]+\]: pass\([0-9]+\): .* \]: .* -> .* -> .* -> .* -> / {
192
193	passed++;
194
195	#
196	# compensate for different syslog daemons
197	#
198	field_ofs=0;
199	split($0,curr_line);
200	for (i=1; i<=NF; i++)
201		if (match(curr_line[i],/sockd\[[0-9]+\]:/)) {
202			field_ofs = i + 1;
203			break;
204		}
205	#
206	# count PASS item
207	#
208	m_pass[curr_line[field_ofs+2]] += 1;
209
210	#
211	# clean up fields
212	#
213	gsub(/,/,"",curr_line[field_ofs+9]);
214	gsub(/:/,"",curr_line[field_ofs+15]);
215	split(curr_line[field_ofs+6],a,/\./);
216	c_ip=a[1]"."a[2]"."a[3]"."a[4];
217	if (match(curr_line[field_ofs+12],/^`world'$/))
218		curr_line[field_ofs+12]="0.0.0.0."a[5];
219	split(curr_line[field_ofs+12],a,/\./);
220	t_ip=a[1]"."a[2]"."a[3]"."a[4];
221	t_port=a[5];
222
223	#
224	# add to totals
225	#
226	total_to_client_bytes += curr_line[field_ofs+4];
227	total_from_client_bytes += curr_line[field_ofs+9];
228	total_to_target_bytes += curr_line[field_ofs+10];
229	total_from_target_bytes += curr_line[field_ofs+15];
230
231	#
232	# update client
233	# (but exclude broken socksified Half-Life requests)
234	#
235	if (match(c_ip,/0\.0\.0\.0/)==0) {
236		client[c_ip]=1;
237		to_client[c_ip] += curr_line[field_ofs+4];
238		from_client[c_ip] += curr_line[field_ofs+9];
239	}
240
241	#
242	# update target
243	#
244	target[t_ip]=1;
245	to_target[t_ip] += curr_line[field_ofs+10];
246	from_target[t_ip] += curr_line[field_ofs+15];
247
248	#
249	# update port
250	#
251	if (match(curr_line[field_ofs+2],/tcp/)) {
252		tcp_port[t_port]=1;
253		tcp_port_to[t_port] += curr_line[field_ofs+10];
254		tcp_port_from[t_port] += curr_line[field_ofs+15];
255		tcp_port_used[t_port] += 1;
256	} else {
257		udp_port[t_port]=1;
258		udp_port_to[t_port] += curr_line[field_ofs+10];
259		udp_port_from[t_port] += curr_line[field_ofs+15];
260		udp_port_used[t_port] += 1;
261	}
262}
263
264
265#
266# After all lines are done, sort and print the statistics
267#
268END {
269	# create a random filename for sorting
270	srand();
271	CONVFMT="%d"
272	r=rand()*999999999;
273	tmpfile="/tmp/tmp.sockd-stat."r;
274
275	printf "SOCKD statistics version 1.1\n";
276	printf "Copyleft 2001 Stephan Eisvogel <eisvogel@hawo.stw.uni-erlangen.de>\n";
277
278	total_clients=0;
279	total_targets=0;
280	for (c in client) total_clients++;
281	for (t in target) total_targets++;
282
283	printf "\nGeneral statistics\n"; draw_line(41);
284	printf "Lines parsed               : %12d\n", lines;
285	printf "Unique clients             : %12d\n", total_clients;
286	printf "Unique targets             : %12d\n", total_targets;
287	printf "Passed requests            : %12d\n", passed;
288
289	for (m in m_pass) printf "    %-22s : %12d\n", m, m_pass[m] >> tmpfile
290	close(tmpfile);
291	sorted_output(tmpfile,2,0,0);
292	rmfile(tmpfile);
293
294	printf "Blocked requests           : %12d\n", denied;
295	for (m in m_block) printf "    %-22s : %12d\n", m, m_block[m] >> tmpfile
296	close(tmpfile);
297	sorted_output(tmpfile,2,0,0);
298	rmfile(tmpfile);
299	draw_line(41);
300
301	printf "\nVolume totals\n"; draw_line(28);
302	printf "Clients <--    %10.1f MB\n",total_to_client_bytes/MEG;
303	printf "Clients -->    %10.1f MB\n",total_from_client_bytes/MEG;
304	printf "Targets <--    %10.1f MB\n",total_to_target_bytes/MEG;
305	printf "Targets -->    %10.1f MB\n",total_from_target_bytes/MEG;
306	draw_line(28);
307
308	#
309	# TCP/UDP port stats
310	#
311	printf "\nPort   Service                      ";
312	printf "To         From        Total     Requests\n";
313	draw_line(77);
314	for (p in tcp_port) {
315		printf "%-6s ",p >> tmpfile;
316		get_service(p"/tcp");
317		outline="("curr_service"/tcp)";
318		printf "%-23s",outline >>tmpfile;
319		printf "%8.1f MB  %8.1f MB  %8.1f MB  %8d\n",
320			tcp_port_to[p]/MEG,
321			tcp_port_from[p]/MEG,
322			(tcp_port_from[p]+tcp_port_to[p])/MEG,
323			tcp_port_used[p] >> tmpfile;
324	}
325	for (p in udp_port) {
326		printf "%-6s ",p >> tmpfile;
327		get_service(p"/udp");
328		outline="("curr_service"/udp)";
329		printf "%-23s",outline >>tmpfile;
330		printf "%8.1f MB  %8.1f MB  %8.1f MB  %8d\n",
331			udp_port_to[p]/MEG,
332			udp_port_from[p]/MEG,
333			(udp_port_from[p]+udp_port_to[p])/MEG,
334			udp_port_used[p] >> tmpfile;
335	}
336	close(tmpfile);
337	sorted_output(tmpfile,6,SHOW_PORTS,0);
338	rmfile(tmpfile);
339	draw_line(77);
340
341	#
342	# Client stats
343	#
344	print "\nClient IP              To         From        Total     FQDN";
345	draw_line(77);
346	for (c in client) {
347		printf "%-16s %8.1f MB  %8.1f MB  %8.1f MB  \n",
348			c,
349			to_client[c]/MEG,
350			from_client[c]/MEG,
351			(from_client[c]+to_client[c])/MEG >> tmpfile;
352	}
353	close(tmpfile);
354	sorted_output(tmpfile,5,SHOW_CLIENTS,LOOKUP_IPS);
355	rmfile(tmpfile);
356	draw_line(77);
357
358	#
359	# Destination stats
360	#
361	print "\nDestination IP         To         From        Total     FQDN";
362	draw_line(77);
363	for (t in target) {
364		printf "%-16s %8.1f MB  %8.1f MB  %8.1f MB  \n",
365			t,
366			to_target[t]/MEG,
367			from_target[t]/MEG,
368			(from_target[t]+to_target[t])/MEG >> tmpfile;
369	}
370	close(tmpfile);
371	sorted_output(tmpfile,5,SHOW_DESTINATIONS,LOOKUP_IPS);
372	rmfile(tmpfile);
373	draw_line(77);
374	printf "\n";
375}
376