Ticket #17: sockstat.pl

File sockstat.pl, 6.4 KB (added by mjhsieh@…, 22 years ago)

modified version of sockstat

Line 
1#!/usr/bin/perl -w
2#-
3# Copyright (c) 1999 Dag-Erling Coïdan Smørgrav
4# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9# 1. Redistributions of source code must retain the above copyright
10#    notice, this list of conditions and the following disclaimer
11#    in this position and unchanged.
12# 2. Redistributions in binary form must reproduce the above copyright
13#    notice, this list of conditions and the following disclaimer in the
14#    documentation and/or other materials provided with the distribution.
15# 3. The name of the author may not be used to endorse or promote products
16#    derived from this software without specific prior written permission.
17#
18# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28#
29# $FreeBSD: src/usr.bin/sockstat/sockstat.pl,v 1.16 2002/04/22 13:44:39 des Exp $
30#
31
32use strict;
33use Getopt::Std;
34
35my %netstat;
36my %fstat;
37my $unknown = [ "?", "?", "?", "?", "?", "?", "?", "?", "?" ];
38
39my $inet_fmt = "%-8.8s %-8.8s %5.5s %4.4s %-6.6s %-21.21s %-21.21s\n";
40my $unix_fmt = "%-8.8s %-8.8s %5.5s %4.4s %-6.6s %-43.43s\n";
41
42my @ranges;
43
44#
45# Parse a port range specification
46#
47sub parse_port_ranges($) {
48    my $spec = shift;           # Range spec
49
50    my $range;                  # Range
51    my ($low, $high);           # Low/high ends of range
52
53    foreach $range (split(/\s*,\s*/, $spec)) {
54        if ($range =~ m/^(\d+)-(\d+)$/) {
55            ($low, $high) = ($1, $2);
56        } elsif ($range =~ m/^-(\d+)$/) {
57            ($low, $high) = (1, $1);
58        } elsif ($range =~ m/^(\d+)-$/) {
59            ($low, $high) = ($1, 65535);
60        } elsif ($range =~ m/^(\d+)$/) {
61            $low = $high = $1;
62        } else {
63            die("invalid range specification: $range\n");
64        }
65        if ($low < 0 || $low > 65535 || $high < 0 || $high > 65535) {
66            die("valid ports numbers are 1-65535 inclusive\n");
67        }
68        if ($low > $high) {
69            $low ^= $high;
70            $high ^= $low;
71            $low ^= $high;
72        }
73        push(@ranges, [ $low, $high ]);
74    }
75}
76
77#
78# Check if a port is in an allowed range
79#
80sub check_range($) {
81    my $addr = shift;           # Address to check
82
83    my $port;                   # Port number
84    my $range;                  # Range
85   
86    if (@ranges == 0) {
87        return 1;
88    }
89
90    if ($addr !~ m/\.(\d+)$/) {
91        return undef;
92    }
93    $port = $1;
94
95    foreach $range (@ranges) {
96        if ($port >= $range->[0] && $port <= $range->[1]) {
97            return 1;
98        }
99    }
100    return undef;
101}
102
103#
104# Gather information about sockets
105#
106sub gather() {
107   
108    local *PIPE;                # Pipe
109    my $pid;                    # Child PID
110    my $line;                   # Input line
111    my @fields;                 # Fields
112
113    # Netstat
114    if (!defined($pid = open(PIPE, "-|"))) {
115        die("open(netstat): $!\n");
116    } elsif ($pid == 0) {
117        exec("/usr/sbin/netstat", "-Aan");
118        die("exec(netstat): $!\n");
119    }
120    while ($line = <PIPE>) {
121        next unless ($line =~ m/^ [0-9a-f]{7} /) || ($line =~ m/^[0-9a-f]{16} /);
122        chomp($line);
123        @fields = split(' ', $line);
124        $netstat{$fields[0]} = [ @fields ];
125    }
126    close(PIPE)
127        or die("close(netstat): $!\n");
128
129    # Fstat
130    if (!defined($pid = open(PIPE, "-|"))) {
131        die("open(fstat): $!\n");
132    } elsif ($pid == 0) {
133        exec("/usr/bin/fstat");
134        die("exec(fstat): $!\n");
135    }
136    while ($line = <PIPE>) {
137        chomp($line);
138        @fields = split(' ', $line);
139        next if ($fields[4] eq "-");
140        push(@{$fstat{$fields[4]}}, [ @fields ]);
141    }
142    close(PIPE)
143        or die("close(fstat): $!\n");
144}
145
146#
147# Replace the last dot in an "address.port" string with a colon
148#
149sub addr($) {
150    my $addr = shift;           # Address
151
152    $addr =~ s/^(.*)\.([^\.]*)$/$1:$2/;
153    return $addr;
154}
155
156#
157# Print information about Internet sockets
158#
159sub print_inet($$$) {
160    my $af = shift;             # Address family
161    my $conn = shift || 0;      # Show connected sockets
162    my $listen = shift || 0;    # Show listen sockets
163
164    my $fsd;                    # Fstat data
165    my $nsd;                    # Netstat data
166
167    printf($inet_fmt, "USER", "COMMAND", "PID", "FD",
168           "PROTO", "LOCAL ADDRESS", "FOREIGN ADDRESS");
169    foreach $fsd (@{$fstat{$af}}) {
170        next unless defined($fsd->[7]);
171        $nsd = $netstat{$fsd->[7]} || $unknown;
172        next unless (check_range($nsd->[4]) || check_range($nsd->[5]));
173        next if (!$conn && $nsd->[5] ne '*.*');
174        next if (!$listen && $nsd->[5] eq '*.*');
175        printf($inet_fmt, $fsd->[0], $fsd->[1], $fsd->[2],
176               substr($fsd->[3], 0, -1),
177               $nsd->[1], addr($nsd->[4]), addr($nsd->[5]));
178    }
179    print("\n");
180}
181
182#
183# Print information about Unix domain sockets
184#
185sub print_unix($$) {
186    my $conn = shift || 0;      # Show connected sockets
187    my $listen = shift || 0;    # Show listen sockets
188
189    my %endpoint;               # Mad PCB to process/fd
190    my $fsd;                    # Fstat data
191    my $nsd;                    # Netstat data
192
193    foreach $fsd (@{$fstat{"unix"}}) {
194        $endpoint{$fsd->[6]} = "$fsd->[1]\[$fsd->[2]\]:" .
195            substr($fsd->[3], 0, -1);
196    }
197    printf($unix_fmt, "USER", "COMMAND", "PID", "FD", "PROTO", "ADDRESS");
198    foreach $fsd (@{$fstat{"unix"}}) {
199        next unless defined($fsd->[6]);
200        next if (!$conn && defined($fsd->[8]));
201        next if (!$listen && !defined($fsd->[8]));
202        $nsd = $netstat{$fsd->[6]} || $unknown;
203        printf($unix_fmt, $fsd->[0], $fsd->[1], $fsd->[2],
204               substr($fsd->[3], 0, -1), $fsd->[5],
205               $nsd->[8] || ($fsd->[8] ? $endpoint{$fsd->[8]} : "(none)"));
206    }
207    print("\n");
208}
209
210#
211# Print usage message and exit
212#
213sub usage() {
214    print(STDERR "usage: sockstat [-46clu] [-p ports]\n");
215    exit(1);
216}
217
218MAIN:{
219    my %opts;                   # Command-line options
220
221    getopts("46clp:u", \%opts)
222        or usage();
223
224    gather();
225
226    if (!$opts{'4'} && !$opts{'6'} && !$opts{'u'}) {
227        $opts{'4'} = $opts{'6'} = $opts{'u'} = 1;
228    }
229    if (!$opts{'c'} && !$opts{'l'}) {
230        $opts{'c'} = $opts{'l'} = 1;
231    }
232    if ($opts{'p'}) {
233        parse_port_ranges($opts{'p'});
234    }
235    if ($opts{'4'}) {
236        print_inet("internet", $opts{'c'}, $opts{'l'});
237    }
238    if ($opts{'6'}) {
239        print_inet("internet6", $opts{'c'}, $opts{'l'});
240    }
241    if ($opts{'u'}) {
242        print_unix($opts{'c'}, $opts{'l'});
243    }
244   
245    exit(0);
246}