| 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 | |
|---|
| 32 | use strict; |
|---|
| 33 | use Getopt::Std; |
|---|
| 34 | |
|---|
| 35 | my %netstat; |
|---|
| 36 | my %fstat; |
|---|
| 37 | my $unknown = [ "?", "?", "?", "?", "?", "?", "?", "?", "?" ]; |
|---|
| 38 | |
|---|
| 39 | my $inet_fmt = "%-8.8s %-8.8s %5.5s %4.4s %-6.6s %-21.21s %-21.21s\n"; |
|---|
| 40 | my $unix_fmt = "%-8.8s %-8.8s %5.5s %4.4s %-6.6s %-43.43s\n"; |
|---|
| 41 | |
|---|
| 42 | my @ranges; |
|---|
| 43 | |
|---|
| 44 | # |
|---|
| 45 | # Parse a port range specification |
|---|
| 46 | # |
|---|
| 47 | sub 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 | # |
|---|
| 80 | sub 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 | # |
|---|
| 106 | sub 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 | # |
|---|
| 149 | sub 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 | # |
|---|
| 159 | sub 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 | # |
|---|
| 185 | sub 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 | # |
|---|
| 213 | sub usage() { |
|---|
| 214 | print(STDERR "usage: sockstat [-46clu] [-p ports]\n"); |
|---|
| 215 | exit(1); |
|---|
| 216 | } |
|---|
| 217 | |
|---|
| 218 | MAIN:{ |
|---|
| 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 | } |
|---|