Ich war auf der Suche nach einer Möglichkeit möglichst automatisch Listen von CIDR Adressen (z.B. 192.168.1.0/24) in verschiedene Formate zu bringen. Einerseits in Listen von IP-Ranges umwandeln und andererseits in „Formate“ wie mysql und iptables. Da ich sowas nicht finden konnte 😉 habe ich mir folgendes Script geschrieben.
Es setzt das Kommando ipcalc voraus, um die CIDR in Ranges umzuwandeln. Als Quelle im Netz bin ich auf www.ipdeny.com. Die bieten nach Ländern aufgeteilte und aggregierte Listen von CIDR.
Wichtig: Jede Liste kann Fehler enthalten, zudem kann ein CIDR Block schneller das Land wechseln wie man IP-Adresse sagen kann 😉 Also daran denken!
Das Script kann sowohl die Liste von ipdeny direkt aus dem Netz als Basis für die folgende Konvertierung nutzen, als auch lokale Listen von CIDR Blöcken. Als Ausgabeformate (-t) gibt es cidr, range, mysql und ipt.
cidr wird eigentlich nur dann genutzt wenn man eine Liste von CIDR von remote holt. Daraus wird das CIDR File erstellt, welches als Basis für alle weiteren Konvertierungen benutzt wird. Man kann aber auch direkt aus dem remote File die anderen Formate erstellen lassen. Dann wird kein lokales CIDR File erstellt. In dieser Version ist die „Verdrahtung“ für das remote File allerdings noch FIX auf ipdeny.com eingestellt. Das plane ich aber in einer release Version dann auf jede URL zuzulassen, welche über HTTP(S) erreichbar ist. Dies ist aber eigentlich erst eine BETA Version des Scriptes.
Bei den anderen Formaten ist v.a. mysql einen Blick wert. Diese Ausgabe besteht aus mysql INSERTS mit aus Anfangsadresse und Endadresse. Diese werden in ein File geschrieben, welches man dann mittels mysql Kommando auf der CLI importieren kann. Es werden aus jedem CIDR Block die Anfangs- und die End-IP Adresse ermittelt und in die Tabelle geschrieben. Dabei wird die mysql Funktion INET_ATON() eingesetzt, welche die IP Adresse in einen INTEGER-Wert „verwandelt“.
Dabei ist es wichtig, dass die beiden INTEGER Spalten in der Tabelle als UNSIGNED deklariert wurden! Mit dem default für int in mysql – SIGNED – könnte man keine IPs speichern, welche „grösser“ als 128.0.0.0 sind.
Die IP Adressen als Integer hat den imensen Vorteil darin, dass mysql sehr schnell darin ist Zahlen zu suchen und sehr langsam darin Zeichenketten zu finden. Zudem kann mysql eine Integer Spalte zum KEY machen, was das Suchen nochmals beschleunigt.
Die Tabelle braucht also nur 2 Interger Spalten UNSIGNED und eine Abfrage ob eine IP_ADRESSE in den Ranges in der Tabelle liegt sieht im Prinzip dann so aus
SELECT 'x' FROM `dbname`.`table` WHERE INET_ATON('IP_ADRESSE') BETWEEN `spalteAnfangIP` AND `spalteEndIP`;
1 2 3 4 5 6 | +---+ | x | +---+ | x | +---+ 1 row in set (0.04 sec) |
bekommt man ein x zurück, dann ist das ein Treffer.
Das ipt Format schreibt CDIR Blöcke direkt in Regeln für iptables. In der aktuellen Version noch direkt und FIX für die INPUT-Kette. Das möchte ich in der release Version dann aber ebenfalls als konfigurierbaren Parameter machen.
Zum Schluss noch das range Format. Einfach STARTIP-ENDIP pro CIDR Block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 | #!/bin/bash ## convertCidr2Range.sh # # @version 0.1b-devel # @type bash script # @author tobi <jahlives@gmx.ch> # @depends bash, ipcalc, wget # @license GPLv3 # @description bash script which processes lists of CIDR blocks # and converts them to different output format # output is defaulted to FILE but can be "forced" to stdout with option -o # Currently supported output format # cidr output is as list of CIDR blocks # range ip-range for each CIDR block # mysql mysql INSERT statements are generated # requires MYSQL_FIRST MYSQL_LAST and MYSQL_DB to be set # iptables iptables rules are generated # ipset ipset rules as output # htaccess .htaccess file will be generated # hosts.deny generate a hosts.deny format # @todo features on todo # add more output formats (for example ipset, hosts.deny, .htaccess) # <TODO#1> # ~~ will be in next major devel 0.2a ~~ # ~~ ipset, htaccess and hosts.deny already added in 0.1b-devel ~~ # add more configure options if ipt is used # <TODO#2> # ~~ will be in next minor devel 0.1.1a ~~ # add possibility to specify a custom remote file as CIDR block file (default is to use www.ipdeny.com) # <TODO#3> # ~~ will be added to 0.1b-devel ~~ # ~~ added to 0.1b-devel ~~ # for <TODO#3> add https support and configuration options for SSL connections # <TODO#4> # ~~ might already available in stable release, otherwise it will be put to a later version ~~ # add support for more protocols to fetch CIDR block files (example FTP, TFTP, SSH) # <TODO#5> # ~~ not in current devel so somewhere in future ~~ # ~~ FTP support added via wget in 0.1b-devel ~~ # ~~ basic SSH support added in 0.1b-devel ~~ # ################################################################################################################################### # path to default config file # all parameters in some way necessary for the script # can be specified as command line arguments CONF=/etc/default/convertCidr2Range if [[ -e $CONF && -f $CONF && -r $CONF ]] ; then # read $CONF source $CONF else ## initialize variables ## these variables cannot be set via cli # # path where to save files DIR=~/block-cidr/ # # MYSQL host to connect MYSQL_HOST='127.0.0.1' # # port to connect to MYSQL_HOST MYSQL_PORT='3308' # ## these variablescan be changed via cli ## see /path/to/script -h # # if set to 1 output is written to stdout # default to 0 which means write to FILES # set -o as argument to set to 1 OUT=0 # # if set to 1 CIDR block file will be fetched from remote # set --fetch as argument to set to 1 FORCE=0 # # country code or more general the subfolder for output FILES in ${DIR}${COUNTRY} # set -c <string> as arguments COUNTRY='' # # output type # set -t <string> as arguments TYPE='' # # path to local file with CIDR blocks # set -f <string> as arguments FILE='' # # MYSQL row name that corresponds to the first ip address of the range to insert # set --first <string> as arguments MYSQL_FIRST='`network`' # # MYSQL row name that corresponds to the last ip address of the range to insert # set --last <string> as arguments MYSQL_LAST='`broadcast`' # # MYSQL db.table name # set --db <string> as arguments MYSQL_DB='`postfix`.`blacklist-cidr`' ## fi while [ $# -gt 0 ] ; do case $1 in '--fetch') FORCE=1 ;; '-f') shift FILE="$1" ;; '-c') shift COUNTRY="$1" ;; '-o') OUT=1 ;; '-t') shift TYPE="$1" ;; '--first') shift MYSQL_FIRST="\`$1\`" ;; '--last') shift MYSQL_LAST="\`$1\`" ;; '--db') shift MYSQL_DB=$(echo $1|awk -F'.' '{print $1}') TABLE=$(echo $1|awk -F'.' '{print $2}') MYSQL_DB="\`${MYSQL_DB}\`.\`${TABLE}\`" ;; *) echo "" echo "Usage $0 [--fetch] [-f /path/to/cidr/file] [-o] -c COUNTRY_CODE (lowercase) -t cidr|mysql|range" echo "--fetch enables fetching cidr file from remote" echo " -f <string> defines the local path of a cidr file" echo " defaults to \${DIR}\${COUNTRY}/cidr" echo " -o if set output is printed to stdout instead to files" echo " defaults to \${DIR}\${COUNTRY}/\${TYPE}" echo " -c <string> two letter code of the country in lowercase" echo " -t <string> type of output to be generated" echo " type can be of the following" echo " cidr output is a list of CIDR blocks. One per line" echo " range convert cidr to range START-END. One per line" echo " mysql output is mysql INSERT statements. One per line" echo " if using mysql as output some the following variables must be set" echo " --first <string> defines the name of the row which contains the starting ip of a range" echo " --last <string> defines the name of the row which contains the last ip of a range" echo " --db <string> defines the DATABASE.TABLE to use" echo " !!!don't add backticks!!! as they will be added by the script" echo " iptables output is a list of iptables rules" echo " ipset output is a list of ipset rules" echo " htaccess generate .htaccess file" echo " hosts.deny hosts.deny file format" echo "" exit 1 ;; esac shift done function exitMessage() { printf "$1\n" [[ "x$2" != 'x' && $2 -ge 0 ]] && exit $2 return 0 } ## converts a list of cidr networks to desired output # $1 path to file with cidr # $2 desired output format function convertFromCidr() { [[ ! -f $1 || ! -r $1 ]] && exitMessage "File $1 does not exist or $(whoami) is not allowed to read" 1 [ "$TYPE" = 'mysql' ] && [[ "x$MYSQL_DB" = 'x' || "x$MYSQL_FIRST" = 'x' || "x$MYSQL_LAST" = 'x' ]] && exitMessage "Eigther \$MYSQL_DB or \$MYSQL_FIRST or \$MYSQL_LAST is not given\nThese values are necessary for type mysql\n$0 -t mysql --db db.table --first MYSQL_ROW_NAME --last MYSQL_ROW_NAME" 1 [ $OUT -eq 0 ] && rm ${DIR}${COUNTRY}"/"$2 >/dev/null 2>&1 if [ $OUT -eq 0 ] ; then touch ${DIR}${COUNTRY}"/"$2 || exitMessage "Could not touch ${DIR}${COUNTRY}/${2}" 1 fi [ $OUT -eq 0 ] && exitMessage "Start converting $1 to ${DIR}${COUNTRY}/$2 type $TYPE" PREP_STRING='' [ "$2" = 'htaccess' ] && PREP_STRING='Order Allow,Deny' # read cidr from file while read line ; do # skip lines with no cidr [ "x$(echo $line|egrep ^[0-9/.]+$)" = 'x' ] && continue if [ "x$PREP_STRING" != 'x' ] ; then [ $OUT -eq 0 ] && echo "$PREP_STRING" >> ${DIR}${COUNTRY}"/"$2 [ $OUT -eq 1 ] && echo "$PREP_STRING" PREP_STRING='' fi # trigger action for $TYPE case "$TYPE" in 'range'|'mysql') # determing start and end ip address for given cidr ADDRESS=$(ipcalc $line -b -n|grep ^Address|awk -F':' '{print $2}'|sed -r 's/\s//g') BROADCAST=$(ipcalc $line -b -n|grep ^Broadcast|awk -F':' '{print $2}'|sed -r 's/\s//g') # print range for cidr to file [ $OUT -eq 0 ] && [ "$TYPE" = 'range' ] && echo "${ADDRESS}-${BROADCAST}" >> ${DIR}${COUNTRY}"/"$2 && continue # print range for cidr to stdout [ "$TYPE" = 'range' ] && echo "${ADDRESS}-${BROADCAST}" && continue # print mysql insert query for cidr to file [ $OUT -eq 0 ] && echo "INSERT INTO $MYSQL_DB ( $MYSQL_FIRST, $MYSQL_LAST ) VALUES ( INET_ATON ( '$ADDRESS' ), INET_ATON ( '$BROADCAST' ) );" >> ${DIR}${COUNTRY}"/"$2 && continue # print mysql insert query for cidr to stdout echo "INSERT INTO $MYSQL_DB ( $MYSQL_FIRST, $MYSQL_LAST ) VALUES ( INET_ATON ( '$ADDRESS' ), INET_ATON ( '$BROADCAST' ) );" ;; 'iptables') # print iptables insert to file [ $OUT -eq 0 ] && echo "iptables -I INPUT -s $line -j DROP" >> ${DIR}${COUNTRY}"/"${TYPE} && continue # print iptables insert to stdout echo "iptables -I INPUT -s $line -j DROP" ;; 'cidr') # print cidr notation to file [ $OUT -eq 0 ] && echo $line >> ${DIR}${COUNTRY}"/"${TYPE} && continue # print cidr notation to stdout echo $line ;; 'htaccess') # print htaccess file [ $OUT -eq 0 ] && echo "Deny from $line" >> ${DIR}${COUNTRY}"/"${TYPE} && continue echo "Deny from $line" ;; 'ipset') # print lines for ipset [ $OUT -eq 0 ] && echo "ipset -A SET_NAME $line" >> ${DIR}${COUNTRY}"/"${TYPE} && continue echo "ipset -A SET_NAME $line" ;; 'hosts.deny') # print lines for hosts.deny [ $OUT -eq 0 ] && echo "ALL: $line" >> ${DIR}${COUNTRY}"/"${TYPE} && continue echo "ALL: $line" ;; esac # read lines from $1 done <$1 [ $OUT -eq 0 ] && exitMessage "Finished ${DIR}${COUNTRY}/$2" } ## function getFile() { [ $OUT -eq 0 ] && exitMessage "Fetching $3" case "$1" in 'http' | 'https' | 'ftp') wget -O - $3 > $2 2>/dev/null return $? ;; 'ssh') # ssh HOST=$(echo $3|awk -F'ssh://' '{print $2}'|awk -F':' '{print $1}') R_FILE=$(echo $3|awk -F'ssh://' '{print $2}'|awk -F':' '{print $2}') exitMessage "ssh $HOST 'cat $R_FILE'" 0 ssh $HOST "'cat $R_FILE'" 2>/dev/null return $? ;; 'tftp') echo 'Test' # tftp ;; esac } ## fetch from URL is enforced if [[ $FORCE -eq 1 && "x$TYPE" != 'x' ]] ; then # create temp file for download TEMP=`mktemp -q /tmp/${COUNTRY}.XXXXXXXXX` || exitMessage 'Could not create temporary file' 1 # fetch file if [[ "x$FILE" != 'x' && "x$(echo $FILE|egrep ^http[s]*://\|ftp://\|ssh://)" != 'x' ]] ; then T_TYPE=$(echo $FILE|awk -F'://' '{print $1}') getFile $T_TYPE $TEMP "${FILE}" else getFile http $TEMP "http://www.ipdeny.com/ipblocks/data/aggregated/${COUNTRY}-aggregated.zone" fi if [ $? -eq 0 ] ; then [ $OUT -eq 0 ] && exitMessage 'Successfully fetched' else [ $OUT -eq 0 ] && exitMessage 'Could not load' 1 fi mkdir -p ${DIR}${COUNTRY} || exitMessage "Could not create ${DIR}${COUNTRY}" 1 [ $OUT -eq 0 ] && [ -e ${DIR}${COUNTRY}"/${TYPE}.$(md5sum ${TEMP}|awk '{print $1}')" ] && rm $TEMP && exitMessage "There is already a $TYPE file based on $TEMP you provided" 1 convertFromCidr $TEMP $TYPE # remove old md5-files [ $OUT -eq 0 ] && rm ${DIR}${COUNTRY}"/"$TYPE".*" >/dev/null 2>&1 # create $TYPE.md5sum($TEMP) [ $OUT -eq 0 ] && touch ${DIR}${COUNTRY}"/"${TYPE}".$(md5sum $TEMP|awk '{print $1}')" rm $TEMP elif [[ $FORCE -eq 0 && "x$TYPE" != 'x' ]] ; then if [ "$TYPE" != 'cidr' ] ; then if [ "x$FILE" = 'x' ] ; then [ $OUT -eq 0 ] && [ -e ${DIR}${COUNTRY}"/cidr" ] && [ -e ${DIR}${COUNTRY}"/${TYPE}.$(md5sum ${DIR}${COUNTRY}/cidr|awk '{print $1}')" ] && exitMessage "There is already a $TYPE file based on ${DIR}${COUNTRY}/cidr you provided" 1 convertFromCidr ${DIR}${COUNTRY}"/cidr" $TYPE [ $OUT -eq 0 ] && rm ${DIR}${COUNTRY}"/"${TYPE}".*" >/dev/null 2>&1 [ $OUT -eq 0 ] && touch ${DIR}${COUNTRY}"/"${TYPE}".$(md5sum ${DIR}${COUNTRY}/cidr|awk '{print $1}')" elif [[ "x$FILE" != 'x' && -e $FILE && -r $FILE && "$FILE" != "${DIR}${COUNTRY}/$TYPE" ]] ; then [ $OUT -eq 0 ] && [ -e ${DIR}${COUNTRY}"/${TYPE}.$(md5sum ${FILE}|awk '{print $1}')" ] && exitMessage "There is already a $TYPE file based on $FILE you provided" 1 convertFromCidr $FILE $TYPE [ $OUT -eq 0 ] && rm ${DIR}${COUNTRY}"/"${TYPE}".*" >/dev/null 2>&1 [ $OUT -eq 0 ] && touch ${DIR}${COUNTRY}"/"${TYPE}".$(md5sum ${FILE}|awk '{print $1}')" fi elif [[ "$TYPE" = 'cidr' && "x$FILE" != 'x' && -e $FILE && -r $FILE && "$FILE" != "${DIR}${COUNTRY}/$TYPE" ]] ; then [ $OUT -eq 0 ] && mkdir -p ${DIR}${COUNTRY} >/dev/null 2>&1 [ $OUT -eq 0 ] && cat $FILE | egrep ^[0-9/.]+$ > ${DIR}${COUNTRY}"/"$TYPE && rm ${DIR}${COUNTRY}"/"${TYPE}".*" >/dev/null 2>&1 && touch ${DIR}${COUNTRY}"/"${TYPE}".$(md5sum ${DIR}${COUNTRY}"/"${TYPE}|awk '{print $1}')" [ $OUT -eq 1 ] && cat $FILE | egrep ^[0-9/.]+$ fi fi |
Leave a Reply