ipset 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. #!/bin/bash
  2. ### BEGIN INIT INFO
  3. # Provides: ipset
  4. # Required-Start: $local_fs $network
  5. # Required-Stop: $local_fs
  6. # Default-Start: 2 3 4 5
  7. # Default-Stop: 0 1 6
  8. # Short-Description: Atomic restore/swap of ipset from /etc/ipset.d/*.conf
  9. ### END INIT INFO
  10. IPSET='/sbin/ipset'
  11. IPSET_DIR='/etc/ipset.d'
  12. [ -x "$IPSET" ] || exit 1
  13. # LSB logging (if not available, define simple ones)
  14. if [ -r "/lib/lsb/init-functions" ]; then
  15. . /lib/lsb/init-functions
  16. else
  17. log_success_msg() { echo "[ OK ] $*"; }
  18. log_failure_msg() { echo "[FAIL] $*" >&2; }
  19. log_warning_msg() { echo "[WARN] $*" >&2; }
  20. fi
  21. # Get base set name from .conf file (filename without .conf)
  22. get_set_name() {
  23. local conf="$1"
  24. basename "$conf" .conf
  25. }
  26. # Create temporary file with swapped set name (name -> name_new)
  27. create_temp_ipset_file() {
  28. local name="$1"
  29. local conf="$IPSET_DIR/$name.conf"
  30. local tmpfile="$IPSET_DIR/$name.ipset"
  31. # Replace all occurrences of 'name' as a whole word with 'name_new'
  32. # This handles lines: "create name hash:ip ..." -> "create name_new hash:ip ..."
  33. # and "add name 1.2.3.4 ..." -> "add name_new 1.2.3.4 ..."
  34. sed -E "
  35. s/^([[:space:]]*)(create|add)[[:space:]]+$name([[:space:]]|$)/\1\2 ${name}_new\3/g;
  36. s/^([[:space:]]*)(create|add)[[:space:]]+$name([[:space:]]|$)/\1\2 ${name}_new\3/g
  37. " "$conf" > "$tmpfile"
  38. echo "$tmpfile"
  39. }
  40. # Load entries into a set from a file (which may be a full ipset save format)
  41. load_from_file() {
  42. local setname="$1"
  43. local file="$2"
  44. "$IPSET" restore -! < "$file" 2>/dev/null
  45. }
  46. # Atomically replace live set with new configuration
  47. replace_atomic() {
  48. local name="$1"
  49. local conf="$IPSET_DIR/$name.conf"
  50. local tmpfile
  51. # 1. Create temporary ipset file with name_new
  52. tmpfile=$(create_temp_ipset_file "$name")
  53. if [ ! -s "$tmpfile" ]; then
  54. log_failure_msg "Failed to create temporary ipset file for $name"
  55. rm -f "$tmpfile"
  56. return 1
  57. fi
  58. # 2. Create temporary ipset set (if already exists, destroy it first)
  59. if "$IPSET" list "${name}_new" &>/dev/null; then
  60. "$IPSET" destroy "${name}_new" 2>/dev/null
  61. fi
  62. # Extract type from conf to create empty set (necessary before restore)
  63. local type=$(sed -n -E 's/^create\s+'"$name"'\s+(\S+).*/\1/p' "$conf" | head -1)
  64. if [ -z "$type" ]; then
  65. log_failure_msg "Cannot determine type for $name from $conf"
  66. rm -f "$tmpfile"
  67. return 1
  68. fi
  69. if ! "$IPSET" create "${name}_new" "$type" comment 2>/dev/null; then
  70. log_failure_msg "Cannot create temporary set ${name}_new"
  71. rm -f "$tmpfile"
  72. return 1
  73. fi
  74. # 3. Load data into temporary set
  75. if ! load_from_file "${name}_new" "$tmpfile"; then
  76. log_failure_msg "Failed to load data into ${name}_new"
  77. "$IPSET" destroy "${name}_new" 2>/dev/null
  78. rm -f "$tmpfile"
  79. return 1
  80. fi
  81. # 4. Atomic swap
  82. if ! "$IPSET" swap "${name}_new" "$name"; then
  83. log_failure_msg "Atomic swap failed for $name"
  84. "$IPSET" destroy "${name}_new" 2>/dev/null
  85. rm -f "$tmpfile"
  86. return 1
  87. fi
  88. # 5. Destroy old set (now named _new) and remove temp file
  89. "$IPSET" destroy "${name}_new" 2>/dev/null
  90. rm -f "$tmpfile"
  91. log_success_msg "Atomically replaced ipset $name"
  92. return 0
  93. }
  94. # Simple restore (when set does not exist)
  95. restore_simple() {
  96. local name="$1"
  97. local conf="$IPSET_DIR/$name.conf"
  98. if "$IPSET" restore -! < "$conf" 2>/dev/null; then
  99. log_success_msg "Restored ipset $name from $conf"
  100. return 0
  101. else
  102. log_failure_msg "Failed to restore $name from $conf"
  103. return 1
  104. fi
  105. }
  106. start_ipset() {
  107. local ret=0
  108. for conf in "$IPSET_DIR"/*.conf; do
  109. [ -f "$conf" ] || continue
  110. name=$(get_set_name "$conf")
  111. [ -z "$name" ] && continue
  112. if "$IPSET" list "$name" &>/dev/null; then
  113. # Set already exists → atomic replace
  114. replace_atomic "$name" || ret=1
  115. else
  116. # Set does not exist → simple restore
  117. restore_simple "$name" || ret=1
  118. fi
  119. done
  120. return $ret
  121. }
  122. stop_ipset() {
  123. local ret=0
  124. for conf in "$IPSET_DIR"/*.conf; do
  125. [ -f "$conf" ] || continue
  126. name=$(get_set_name "$conf")
  127. [ -z "$name" ] && continue
  128. if "$IPSET" list "$name" &>/dev/null; then
  129. "$IPSET" destroy "$name" 2>/dev/null || ret=1
  130. log_success_msg "Destroyed $name"
  131. fi
  132. # Also clean any leftover temporary sets
  133. if "$IPSET" list "${name}_new" &>/dev/null; then
  134. "$IPSET" destroy "${name}_new" 2>/dev/null
  135. log_success_msg "Destroyed leftover ${name}_new"
  136. fi
  137. # Remove any stray .ipset temp files (if left)
  138. rm -f "$IPSET_DIR/$name.ipset"
  139. done
  140. return $ret
  141. }
  142. save_ipset() {
  143. local ret=0
  144. for name in $(ipset list -n); do
  145. [ -z "$name" ] && continue
  146. conf="$IPSET_DIR/$name"
  147. if "$IPSET" save "$name" > "$conf.tmp" 2>/dev/null; then
  148. mv "$conf.tmp" "$conf.conf"
  149. log_success_msg "Saved $name to $conf.conf"
  150. else
  151. rm -f "$conf.tmp"
  152. log_failure_msg "Failed to save $name"
  153. ret=1
  154. fi
  155. done
  156. return $ret
  157. }
  158. status_ipset() {
  159. local ret=0
  160. for conf in "$IPSET_DIR"/*.conf; do
  161. [ -f "$conf" ] || continue
  162. name=$(get_set_name "$conf")
  163. [ -z "$name" ] && continue
  164. if "$IPSET" list "$name" &>/dev/null; then
  165. echo "$name loaded"
  166. else
  167. echo "$name NOT loaded"
  168. ret=1
  169. fi
  170. done
  171. return $ret
  172. }
  173. case "$1" in
  174. start) start_ipset ;;
  175. stop) stop_ipset ;;
  176. restart) stop_ipset; start_ipset ;;
  177. reload) stop_ipset; start_ipset ;;
  178. save) save_ipset ;;
  179. status) status_ipset ;;
  180. *)
  181. echo "Usage: $0 {start|stop|restart|reload|save|status}"
  182. exit 1
  183. esac
  184. exit $?