|
|
@@ -1,95 +1,205 @@
|
|
|
-#! /bin/bash
|
|
|
-
|
|
|
-#
|
|
|
+#!/bin/bash
|
|
|
### BEGIN INIT INFO
|
|
|
-# Provides: ipset
|
|
|
-# Required-Start: $local_fs $network $remote_fs $syslog
|
|
|
-# Required-Stop: $local_fs $network $remote_fs $syslog
|
|
|
-# Default-Start: 2 3 4 5
|
|
|
-# Default-Stop: 0 1 6
|
|
|
-# Short-Description: start and stop the ipset lists
|
|
|
-# Description: start and stop the ipset lists
|
|
|
+# Provides: ipset
|
|
|
+# Required-Start: $local_fs $network
|
|
|
+# Required-Stop: $local_fs
|
|
|
+# Default-Start: 2 3 4 5
|
|
|
+# Default-Stop: 0 1 6
|
|
|
+# Short-Description: Atomic restore/swap of ipset from /etc/ipset.d/*.conf
|
|
|
### END INIT INFO
|
|
|
|
|
|
+IPSET='/sbin/ipset'
|
|
|
+IPSET_DIR='/etc/ipset.d'
|
|
|
+[ -x "$IPSET" ] || exit 1
|
|
|
+
|
|
|
+# LSB logging (if not available, define simple ones)
|
|
|
if [ -r "/lib/lsb/init-functions" ]; then
|
|
|
- . /lib/lsb/init-functions
|
|
|
+ . /lib/lsb/init-functions
|
|
|
else
|
|
|
- log_success_msg() {
|
|
|
- echo "$@"
|
|
|
- }
|
|
|
- log_warning_msg() {
|
|
|
- echo "$@" >&2
|
|
|
- }
|
|
|
- log_failure_msg() {
|
|
|
- echo "$@" >&2
|
|
|
- }
|
|
|
+ log_success_msg() { echo "[ OK ] $*"; }
|
|
|
+ log_failure_msg() { echo "[FAIL] $*" >&2; }
|
|
|
+ log_warning_msg() { echo "[WARN] $*" >&2; }
|
|
|
fi
|
|
|
|
|
|
+# Get base set name from .conf file (filename without .conf)
|
|
|
+get_set_name() {
|
|
|
+ local conf="$1"
|
|
|
+ basename "$conf" .conf
|
|
|
+}
|
|
|
|
|
|
-IPSET='/sbin/ipset'
|
|
|
-IPSET_DIR='/etc/ipset.d'
|
|
|
+# Create temporary file with swapped set name (name -> name_new)
|
|
|
+create_temp_ipset_file() {
|
|
|
+ local name="$1"
|
|
|
+ local conf="$IPSET_DIR/$name.conf"
|
|
|
+ local tmpfile="$IPSET_DIR/$name.ipset"
|
|
|
|
|
|
-# if the ip configuration utility isn't around we can't function.
|
|
|
-[ -x ${IPSET} ] || exit 1
|
|
|
+ # Replace all occurrences of 'name' as a whole word with 'name_new'
|
|
|
+ # This handles lines: "create name hash:ip ..." -> "create name_new hash:ip ..."
|
|
|
+ # and "add name 1.2.3.4 ..." -> "add name_new 1.2.3.4 ..."
|
|
|
+ sed -E "
|
|
|
+ s/^([[:space:]]*)(create|add)[[:space:]]+$name([[:space:]]|$)/\1\2 ${name}_new\3/g;
|
|
|
+ s/^([[:space:]]*)(create|add)[[:space:]]+$name([[:space:]]|$)/\1\2 ${name}_new\3/g
|
|
|
+ " "$conf" > "$tmpfile"
|
|
|
|
|
|
-stop_ipset() {
|
|
|
-ls -x -1 "${IPSET_DIR}/"*.conf | while read IPSET_FILE; do
|
|
|
-ipset_name=`grep -P "^create\s+(\S+)\s+" "${IPSET_FILE}" | awk '{ print $2 }' | sed 's/_new//'`
|
|
|
-[ -z "${ipset_name}" ] && continue
|
|
|
-echo -n $"Destroy ${ipset_name} ipset"
|
|
|
-${IPSET} destroy ${ipset_name} >/dev/null 2>&1
|
|
|
-echo
|
|
|
-done
|
|
|
-return 0
|
|
|
+ echo "$tmpfile"
|
|
|
}
|
|
|
|
|
|
-start_ipset() {
|
|
|
-ls -x -1 "${IPSET_DIR}/"*.conf | while read IPSET_FILE; do
|
|
|
-ipset_name=`grep -P "^create\s+(\S+)\s+" "${IPSET_FILE}" | awk '{ print $2 }' | sed 's/_new//'`
|
|
|
-if [ ! -e "${IPSET_DIR}/${ipset_name}.ipset" ]; then
|
|
|
- cat "${IPSET_FILE}" | sed 's/_new//' >"${IPSET_DIR}/${ipset_name}.ipset"
|
|
|
+# Load entries into a set from a file (which may be a full ipset save format)
|
|
|
+load_from_file() {
|
|
|
+ local setname="$1"
|
|
|
+ local file="$2"
|
|
|
+ "$IPSET" restore -! < "$file" 2>/dev/null
|
|
|
+}
|
|
|
+
|
|
|
+# Atomically replace live set with new configuration
|
|
|
+replace_atomic() {
|
|
|
+ local name="$1"
|
|
|
+ local conf="$IPSET_DIR/$name.conf"
|
|
|
+ local tmpfile
|
|
|
+
|
|
|
+ # 1. Create temporary ipset file with name_new
|
|
|
+ tmpfile=$(create_temp_ipset_file "$name")
|
|
|
+ if [ ! -s "$tmpfile" ]; then
|
|
|
+ log_failure_msg "Failed to create temporary ipset file for $name"
|
|
|
+ rm -f "$tmpfile"
|
|
|
+ return 1
|
|
|
+ fi
|
|
|
+
|
|
|
+ # 2. Create temporary ipset set (if already exists, destroy it first)
|
|
|
+ if "$IPSET" list "${name}_new" &>/dev/null; then
|
|
|
+ "$IPSET" destroy "${name}_new" 2>/dev/null
|
|
|
+ fi
|
|
|
+ # Extract type from conf to create empty set (necessary before restore)
|
|
|
+ local type=$(sed -n -E 's/^create\s+'"$name"'\s+(\S+).*/\1/p' "$conf" | head -1)
|
|
|
+ if [ -z "$type" ]; then
|
|
|
+ log_failure_msg "Cannot determine type for $name from $conf"
|
|
|
+ rm -f "$tmpfile"
|
|
|
+ return 1
|
|
|
+ fi
|
|
|
+ if ! "$IPSET" create "${name}_new" "$type" comment 2>/dev/null; then
|
|
|
+ log_failure_msg "Cannot create temporary set ${name}_new"
|
|
|
+ rm -f "$tmpfile"
|
|
|
+ return 1
|
|
|
+ fi
|
|
|
+
|
|
|
+ # 3. Load data into temporary set
|
|
|
+ if ! load_from_file "${name}_new" "$tmpfile"; then
|
|
|
+ log_failure_msg "Failed to load data into ${name}_new"
|
|
|
+ "$IPSET" destroy "${name}_new" 2>/dev/null
|
|
|
+ rm -f "$tmpfile"
|
|
|
+ return 1
|
|
|
+ fi
|
|
|
+
|
|
|
+ # 4. Atomic swap
|
|
|
+ if ! "$IPSET" swap "${name}_new" "$name"; then
|
|
|
+ log_failure_msg "Atomic swap failed for $name"
|
|
|
+ "$IPSET" destroy "${name}_new" 2>/dev/null
|
|
|
+ rm -f "$tmpfile"
|
|
|
+ return 1
|
|
|
fi
|
|
|
-echo -n $"Load ${ipset_name} ipset"
|
|
|
-${IPSET} restore -file "${IPSET_DIR}/${ipset_name}.ipset" >/dev/null 2>&1
|
|
|
-echo
|
|
|
-done
|
|
|
-return 0
|
|
|
+
|
|
|
+ # 5. Destroy old set (now named _new) and remove temp file
|
|
|
+ "$IPSET" destroy "${name}_new" 2>/dev/null
|
|
|
+ rm -f "$tmpfile"
|
|
|
+ log_success_msg "Atomically replaced ipset $name"
|
|
|
+ return 0
|
|
|
+}
|
|
|
+
|
|
|
+# Simple restore (when set does not exist)
|
|
|
+restore_simple() {
|
|
|
+ local name="$1"
|
|
|
+ local conf="$IPSET_DIR/$name.conf"
|
|
|
+ if "$IPSET" restore -! < "$conf" 2>/dev/null; then
|
|
|
+ log_success_msg "Restored ipset $name from $conf"
|
|
|
+ return 0
|
|
|
+ else
|
|
|
+ log_failure_msg "Failed to restore $name from $conf"
|
|
|
+ return 1
|
|
|
+ fi
|
|
|
+}
|
|
|
+
|
|
|
+start_ipset() {
|
|
|
+ local ret=0
|
|
|
+ for conf in "$IPSET_DIR"/*.conf; do
|
|
|
+ [ -f "$conf" ] || continue
|
|
|
+ name=$(get_set_name "$conf")
|
|
|
+ [ -z "$name" ] && continue
|
|
|
+
|
|
|
+ if "$IPSET" list "$name" &>/dev/null; then
|
|
|
+ # Set already exists → atomic replace
|
|
|
+ replace_atomic "$name" || ret=1
|
|
|
+ else
|
|
|
+ # Set does not exist → simple restore
|
|
|
+ restore_simple "$name" || ret=1
|
|
|
+ fi
|
|
|
+ done
|
|
|
+ return $ret
|
|
|
+}
|
|
|
+
|
|
|
+stop_ipset() {
|
|
|
+ local ret=0
|
|
|
+ for conf in "$IPSET_DIR"/*.conf; do
|
|
|
+ [ -f "$conf" ] || continue
|
|
|
+ name=$(get_set_name "$conf")
|
|
|
+ [ -z "$name" ] && continue
|
|
|
+ if "$IPSET" list "$name" &>/dev/null; then
|
|
|
+ "$IPSET" destroy "$name" 2>/dev/null || ret=1
|
|
|
+ log_success_msg "Destroyed $name"
|
|
|
+ fi
|
|
|
+ # Also clean any leftover temporary sets
|
|
|
+ if "$IPSET" list "${name}_new" &>/dev/null; then
|
|
|
+ "$IPSET" destroy "${name}_new" 2>/dev/null
|
|
|
+ log_success_msg "Destroyed leftover ${name}_new"
|
|
|
+ fi
|
|
|
+ # Remove any stray .ipset temp files (if left)
|
|
|
+ rm -f "$IPSET_DIR/$name.ipset"
|
|
|
+ done
|
|
|
+ return $ret
|
|
|
}
|
|
|
|
|
|
save_ipset() {
|
|
|
-ls -x -1 "${IPSET_DIR}/"*.conf | while read IPSET_FILE; do
|
|
|
-ipset_name=`grep -P "^create\s+(\S+)\s+" "${IPSET_FILE}" | awk '{ print $2 }' | sed 's/_new//'`
|
|
|
-[ -z "${ipset_name}" ] && continue
|
|
|
-echo -n $"Save ${ipset_name} ipset"
|
|
|
-${IPSET} save ${ipset_name} -file "${IPSET_DIR}/${ipset_name}.ipset" >/dev/null 2>&1
|
|
|
-echo
|
|
|
-done
|
|
|
-return 0
|
|
|
+ local ret=0
|
|
|
+ for name in $(ipset list -n); do
|
|
|
+ [ -z "$name" ] && continue
|
|
|
+ conf="$IPSET_DIR/$name"
|
|
|
+ if "$IPSET" save "$name" > "$conf.tmp" 2>/dev/null; then
|
|
|
+ mv "$conf.tmp" "$conf.conf"
|
|
|
+ log_success_msg "Saved $name to $conf.conf"
|
|
|
+ else
|
|
|
+ rm -f "$conf.tmp"
|
|
|
+ log_failure_msg "Failed to save $name"
|
|
|
+ ret=1
|
|
|
+ fi
|
|
|
+ done
|
|
|
+ return $ret
|
|
|
+}
|
|
|
+
|
|
|
+status_ipset() {
|
|
|
+ local ret=0
|
|
|
+ for conf in "$IPSET_DIR"/*.conf; do
|
|
|
+ [ -f "$conf" ] || continue
|
|
|
+ name=$(get_set_name "$conf")
|
|
|
+ [ -z "$name" ] && continue
|
|
|
+ if "$IPSET" list "$name" &>/dev/null; then
|
|
|
+ echo "$name loaded"
|
|
|
+ else
|
|
|
+ echo "$name NOT loaded"
|
|
|
+ ret=1
|
|
|
+ fi
|
|
|
+ done
|
|
|
+ return $ret
|
|
|
}
|
|
|
|
|
|
-# See how we were called.
|
|
|
case "$1" in
|
|
|
- start)
|
|
|
- start_ipset
|
|
|
- RET=$?
|
|
|
- ;;
|
|
|
- stop)
|
|
|
- stop_ipset
|
|
|
- RET=$?
|
|
|
- ;;
|
|
|
- save)
|
|
|
- save_ipset
|
|
|
- RET=$?
|
|
|
- ;;
|
|
|
- restart|reload)
|
|
|
- stop_ipset
|
|
|
- start_ipset
|
|
|
- RET=$?
|
|
|
- ;;
|
|
|
- *)
|
|
|
- echo $"Usage: $0 {start|stop|restart|reload}"
|
|
|
+ start) start_ipset ;;
|
|
|
+ stop) stop_ipset ;;
|
|
|
+ restart) stop_ipset; start_ipset ;;
|
|
|
+ reload) stop_ipset; start_ipset ;;
|
|
|
+ save) save_ipset ;;
|
|
|
+ status) status_ipset ;;
|
|
|
+ *)
|
|
|
+ echo "Usage: $0 {start|stop|restart|reload|save|status}"
|
|
|
exit 1
|
|
|
esac
|
|
|
|
|
|
-exit ${RET}
|
|
|
-
|
|
|
+exit $?
|