Archive

Kategorien

Shellscript CIDR Blöcke „konvertieren“

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

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre lang="" line="" escaped="" cssfile="">

  

  

  

one + nine =

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahren Sie mehr darüber, wie Ihre Kommentardaten verarbeitet werden .