# # Private BASHlib for Watcher-Report3 - Efficiency report # SQL="sqlite3 $POOL/$ME" export AWKPATH #declare -A grands=([records]=0 [cidrs]=0 [plain]=0 [nodes]=0 [label]='') #declare -A whites=([records]=0 [cidrs]=0 [plain]=0 [nodes]=0 [label]='') _LABELW=34 OUTFORM="%-34s : %8d %8d %8d %'15d\n" TOTFORM="%34s » %8d %8d %8d %'15d\n" DETFORM="%34s . %8d %8d %8d %'15d\n" COLFORM="%34s | %8s %8s %8s %15s\n" # new DB_preload (NFT) # global REL_CHAINS delivers relevant chains DB_preload() { local funtag="[${FUNCNAME[0]}]" local csvfile="$POOL/$ME-preload.csv" local member membertype ipset chain target comment local setname elements # Write CSV header echo "member,membertype,ipset,chain,target,comment,packets,bytes" > "$csvfile" # REL_CHAINS must be defined globally before calling DB_preload() for chain in $(echo $REL_CHAINS) do # Get all unique sets used in the chain REL_SETS=$(nft_list_chain $chain) for setname in $(echo $REL_SETS) do elements=$(element_list "$the_set") IFS="," read -r the_set the_target <<< "$setname" awk -v chain="$chain" \ -v the_set="$the_set" \ -v the_target="$the_target" ' @include "netlib.awk" { member = $1 type = "IP" # if (index(member, "/")) { # type="CIDR" # } else {type="IP" } if (index(member, "/")) { type = "CIDR" } else if (index(member, "-")) { split(member, parts, "-") # Convert range back to CIDR member = cidr(parts[1], parts[2]) type = "CIDR" } else { type = "IP" } match($0, /comment "(.*)"/, tmp) if (tmp[1] != "") { comment = "\"" tmp[1] "\"" } else {comment = "none"} #packets=1 # to be calculated match($0, /packets "(.*)"/, tmp) packets=tmp[1] #bytes=1 # to be calculated match($0, /bytes "(.*)"/, tmp) bytes=tmp[1] if ( member != "" ) printf("%s,%s,%s,%s,%s,%s,%d,%d\n", \ member, type, the_set, chain, the_target, comment, packets, bytes) } ' <<< "$elements" >> "$csvfile" done done DB_dump "$csvfile" } DB_dump() { local funtag="[${FUNCNAME[0]}]" local csvfile="$1" local tmpcsv="${csvfile}.nohdr" local Schema=" DROP TABLE IF EXISTS ipsets; CREATE TABLE ipsets ( member TEXT, -- IP addr membertype TEXT, -- Addr type: IP, CIDR ipset TEXT, -- The IPSET chain TEXT, -- Chain name target TEXT, -- Chain target: ACCEPT, DROP, RETURN comment TEXT NOT NULL, packets INTEGER, -- Packet count bytes INTEGER, -- Byte count PRIMARY KEY (ipset, member) );" # 1) Schema neu anlegen echo "$Schema" | $SQL # 2) Header abschlagen tail -n +2 "$csvfile" > "$tmpcsv" # 3) CSV importieren $SQL <<-EOF .mode csv .separator "," .import $tmpcsv ipsets EOF # 4) Aufräumen # rm -f "$tmpcsv" } # Draw a section headline # $1 headline text # $2 character for line headline() { local funtag="[${FUNCNAME[0]}]" local total=78 awk -v text="$1" -v tot_width=$total -v char="$2" ' BEGIN { prefixlen=5 for (i=1; i<=prefixlen; i++) { prefix = prefix char } strlen = length(text); suffixlen = tot_width - length(prefix) - strlen - 1; # "- 2" für die Leerzeichen if (suffixlen < 0) suffixlen = 0; suffix = sprintf("%*s", suffixlen, ""); # Generiere leerzeichen-basiertes Padding gsub(" ", char, suffix); # Ersetze Leerzeichen mit "-" print "" print prefix " " text " " suffix; }' } # Draw a simple separation line # $1 character for line sepline() { local funtag="[${FUNCNAME[0]}]" local total=79 awk -v tot_width=$total -v char="$1" ' BEGIN { for (i=1; i<=tot_width; i++) { line = line char } print line; }' } # Print a section headline sec_headline() { # $1 Section name # $2 character to use local funtag="[${FUNCNAME[0]}]" local total=53 awk -v text="$1" -v tot_width=$total -v char="$2" ' BEGIN { prefixlen=5 for (i=1; i<=prefixlen; i++) { prefix = prefix char } strlen = length(text); suffixlen = tot_width - length(prefix) - strlen - 1; # "- 2" für die Leerzeichen if (suffixlen < 0) suffixlen = 0; suffix = sprintf("%*s", suffixlen, ""); # Generiere leerzeichen-basiertes Padding gsub(" ", char, suffix); # Ersetze Leerzeichen mit "-" print "" print prefix " " text " " suffix; }' } ################################################################### # Dropped packets determination - Basis for efficiency calculations ################################################################### # Cummulate packets from legal access taken from the WATCGCNT chain cummulate_passed_connections() { local funtag="[${FUNCNAME[0]}]" #local report awk ' BEGIN { sum = 0 } /(tcp dport)/ { if (match($0, /packets ([0-9]+)/, n)) sum += n[1] } END { print sum } ' <<< "$(nft_list_chain WATCHCNT)" } cummulate_passthru_connections() { local funtag="[${FUNCNAME[0]}]" local report awk ' BEGIN { packets = 0 } /^[1-9]/ { # Extract packet count ... match($0, /packets ([0-9]+)/, tmp) packets += tmp[1] } END { print packets } ' <<< "$(element_list passthru)" } # Determine IPSETs of particular chain type; e.g. DROP chaintype() { local funtag="[${FUNCNAME[0]}]" local chain="$1" local tmp #tmp=$(iptables -nL INPUT | grep "$chain" | grep 'match-set') tmp=$(nft_chains) awk ' { match($0,/match-set ([^ ]+)/,tmp) set = tmp[1] sets = sets" "set } END { print sets } ' <<< "$tmp" } report_efficiency () { local funtag="[${FUNCNAME[0]}]" local since now watcherup days rest local total_connections=0 total_passed_connections=0 local firewall_records efficiency local ipset_name counter allchains tmp_chains local invalid="INVALID" FILT_START=$(date +%s%3N) # TLchains='INPUT' # Determine relevant chains; i.e those with 'saddr @' in it allchains=$(nft_chains) for c in $allchains do exists=$(nft_list_chain $c | grep -cw 'saddr @') if [ $exists > 0 ] then tmp_chains="$tmp_chains $c" fi done REL_CHAINS=$(echo $tmp_chains | xargs) DB_preload if [ ! -e $POOL/EFF_MIN ] then echo "100.0" > $POOL/EFF_MIN fi if [ ! -e $POOL/EFF_MAX ] then echo "0.0" > $POOL/EFF_MAX fi echo " ««««« Connection attempts of DROPed bandits »»»»» " read since < $(Masterpath)/START_TIME now=$(date +%s) watcherup=$(( now - since )) echo -n "Since: $(date -d @$since --iso=seconds)" days=$(( watcherup / 86400 )) rest=$(( watcherup % 86400 )) echo " Uptime: $days d, $(date +%H:%M:%S -ud@$rest)" # Collect all DROP ipsets from the database mapfile -t drop_ipsets < <(sqlite3 $POOL/$ME "SELECT DISTINCT ipset FROM ipsets WHERE target = 'DROP';") # Iterate and sum up counters for each DROP ipset for ipset_name in "${drop_ipsets[@]}" do counter=$(ipset -t list "$ipset_name" | awk '/^Number of packets:/ {print $4}') total_connections=$(( total_connections + counter )) done report=$(dropped_packets) echo "$report" total_connections=$( grep "Total DROPed connections" <<< "$report" | awk '{ print $4 }' ) # Get total passed connections (ACCEPT chain) total_passed_connections=$(cummulate_passed_connections) # Get total passthru connections (ACCEPT chain) white-bots total_passthru_connections=$(cummulate_passthru_connections) # Print the results sec_headline Summary _ printf "%34s: %8d\n" "Total DROPed connections" $total_connections printf "%34s: %8d\n" "Total passed connections" $total_passed_connections printf "%34s: %8d\n" "Total passthru connections" $total_passthru_connections firewall_records=$(ipset -t list | awk '/Number of/ { sum += $4 } END {print sum}') printf "%34s: %8d\n" "Total records in firewall" $firewall_records # Calculate efficiency (avoid division by zero) if [ $((total_connections + total_passed_connections)) -gt 0 ] then PT=$total_passthru_connections efficiency=$(echo "scale=3; ($total_connections / ($total_connections + $total_passed_connections )) * 100" | bc) else efficiency="0.00" fi if [ $total_connections -gt 0 ] then read min < $POOL/EFF_MIN read max < $POOL/EFF_MAX invalid="" fi echo "$total_connections $total_passed_connections $total_passthru_connections " |\ awk -v pool="$POOL" \ -v efficiency="$efficiency" \ -v min_efficiency="$min" \ -v max_efficiency="$max" \ -v invalid="$invalid" ' BEGIN { } { total_connections = $1 total_passed_connections = $2 total_passthru_connections = $3 #printf "%35s %8.2f%%\n", "Total dropped connections:", total_connections/100 #printf "%35s %8.2f%%\n", "Total passed connections:", total_passed_connections/100 #printf "%35s %8.2f%%\n", "Total passthru connections:", total_passthru_connections/100 # Aktualisiere max/min Effizienz nur bei Veränderung if (efficiency > max_efficiency) { max_efficiency = efficiency print max_efficiency > pool"/EFF_MAX" } if (efficiency < min_efficiency) { min_efficiency = efficiency print min_efficiency > pool"/EFF_MIN" } # Ausgabe formatieren printf "%35s %8.2f%% %s\n", "Efficiency:", efficiency, invalid printf "%35s %8.2f%% %s\n", ".... min:", min_efficiency, invalid printf "%35s %8.2f%% %s\n", ".... max:", max_efficiency, invalid }' # printf "%35s %8.2f%%\n" "Efficiency:" $efficiency sec_headline Legend _ echo " passthru - Count of 'white bots' TD/TP ~ Total dropped/passed Efficiency = TD / (TD+TP) " FILT_END=$(date +%s%3N) FILT_TIME=$(( FILT_END - FILT_START)) printf "%s took %d ms\n" $funtag $FILT_TIME } # # Collect all packets from chains with a DROP target # org-dropped_packets() { local funtag="[${FUNCNAME[0]}]" local all resultset total_connections total_packets #head -10 <<< "$ALL" # Compare with the ALL format resultset=$($SQL " SELECT member, packets, bytes, comment FROM ipsets WHERE target = 'drop' ;" ) echo "$resultset" > $POOL/resultset #head -10 <<< "$resultset" # Make an ALL format from the DB resultset ... all=$( awk ' BEGIN { FS = "|" } { print printf "%s packets %d bytes %d comment \"%s\"\n", $1, $2 ,$3, $4 } ' <<< "$resultset" ) #echo "All rebuilt:" #head -10 <<< "$all" # From here on everything works as before ... # with a proper 'all' provided report=`awk ' function drawline (len,char, i,line) { for (i=1; i<=len; i++) { line = line char } print line } /(packets 0)/ { next } { # Extract IPSET Name ... match($0, /comment "([^"]+)"/, tmp) set = tmp[1] if ( set == "" ) { next } gsub(" ", "", set) # Remove spaces from set names sets[set] = set # Extract packet count ... match($0, /packets ([0-9]+)/, tmp) packets = tmp[1] # Extract byte count ... match($0, /bytes ([0-9]+)/, tmp) bytes = tmp[1] # printf "Packets: %8d, Bytes %15d\n", packets, bytes # Debug ... packet_sum[set] += packets byte_sum[set] += bytes } END { printf "%-35s %8s %15s\n", "IPset comment", "Packets","Bytes" #drawline (50,"–") print "–––––––––––––––––––––––––––––––––––––––––––––––––––" asort (sets) for (s in sets) { printf "%-35s %8d %15d\n", sets[s], packet_sum[sets[s]], byte_sum[sets[s]] total_packets += packet_sum[sets[s]] total_bytes += byte_sum[sets[s]] } #drawline (50,"–") print "–––––––––––––––––––––––––––––––––––––––––––––––––––" printf "%34s: %8d %15d\n", "Total DROPed connections", total_packets, total_bytes } ' <<< $all` # Show report on display/stdout echo "$report" total_connections=`grep "Total DROPed connections" <<< "$report" | awk '{print $4}'` } # Dropped packets taken from DB by SQL dropped_packets() { local funtag="[${FUNCNAME[0]}]" local all resultset total_connections total_packets resultset=$( $SQL " SELECT comment, sum(packets), sum(bytes) FROM ipsets WHERE target = 'drop' GROUP BY comment ;" ) echo "$resultset" > $POOL/resultset echo "$resultset" #head -10 <<< "$resultset" read -r comment packetsum bytesum <<< "$resultset" report=$( awk ' { # printf ("%35s %8d %8d\n", $1, $2, $3) packets += $2 bytes += $3 } END { printf ("Total DROPed connections: %8d %8d\n", packets, bytes) } ' <<< "$resultset" ) # Show report on display/stdout echo "$report" total_connections=`grep "Total DROPed connections" <<< "$report" | awk '{print $4}'` } # vim: set filetype=sh noexpandtab tabstop=8 shiftwidth=8 autoindent smartindent :