#!/usr/bin/gawk -f # BEGIN { sf4sf_version = "2010-01-02a" } # # sf4sf is a Syslog Filter For SlackFire and other linux iptables based # firewall filters. It uses the iptables LOG option. # # Actually, this was `fwfilter' written by Arno van Amersfoort # (http://rocky.eld.leidenuniv.nl/) slightly modified for use # with SlackFire. (fwfilter no longer available as at 2008-06-06) # # Rewrite (no longer 'slightly modified' ;) 2008-06-01, last edit # Copyright (C) 2008-2010 Grant Coady GPLv2 # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public License # as published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details: # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html # # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- # Possible usage: # ```````````````` # tail -f /var/log/syslog | sf4sf # grep Fired /var/log/syslog | sf4sf # grep Trojan /var/log/syslog | sf4sf # ... # # Note: if you want to both pipe in data and read data file/s, you # must place a '-' on the command line for the piped data to indicate # when it will be read, for example: # zcat filename.gz | sf4sf [options] - /var/log/file (| before file) # zcat filename.gz | sf4sf [options] /var/log/file - (file before |) # # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- # Documentation # `````````````` # This source file has it all... ;) # # Name resolution # ```````````````` # Host name resolution with smarts to trim localnet domain names, discover # machine name at runtime, or to use supplied nicknames for known machines. # # Country code and/or name lookup # ```````````````````````````````` # This provides a country code and name lookup (IP geolocation), for example: # 04:38:34 JLE:inp:drop deny_rpt TCP 3895 -> 135 (msrpc) TTL=113 SYN ppp0<- # 70.84.0.123.cc9.ne.jp (JP:Japan) -> deltree # # Or like this when name lookup fails: # 08:59:18 JLE:inp:drop msft_rpt TCP 6000 -> 135 (msrpc) TTL=101 SYN ppp0<- # 219.133.104.247 (CN:China) -> deltree # # You can turn off country names for commonly seen countries, this feature # is also turned off for localnet and nominated machines. # # Output formatting # `````````````````` # o Colours! You may specify your own ansi colour scheme. # # o Single long line or multiline output # # o Report macro expansion for custom output formatting # # Custom settings file # ````````````````````` # Save your custom settings to a file, specify the custom settings file # like this example: # tail -f /var/log/syslog | sf4sf -f /etc/sf4sf.conf [- more options] # # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- # Related # ```````` # o sf4sf-extract-settings -- extract user settings from this program to a # custom settings file. # # o ip2c-database -- delivers country code and name for IPs (geolocation), # for information see: http://bugs.id.au/firewall/ or visit # ftp://bugs.id.au/junkview/ to get latest ip2c-database tarball. # Extract tarball to default location: /usr/local/share/junkview, or to # a directory of your choice and adjust 'ip2c_data_dir' below to suit. # # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- # If you change some setting here and it does not take effect, check if you # are using the '-f /etc/sf4sf.conf' option. If so, edit that custom # settings file instead of making changes here. # BEGIN { # User settings # `````````````` # You may use a .conf file, perhaps /etc/sf4sf.conf, to hold your # settings and thus allow the install of program updates without # losing your settings, see sf4sf-extract-settings script. # Do not alter anything to the left of an '=' sign, bad things # will happen! # Log prefix (--log-prefix) # `````````````````````````` # Set 'log_prefix' to match your iptables' --log-prefix value # Example: log_prefix = "Fired" # default for SlackFire! log_prefix = "Fired" # Set this to '1' to show log messages not from iptables showskipped = 0 # Name resolution # ```````````````` # Enable this option if you want to resolve IP addresses to names, # set to '0' to disable name resolution resolve = 1 # Control how to resolve this machine's name. Examples: # machine = "1.2.3.3" # no name for 1.2.3.3, show addr # machine = "1.2.3.1:far" # show 'far' for machine 1.2.3.1 # machine = "auto:far" # discover IP and use supplied name # machine = "auto:!" # discover IP and use logfile name # machine = "1.2.3.4:!" # specify IP and use logfile name machine = "auto:!" # List local machine IPs and their names. # Examples: # machine_name["1.1.1.1"] = "-" # no name, show addr # machine_name["1.2.3.1"] = "!" # display name from logfile # machine_name["1.2.3.4"] = "boo" # display name = 'boo' # machine_name["1.2.3.1"] = "far" # display name = 'far' # Suppress localnet domain, when set this option stops the display # of the localnet domain as reported by 'host ' # Examples: # localnet_domain_trim = "auto" # discover domain # localnet_domain_trim = ".example.com" # specify domain localnet_domain_trim = "auto" # Country code and/or name lookup # ```````````````````````````````` # Load the IP to country codes database to provide geolocation # information. Requires ip2c-database downloaded from the home # site: http://bugs.id.au/firewall/ # Location of the extracted database files' directory, for example: # ip2c_data_dir = "/usr/local/share/junkview" ip2c_data_dir = "/usr/local/share/junkview" # If you are using the ip2cn-server, fill in the next value, sf4sf # will test if the ip2cn-server is listening on the nominated port # and fall back to loading the database files set above. Comment # out this value if you are not sharing the database with another # program. # Example: ip2cn-peerport = 4743 ip2cn_peerport = 4743 # Select display of country-code, country-name, or enable both, # giving, for example: (AU), (Australia) or (AU:Australia) country_code = 1 country_name = 1 # Suppress 'country_code' or 'country_name' for defined localnet/s, # separate multiple entries with a comma. # Example: localnet = "192.168.1.0/24,192.168.2.0/24" localnet = "192.168.3.0/24" # Suppress country code/name display for common countries you see, # comma separated uppercase list. Country code/name is not hidden if # there is no resolved name to display. # Example: 'hide_common_cc = "US"' hide_common_cc = "AU,US" # Output formatting # `````````````````` # Set this to '1' will hide the date, '0' will display date and time hide_date = 1 # Enable this if you want the information to be split and shown on # multiple lines to increase readability -- recommended multiline = 1 # Enable this option to use ANSI colors to increase readability, # may be disabled by command line option 'ansicolor=0' for piping # through 'less', for example. ansicolor = 1 # Extended colour control is possible by building a list of items, # semi-colon separated, for example: "30;47" gives black on white # background, "1;32;40" gives bright green on black. # # Attributes Foreground Colors Background Colors # 0 Reset all attr 30 Black 40 Black # 1 Bright 31 Red 41 Red # 2 Dim 32 Green 42 Green # 7 Reverse 33 Yellow 43 Yellow # 34 Blue 44 Blue # 35 Magenta 45 Magenta # 36 Cyan 46 Cyan # 37 White 47 White # # use the 'ansicolours' script to see how these colours appear on # your terminal # ansi foreground colours red = 31 # don't change this group green = 32 yellow = 33 blue = 34 magenta = 35 cyan = 36 white = 37 normal = "X" # Set colours here: choose from 'ansi foreground colours' table; # or use quoted sequences from the extended colour control table. color_bright = 0 # 1 or 0: bright or dim colour color_repeat = cyan # 'message repeated' color_datetime = normal # log record datetime color_prefix = normal # --log-prefix color_prefix_fire = yellow # --log-prefix match (fire) color_arrow = normal # '->' color_src = red # SRC= color_src_fire = magenta # SRC= (fire) color_dst = green # DST= color_dst_fire = cyan # DST= (fire) color_in = red # IN= color_out = green # OUT= color_local_src = green # localnet src name color_local_dst = green # localnet dst name color_name_src = normal # SRC= resolved name color_name_dst = normal # DST= resolved name color_udp = "1;36" # PROTO=UDP color_tcp = "1;35" # PROTO=TCP color_icmp = red # PROTO=ICMP color_icmp_fire = "1;31" # PROTO=ICMP (fire) color_tcp_flags = normal # TCP flags color_tcp_flags_fire = yellow # TCP flags (fire) color_ttl = normal # TTL=nnn color_ttl_fire = yellow # TTL=nnn (fire) color_icmp_info = cyan # ICMP [...] info color_icmp_type = red # ICMP X.X color_spt = magenta # SPT= color_spt_low = "1;31" # SPT=0..1023 color_spt_fire = "1;35" # SPT= (fire) color_dpt = cyan # DPT= color_dpt_fire = "1;36" # DPT= (fire) # Set max line length for dst: src: before it gets # split to two lines, names are not split. maxlin = 95 # Custom output formatting using tokens, each token represents a # chunk of the firewall log record after the date/time and prefix: # # token expansion # ------ ---------------------------------------------- # addr dotquad_addr -> dotquad_addr # port port -> port (name) # iface ppp0<-, ppp0->, eth1->ppp0, etc # name resolved name or dotquad addr on resolve fail # line? -> (includes optional # country code:name info). # namprt : -> : # line newline if multiline, else space # proto TCP, UDP, ICMP or IGMP # ttl TTL=nnn # tcp SYN ACK and friends (TCP flags) # icmp ICMP information, two styles depending on # record format, must be end of line for icmp2 # as this style has conditional line (line?): # 'line? info line []' # # tab:nn space to column 'nn', see macro_tab_newline # # Tokens are whitespace separated and errors shown as !text! # # Examples: # ICMP report format: you probably don't want to change these, there # are two formats due to some ICMP messages having additional info # about the TCP packet they refer to, within [...] at record end. # macro_icmp1 = "tab:32 proto icmp ttl iface line name" # macro_icmp2 = "tab:32 addr ttl iface line name line proto icmp" macro_icmp1 = "tab:32 proto icmp iface ttl line name" macro_icmp2 = "tab:32 addr iface ttl line name proto icmp" # TCP|UDP may have different report formats # macro_tcp = "tab:32 proto port iface ttl tcp line name" # macro_udp = "tab:32 proto port iface ttl line name" macro_tcp = "tab:32 proto port tab:64 iface ttl tcp line name" macro_udp = "tab:32 proto port tab:64 iface ttl line name" # IGMP has fewer fields to report # macro_igmp = "tab:32 proto tab:60 iface ttl line name" macro_igmp = "tab:32 proto tab:64 iface ttl line name" # Control tab expansion, set '1' to insert a newline if current line # column is beyond the requested tab stop. Set '0' to get a single # space when requested tab stop is before current column position. macro_tab_newline = 0 # Max wait settings # `````````````````` # Set max wait time for name resolution in seconds, default is 5 sec. host_max_secs = 4 # special format of the 'end of user data' line marks end of what is # read by the sf4sf-extract-settings script, so don't change it! #XXXXXXXXXXXXXXXXXXXXXXXXXXXXX end of user data XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX # Debug options # `````````````` # debug options: use 'debug=""' on the command line where # contains one or more of these options: # h host: # p port: # r # # Example: '... sf4sf ... debug="h"' show host name resolution results # # Also you may use command line option 'showskipped=1' to display any # records normally ignored by sf4sf. # # Command line option 'nohost=1' disables host name resolution. # announce self to world printf "\n## Syslog Filter for SlackFire! -- sf4sf version: %s.\n", sf4sf_version # magic numbers traceport_str = 33434 # traceroute port start traceport_end = 33523 # traceroute port end (start + 30 * 3 - 1) # where to get port names information service1 = "/usr/share/nmap/nmap-services" # first try nmap's file service2 = "/etc/services" # fall back to system services file # ICMP info from RFC 792, abbreviated (Grant, 2008-06-03) # sourced from: http://www.networksorcery.com/enp/protocol/icmp.htm icmp_data[0,0] = "Echo reply" icmp_data[3,0] = "Net unreachable" icmp_data[3,1] = "Host unreachable" icmp_data[3,2] = "Protocol unreachable" icmp_data[3,3] = "Port unreachable" icmp_data[3,4] = "Fragmentation needed, DF set" icmp_data[3,5] = "Source route failed" icmp_data[3,6] = "Destination network unknown" icmp_data[3,7] = "Destination host unknown" icmp_data[3,8] = "Source host isolated Obsolete!" icmp_data[3,9] = "Dest network admin prohibited" icmp_data[3,10] = "Dest host admin prohibited" icmp_data[3,11] = "Network unreachable TOS" icmp_data[3,12] = "Host unreachable TOS" icmp_data[3,13] = "Communication admin prohibited" icmp_data[3,14] = "Host precedence violation" icmp_data[3,15] = "Precedence cutoff in effect" icmp_data[4,0] = "Source quench" icmp_data[5,0] = "Redirect for network" icmp_data[5,1] = "Redirect for host" icmp_data[5,2] = "Redirect for TOS & network" icmp_data[5,3] = "Redirect for TOS & host" icmp_data[6,0] = "Alternate Host Address" icmp_data[8,0] = "Ping!" icmp_data[11,0] = "Time to live (TTL) zero" icmp_data[11,1] = "Fragment reassembly timeout" icmp_data[12,0] = "Parameter problem" icmp_data[13,0] = "Timestamp request" icmp_data[14,0] = "Timestamp reply" icmp_data[15,0] = "Information request" icmp_data[16,0] = "Information reply" # protocol numbers we may care about protonum[1] = "ICMP" protonum[2] = "IGMP" protonum[6] = "TCP" protonum[17] = "UDP" protonum[37] = "DDP" protonum[41] = "IPV6" protonum[46] = "RSVP" # select port names datafile cmd = "if [ -r " service1 " ]; then echo y; else echo n; fi" cmd | getline s; close(cmd) services = (s ~ /y/ ? service1 : service2); room_needed = 1 # check if can access the ip2cn-server use_ip2cn_server = 0 if (ip2cn_peerport && (country_code || country_name)) { ip2cn_server = "/inet/tcp/0/localhost/" ip2cn_peerport cmd = "netstat -l | grep " ip2cn_peerport " | grep -c LISTEN" cmd | getline netstat; close(cmd) if (netstat) { ++use_ip2cn_server ++got_ip2c print "## Using ip2cn_server for country name lookups" } } # check if can access the ip2c database files: ip2c-data, ip2c-names if (ip2c_data_dir && (country_code || country_name) \ && !use_ip2cn_server) { ip2c_index = ip2c_data_dir "/ip2c-index" ip2c_names = ip2c_data_dir "/ip2c-names" cmd = "if [ -r " ip2c_index " -a -r " ip2c_names " ];" cmd = cmd "then echo y; else echo n; fi" cmd | getline s; close(cmd) if (s ~ /y/) { ++got_ip2c; room_needed += 2 } } # now massage the command line argument list i = 1 while (i < ARGC && ARGV[i] ~ /=/) # skip past var=val list ++i if (i < ARGC) { for (j = ARGC; j >= i; j--) # make room for ip2c + services ARGV[j + room_needed] = ARGV[j] if (got_ip2c) { # insert ip2c-database ARGV[i++] = ip2c_index; ++ARGC ARGV[i++] = ip2c_names; ++ARGC } ARGV[i] = services; ++ARGC # insert services } else { if (got_ip2c) { # append ip2c-database ARGV[ARGC++] = ip2c_index ARGV[ARGC++] = ip2c_names } ARGV[ARGC++] = services # append services ARGV[ARGC++] = "-" } } ############################ end of BEGIN block ############################# function initialise( a, addr, cmd, i, mask, name) { # prepare localnet option for runtime use if (localnet) create_addr_mask(localnet, net_addr_mask) # prime auto machine address acquire if (machine) { split(machine, a, ":") if (a[1] ~ /auto/ && a[2] ~ /!/) get_machine_ip = "auto" } } function create_addr_mask(list, array, a, i, na, nm) { delete array $0 = list gsub(/,/, " ") # use awk's parser to scan list for (i = 1; i <= NF; i++) { split($i, a, "/") # create netmask + netaddr from CIDR nm = and(lshift(0xffffffff, 32 - a[2]), 0xffffffff) split(a[1], a, ".") na = ((a[1]*256+a[2])*256+a[3])*256+a[4] array[sprintf("%d", and(na, nm))] = nm } } function test_addr_mask(addr, array, a, ip, na) { split(addr, a, ".") ip = ((a[1]*256+a[2])*256+a[3])*256+a[4] for (na in array) { if (!(and(ip, array[na]) - na)) return 1 } return 0 } function is_local(addr) { if (!localnet) return 0 return test_addr_mask(addr, net_addr_mask) } function port_lookup(pnum, proto, a, s) { if (proto ~ /UDP/ && pnum >= traceport_str && pnum <= traceport_end) s = sprintf("%d Traceroute?", pnum) else if (port_data[pnum,proto]) s = sprintf("%d (%s)", pnum, port_data[pnum,proto]) else s = sprintf("%d", pnum) return s } # country code and name lookup, use ip2cn-server or the database files function cc_lookup(addr, a, i, l, m, h) { # no lookup needed for local addresses if (is_local(addr)) return "" # user the ip2cn-server if was found running if (use_ip2cn_server) { print addr " -" |& ip2cn_server ip2cn_server |& getline m return m } # otherwise perform lookup (standalone mode) # binary search ip2c-data for country code split(addr, a, "."); i = ((a[1]*256+a[2])*256+a[3])*256+a[4] l = 1; h = ipdatsize while (h - l > 1) { m = int((l + h) / 2) if (ipdata_str[m] < i) { l = m } else { h = m } } if (i < ipdata_str[h]) --h if (i > ipdata_end[h]) return "--:unassigned" # return country code and country name return sprintf("%s:%s", ipdata_cc[h], ipname[ipdata_cc[h]]) } # host name resolution function resolve_name(addr, a, cc, cmd, fail, ld, name, qd, x) { if (!resolve) return addr # no name resolution # first check for localnet names, these do not require host or # country code/name lookup if (machine) { split(machine, a, ":") if ((a[1] ~ /auto/ && addr == machine_ip) || addr == a[1]) { if (a[2] ~ /!/) return $4 # logfile name else if (a[2]) return a[2] # supplied name else return addr # no name, show addr } } name = machine_name[addr] if (name ~ /^-$/) return addr # no name, show addr if (name ~ /!/) return $4 # logfile name if (name) return name # supplied name # not a known machine, resolve host name if (nohost) { fail = -1 # debug option } else { fail = 0 # dig options explained # +short give short answer # +time=s how many seconds to wait for response # +retry=nr how many retries # +ignore ignore UDP truncation, no TCP retry # -x addr reverse lookup IP addr cmd = "dig +short +time=" host_max_secs cmd = cmd " +retry=0 +ignore -x " addr cmd | getline name; close(cmd) if (debug ~ /h/ && (debug !~ /x/ || \ (debug ~ /x/ && name ~ /^;;/))) { if (FNR != last_lookup_nr) { last_lookup_nr = FNR printf "%s", "\n" } sub(/^$/, "[empty]", name) printf " debug-h addr: %-15s %s\n", addr, name } sub(/\.$/, "", name) # remove name trail dot # handle name resolution failure if (length(name) == 0 || name ~ /^;;/) { ++fail name = addr } } # perhaps lookup country code, country name cc = "" if (country_code || country_name) { cc = cc_lookup(addr) if (cc) { split(cc, a, ":") if (!fail && index(hide_common_cc, a[1])) cc = "" else { if (country_code && country_name) cc = a[1] ":" a[2] else if (country_code) cc = a[1] else cc = a[2] } } } # check for local domain trimming -- don't show the well-known local # domain, display the machine name only (name before first dot) if (!fail && localnet_domain_trim) { qd = name sub(/^[-_[:alnum:]]+[^\.]/, "", qd) if (localnet_domain_trim ~ /auto/) { cmd = "host " $4 cmd | getline x close(cmd) split(x, a) ld = a[1] sub(/^[-_[:alnum:]]+[^\.]/, "", ld) } else { ld = localnet_domain_trim } if (qd == ld) { sub(/\..*$/, "", name) # remove local domain } } if (cc) { return sprintf("%s (%s)", name, cc) } return name } ########################### start main program ############################## !initialised { initialise(); ++initialised } # read database files FNR == 1 && (FILENAME == ip2c_index || FILENAME == ip2c_names || \ FILENAME == services) { printf "## Read data file: %s\n", FILENAME } FNR == 1 && (FILENAME == ip2c_index || FILENAME == ip2c_names) { next } /^#|^$/ && (FILENAME == ip2c_index || FILENAME == ip2c_names || \ FILENAME == services) { next } FILENAME == ip2c_index { # record format: ipdata_str[++ipdatsize] = $1 ipdata_end[ipdatsize] = $2 ipdata_cc[ipdatsize] = $3 next } FILENAME == ip2c_names { # record format: : split($0, a, ":") ipname[a[1]] = a[2] next } FILENAME == services { # record format: / # comment split($2, a, "/") if (a[2] !~ /tcp/ && a[2] !~ /udp/) next # skip, not ipv4 (ddp) port = a[1] + 0 proto = toupper(a[2]) port_data[port,proto] = $1 next } # use 'sf4sf database_load_time=1 /dev/null' to get database load time database_load_time { exit } # remove optional 2.6 kernel log timestamp $6 ~ /\[/ { sub(/\[[^\]]+\] /, "", $0) } # make timestamp hide_date { timestamp = sprintf("%s", $3); nl = "\n\t " } !hide_date { timestamp = sprintf("%s %02d %s", $1, $2, $3); nl = "\n\t\t" } # repeated record check and display, use previous 'prefix' value as arm key /last message repeated/ && prefix { s = $4; for (i = 5; i <= NF; i++) { s = s " " $i } printf "\n"; colorprn(timestamp, color_datetime); printf " " colorprn(s, color_repeat); next } !/last message repeated/ { prefix = "" } # option debug, option showskipped, filter debug ~ /r/ { printf "\n%s", $0 } !debug ~ /r/ && showskipped && !/IN=/ && $0 !~ log_prefix { printf "\n" $0 } !/IN=/ && $0 !~ log_prefix { next } # process iptables log record, gather record x=y pairs { delete rf for (i = 6; i <= NF; i++) { if ($i ~ /[[]/) break # stop at ICMP [...] info start if (split($i, a, "=") == 2) rf[a[1]] = a[2] } } # perhaps acquire machine ip for 'auto' machine name get_machine_ip ~ /auto/ && !rf["IN"] && rf["OUT"] { machine_ip = rf["SRC"] } get_machine_ip ~ /auto/ && rf["IN"] && !rf["OUT"] { machine_ip = rf["DST"] } { # show timestamp printf "\n"; colorprn(timestamp, color_datetime); printf " " # show --log-prefix for (i = 6; i <= NF && $i !~ /IN=.*/; i++) prefix = (prefix ? prefix " " $i : $i) fire = (prefix ~ log_prefix ? 1 : 0) colorprn(prefix, (fire ? color_prefix_fire : color_prefix)) printf " " linecol = length(timestamp) + length(prefix) + 2 # collect remaining info from this record name_src = resolve_name(rf["SRC"]) name_dst = resolve_name(rf["DST"]) port_src = port_dst = tcpflags = icmp1 = icmp2 = "" } rf["PROTO"] ~ /[0-9]+/ { rf["PROTO"] = protonum[rf["PROTO"]] } rf["PROTO"] ~ /TCP/ || rf["PROTO"] ~ /UDP/ { # get port info port_src = sprintf("%d", rf["SPT"]) port_dst = port_lookup(rf["DPT"], rf["PROTO"]) } rf["PROTO"] ~ /TCP/ { # get TCP flags i = 11; while ($i !~ /DPT/ && i < NF) ++i # seek DPT=? for (; i <= NF; i++) { if ($i ~ /SYN|ACK|FIN|RST|PSH|CWR|ECE/) tcpflags = (tcpflags ? tcpflags " " $i : $i) } } rf["PROTO"] ~ /ICMP/ { # get ICMP additional info - 1 i = 11; while ($i !~ /CODE/ && i < NF) ++i # seek CODE=? while ($++i !~ /\[.*/ && i <= NF) if ($i !~ /(ID|SEQ).*/) icmp1 = (icmp1 ? icmp1 " " $i : $i) } rf["PROTO"] ~ /ICMP/ && i < NF { # get ICMP additional info - 2 delete irf for (j = i; j <= NF; j++) { if (split($j, a, "=") == 2) { sub(/\[/, "", a[1]); irf[a[1]] = a[2] } } # src -> dst, proto, sport -> dport, ttl icmp2 = sprintf("[ %s -> %s %s %s -> %s TTL=%s ", \ irf["SRC"], irf["DST"], irf["PROTO"], \ irf["SPT"], irf["DPT"], irf["TTL"]) # tcp flags and closing ']' for (; i <= NF; i++) { if ($i ~ /SYN|ACK|FIN|RST|PSH|CWR|ECE|]/) icmp2 = icmp2 " " $i } } # check for empty report macro parameters, perhaps set defaults rf["PROTO"] ~ /TCP/ && macro_tcp { $0 = macro_tcp } rf["PROTO"] ~ /TCP/ && !macro_tcp { $0 = "addr iface line name line proto port ttl tcp" } rf["PROTO"] ~ /UDP/ && macro_udp { $0 = macro_udp } rf["PROTO"] ~ /UDP/ && !macro_udp { $0 = "addr iface line name line proto port ttl" } rf["PROTO"] ~ /ICMP/ && !icmp2 && macro_icmp1 { $0 = macro_icmp1 } rf["PROTO"] ~ /ICMP/ && icmp2 && macro_icmp2 { $0 = macro_icmp2 } rf["PROTO"] ~ /ICMP/ && (!macro_icmp1 || !macro_icmp2) { $0 = "addr iface line name line proto ttl icmp" } rf["PROTO"] ~ /IGMP/ && macro_igmp { $0 = macro_igmp } rf["PROTO"] ~ /IGMP/ && !macro_igmp { $0 = "addr iface line name line proto ttl" } # handle unknown protocol -- display raw record information rf["PROTO"] !~ /TCP|UDP|ICMP|IGMP/ { printf "%s ", "unknown protocol:" i = 6; while ($i !~ /^IN=/ && i < NF) ++i # seek IN= for (;i <= NF; i++) { printf "%s ", $i } # print record info printf "\r"; next } { # perform report macro expansion for (i = 1; i <= NF; i++) { if ($i ~ \ /addr|port|iface|name|namprt|line|proto|ttl|tcp|icmp|tab:/) expand_token($i) else printf "!%s! ", $i } printf "\r" # Done! } function expand_token(t) { if (t ~ /addr/) show_addr(rf["SRC"], rf["DST"]) if (t ~ /port/) show_ports(port_src, port_dst) if (t ~ /iface/) show_interface(rf["IN"], rf["OUT"]) if (t ~ /name/) show_name(name_src, name_dst) if (t ~ /namprt/) show_nameport(name_src, name_dst, \ port_src, port_dst) if (t ~ /line/) newline() if (t ~ /proto/) show_protocol(rf["PROTO"]) if (t ~ /ttl/) show_ttl(rf["TTL"]) if (t ~ /tcp/) show_tcp_flags(tcpflags) if (t ~ /icmp/) show_icmp_info(rf["TYPE"], rf["CODE"], \ icmp1, icmp2) if (t ~ /tab:/) expand_tab(t) } function colorprn(s, c, a, i, n) { if (ansicolor && c !~ /X/) { if (c ~ /;/) { n = split(c, a, ";") printf "\033[%d", a[1] for (i = 2; i <= n; i++) printf ";%d", a[i] printf "m%s\033[0m", s } else printf "\033[%d;%dm%s\033[0m", color_bright, c, s } else printf s } function expand_tab(t, a) { split(t, a, ":") if (a[2] < 20 || a[2] > maxlin) return if (linecol > a[2] && macro_tab_newline) newline() while (linecol < a[2]) { printf " "; ++linecol } } function show_addr(sa, da) { colorprn(sa, (fire ? color_src_fire : color_src)) colorprn(" -> ", color_arrow) colorprn(da, (fire ? color_dst_fire : color_dst)); printf " " linecol += length(sa) + length(da) + 5 } function show_interface(i, o) { if (i && o) { colorprn(i, color_in) colorprn("->", color_arrow) colorprn(o, color_out); printf " " linecol += length(i) + length(o) + 3 } else { if (i) { colorprn("inet->", color_arrow) colorprn(i, color_in) linecol += length(i) + 7 } else { colorprn(o, color_out) colorprn("->inet", color_arrow) linecol += length(o) + 7 } printf " " } } function show_protocol(p, c) { if (p ~ /UDP/) c = color_udp else if (p ~ /TCP/) c = color_tcp else if (p ~ /ICMP/) c = (fire ? color_icmp_fire : color_icmp) else c = white colorprn(p, c); printf " "; linecol += length(p) + 1 } function show_ttl(t) { colorprn("TTL=" t, (fire ? color_ttl_fire : color_ttl)) printf " "; linecol += length(t) + 5 } function show_tcp_flags(t) { colorprn(t, (fire ? color_tcp_flags_fire : color_tcp_flags)) printf " "; linecol += length(t) + 1 } function show_ports(sp, dp, c) { c = (sp+0 < 1024 ? color_spt_low : (fire ? color_spt_fire : color_spt)) colorprn(sp, c); colorprn(" -> ", color_arrow) colorprn(dp, (fire ? color_dpt_fire : color_dpt)); printf " " linecol += length(sp) + length(dp) + 5 } function show_name(sn, dn, c, n) { c = (sn !~ /\./ ? color_local_src : color_name_src) n = (length(sn) + length(dn) + 5 > maxlin - linecol ? nl : "") colorprn(sn, c); printf n; colorprn(" -> ", color_arrow) c = (dn !~ /\./ ? color_local_dst : color_name_dst) colorprn(dn, c); printf " " if (!n) linecol += length(sn) + length(dn) + 5 else linecol += length(dn) + 5 } function show_nameport(sn, dn, sp, dp, c, n) { n = (length(sn) + length(dn) + length(sp) + length(dp) + 7 \ > maxlin - linecol ? nl : "") c = (sn !~ /\./ ? color_local_src : color_name_src) colorprn(sn, c); printf ":" c = (sp+0 < 1024 ? color_spt_low :(fire ? color_spt_fire : color_spt)) colorprn(sp, c); printf n; colorprn(" -> ", color_arrow) c = (dn !~ /\./ ? color_local_dst : color_name_dst) colorprn(dn, c); printf ":" colorprn(dp, (fire ? color_dpt_fire : color_dpt)); printf " " if (!n) linecol += length(sn) + length(dn) + length(sp) + length(dp)+7 else linecol += length(dn) + length(dp) + 6 } function show_icmp_info(t, c, i1, i2, s) { s = icmp_data[t,c] s = sprintf("%d.%d", t, c) (s ? sprintf(" (%s)", s) : "") if (linecol + length(s) + 1 > maxlin) newline() colorprn(s, color_icmp_type); printf " " linecol += length(s) + 1 if (i1) { colorprn(i1, color_icmp_info); printf " " linecol += length(i1) + 1 } if (i2) { newline(); colorprn(i2, color_icmp_info); printf " " linecol += length(i2) + 1 } } function newline() { if (multiline) { printf nl; linecol = (hide_date ? 9 : 16) } else { printf " "; linecol += 1 } } END { print "\nDone!" } # EOF