diff --git a/ipset-config b/ipset-config deleted file mode 100644 index 758f40b..0000000 --- a/ipset-config +++ /dev/null @@ -1,5 +0,0 @@ -# Save current ipsets on stop. -# Value: yes|no, default: no -# Saves all ipsets to /etc/sysconfig/ipset.d/ if service gets stopped -# (e.g. on system shutdown). -IPSET_SAVE_ON_STOP="no" diff --git a/ipset.service b/ipset.service index 65c3121..f4a0802 100644 --- a/ipset.service +++ b/ipset.service @@ -9,11 +9,10 @@ RemainAfterExit=yes ExecStart=/usr/libexec/ipset/ipset.start-stop start ExecStop=/usr/libexec/ipset/ipset.start-stop stop ExecReload=/usr/libexec/ipset/ipset.start-stop reload -# Save current ipset entries on stop. +# Save current ipset entries on stop/restart. # Value: yes|no, default: no -# Saves all ipsets to /etc/sysconfig/ipset if ipset gets stopped -Environment=IPSET_SAVE_ON_STOP=no -EnvironmentFile=-/etc/sysconfig/ipset-config +# Saves all ipsets to /etc/ipset/ipset if ipset gets stopped +Environment=IPSET_SAVE_ON_STOP=no IPSET_SAVE_ON_RESTART=no [Install] WantedBy=basic.target diff --git a/ipset.spec b/ipset.spec index 5766288..ee618cc 100644 --- a/ipset.spec +++ b/ipset.spec @@ -1,6 +1,6 @@ Name: ipset Version: 7.15 -Release: 5%{?dist} +Release: 5%{?dist}.1 Summary: Manage Linux IP sets License: GPLv2 @@ -8,7 +8,6 @@ URL: http://ipset.netfilter.org/ Source0: %{url}/%{name}-%{version}.tar.bz2 Source1: %{name}.service Source2: %{name}.start-stop -Source3: %{name}-config Patch0: 0001-Fix-typo-in-ipset-translate-man-page.patch Patch1: 0002-Fix-IPv6-sets-nftables-translation.patch Patch2: 0003-ipset-translate-allow-invoking-with-a-path-name.patch @@ -104,10 +103,6 @@ install -c -m 644 %{SOURCE1} %{buildroot}/%{_unitdir} install -d -m 755 %{buildroot}%{_libexecdir}/%{name} install -c -m 755 %{SOURCE2} %{buildroot}%{_libexecdir}/%{name} -# install ipset-config -install -d -m 755 %{buildroot}%{_sysconfdir}/sysconfig -install -c -m 600 %{SOURCE3} %{buildroot}%{_sysconfdir}/sysconfig/%{name}-config - # Create directory for configuration mkdir -p %{buildroot}%{_sysconfdir}/%{name} @@ -124,18 +119,6 @@ fi %post service %systemd_post %{name}.service -if [[ -f /etc/ipset/ipset ]] && [[ ! -f /etc/sysconfig/ipset ]]; then - mv /etc/ipset/ipset /etc/sysconfig/ipset - ln -s /etc/sysconfig/ipset /etc/ipset/ipset - echo "Warning: ipset save location has moved to /etc/sysconfig" -fi -[[ -f /etc/sysconfig/iptables-config ]] && . /etc/sysconfig/iptables-config -[[ -f /etc/sysconfig/ip6tables-config ]] && . /etc/sysconfig/ip6tables-config -if [[ ${IPTABLES_SAVE_ON_STOP} == yes ]] || \ - [[ ${IP6TABLES_SAVE_ON_STOP} == yes ]]; then - echo "Warning: ipset no longer saves automatically when iptables does" - echo " must enable explicitly in /etc/sysconfig/ipset-config" -fi %preun service if [[ $1 -eq 0 && -n $(lsmod | grep "^xt_set ") ]]; then @@ -168,12 +151,14 @@ fi %files service %{_unitdir}/%{name}.service %dir %{_libexecdir}/%{name} -%config(noreplace) %attr(0600,root,root) %{_sysconfdir}/sysconfig/ipset-config -%ghost %config(noreplace) %attr(0600,root,root) %{_sysconfdir}/sysconfig/ipset %attr(0755,root,root) %{_libexecdir}/%{name}/%{name}.start-stop +%dir %{_sysconfdir}/%{name} %changelog +* Thu Oct 06 2022 Nicolas Chauvet - 7.15-5.1 +- Revert initscript modification + * Tue Aug 23 2022 Nicolas Chauvet - 7.15-5 - Backport upstream patches - rhbz#2117654 ipset-translate does not work with IPv6 sets diff --git a/ipset.start-stop b/ipset.start-stop index 377beed..da21890 100644 --- a/ipset.start-stop +++ b/ipset.start-stop @@ -1,359 +1,209 @@ -#!/bin/sh +#!/bin/bash # # ipset Start and stop ipset firewall sets # -# config: /etc/sysconfig/ipset-config +# config: /etc/ipset/ipset +# -IPSET_BIN=/usr/sbin/ipset -IPSET_CONFIG=/etc/sysconfig/ipset-config -IPSET_DATA_COMPAT=/etc/sysconfig/ipset -IPSET_DATA_COMPAT_BACKUP=${IPSET_DATA_COMPAT}.save -IPSET_DATA_DIR=/etc/sysconfig/ipset.d -IPSET_DATA_DIR_BACKUP=${IPSET_DATA_DIR}.save -IPSET_DATA_SAVED_FLAG=${IPSET_DATA_DIR}/.saved -IPSET_LOCK=/run/ipset.lock -IPSET_RUN=/run/ipset.run -CLEAN_FILES="" +IPSET=ipset +IPSET_BIN=/usr/sbin/${IPSET} +IPSET_DATA=/etc/${IPSET}/${IPSET} -trap "rm -rf \${CLEAN_FILES}" EXIT +IPTABLES_CONFIG=/etc/sysconfig/iptables-config +IP6TABLES_CONFIG=${IPTABLES_CONFIG/iptables/ip6tables} -info() { - echo "ipset: ${*}" >&2 +TMP_FIFO=/tmp/${IPSET}.$$ + +if [[ ! -x ${IPSET_BIN} ]]; then + echo "${IPSET_BIN} does not exist." + exit 5 +fi + +CLEAN_FILES=TMP_FIFO +trap "rm -f \$CLEAN_FILES" EXIT + +# Default ipset configuration: +[[ -z $IPSET_SAVE_ON_STOP ]] && IPSET_SAVE_ON_STOP=no # Overridden by ip(6)tables IP(6)TABLES_SAVE_ON_STOP +[[ -z $IPSET_SAVE_ON_RESTART ]] && IPSET_SAVE_ON_RESTART=no # Overridden by ip(6)tables IP(6)TABLES_SAVE_ON_RESTART + +# Load iptables configuration(s) +[[ -f "$IPTABLES_CONFIG" ]] && . "$IPTABLES_CONFIG" +[[ -f "$IP6TABLES_CONFIG" ]] && . "$IP6TABLES_CONFIG" + +# It doesn't make sense to save iptables config and not our config +[[ ${IPTABLES_SAVE_ON_STOP} = yes || ${IP6TABLES_SAVE_ON_STOP} = yes ]] && IPSET_SAVE_ON_STOP=yes +[[ ${IPTABLES_SAVE_ON_RESTART} = yes || ${IP6TABLES_SAVE_ON_RESTART} = yes ]] && IPSET_SAVE_ON_RESTART=yes + +check_can_unload() { + # If the xt_set module is loaded and can't be unloaded, then iptables is + # using ipsets, so refuse to stop the service. + if [[ -n $(lsmod | grep "^xt_set ") ]]; then + rmmod xt_set 2>/dev/null + [[ $? -ne 0 ]] && echo Current iptables configuration requires ipsets && return 1 + fi + + return 0 } -warn() { - echo "<4>ipset: ${*}" >&2 +flush_n_delete() { + local ret=0 set + + # Flush sets + ${IPSET_BIN} flush + let ret+=$? + + # Delete ipset sets. If we don't do them individually, then none + # will be deleted unless they all can be. + for set in $(${IPSET_BIN} list -name); do + ${IPSET_BIN} destroy 2>/dev/null + [[ $? -ne 0 ]] && ret=1 + done + + return $ret } -err() { - echo "<3>ipset: ${*}" >&2 +start_clean() +{ + mkfifo -m go= "${TMP_FIFO}" + [[ $? -ne 0 ]] && return 1 + + # Get the lists of sets in current(old) config and new config + old_sets="$(${IPSET_BIN} list -name | sort -u)" + new_sets="$(grep ^create "${IPSET_DATA}" | cut -d " " -f 2 | sort -u)" + + # List of sets no longer wanted + drop_sets="$( printf "%s\n" "${old_sets}" > "${TMP_FIFO}" & + printf "%s\n" "${new_sets}" | comm -23 "${TMP_FIFO}" - + )" + + # Get rid of sets no longer needed + # Unfortunately -! doesn't work for destroy, so we have to do it a command at a time + for dset in $drop_sets; do + ipset destroy $dset 2>/dev/null + # If it won't go - ? in use by iptables, just clear it + [[ $? -ne 0 ]] && ipset flush $dset + done + + # Now delete the set members no longer required + ${IPSET_BIN} save | grep "^add " | sort >${TMP_FIFO} & + grep "^add " ${IPSET_DATA} | sort | comm -23 ${TMP_FIFO} - | sed -e "s/^add /del /" \ + | ${IPSET_BIN} restore -! + + # At last we can add the set members we haven't got + ipset restore -! <${IPSET_DATA} + + rm ${TMP_FIFO} + + return 0 } -[ -x ${IPSET_BIN} ] || { err "Cannot execute ${IPSET_BIN}"; exit 1; } +start() { + # Do not start if there is no config file. + [[ ! -f "$IPSET_DATA" ]] && echo "Loaded with no configuration" && return 0 -# Source ipset configuration -# shellcheck source=ipset-config -[ -f ${IPSET_CONFIG} ] && . ${IPSET_CONFIG} + # We can skip the first bit and do a simple load if + # there is no current ipset configuration + res=1 + if [[ -n $(${IPSET_BIN} list -name) ]]; then + # The following may fail for some bizarre reason + start_clean + res=$? -set -f + [[ $res -ne 0 ]] && echo "Some old configuration may remain" + fi -lock() { - CLEAN_FILES="${CLEAN_FILES} ${IPSET_LOCK}" - until mkdir ${IPSET_LOCK} 2>/dev/null; do :; done -} + # res -ne 0 => either start_clean failed, or we didn't need to run it + if [[ $res -ne 0 ]]; then + # This is the easy way to start but would leave any old + # entries still configured. Still, better than nothing - + # but fine if we had no config + ${IPSET_BIN} restore -! <${IPSET_DATA} + res=$? + fi -save() { - fail=0 + if [[ $res -ne 0 ]]; then + return 1 + fi - # Make backups of existing configuration first, if any - [ -d ${IPSET_DATA_DIR} ] && mv -Tf ${IPSET_DATA_DIR} ${IPSET_DATA_DIR_BACKUP} - [ -f ${IPSET_DATA_COMPAT} ] && mv -Tf ${IPSET_DATA_COMPAT} ${IPSET_DATA_COMPAT_BACKUP} - - rm -f ${IPSET_DATA_SAVED_FLAG} - - # Save each set in a separate file - mkdir -p ${IPSET_DATA_DIR} - chmod 0700 ${IPSET_DATA_DIR} - IFS=" -" - for set in $(${IPSET_BIN} list -n -t); do - # Empty name allowed, use ".set" as suffix. 'ipset save' doesn't - # quote set names with spaces: if we have a space in the name, - # work around this by quoting it ourselves in the output. - # shellcheck disable=SC2003 # No POSIX equivalent to expr index - if expr index "${set}" " " >/dev/null; then - :> "${IPSET_DATA_DIR}/${set}.set" - for line in $(${IPSET_BIN} save "${set}"); do - create=0 - echo "${line}" | grep -q "^create " && create=1 - if [ $create -eq 1 ]; then - line=${line#create *} - else - line=${line#add *} - fi - line=${line#${set} *} - set="$(echo "${set}" | sed 's/"/\\"/g')" - if [ $create -eq 1 ]; then - echo "create \"${set}\" ${line}" >> "${IPSET_DATA_DIR}/${set}.set" - else - echo "add \"${set}\" ${line}" >> "${IPSET_DATA_DIR}/${set}.set" - fi - done - else - ${IPSET_BIN} save "${set}" > "${IPSET_DATA_DIR}/${set}.set" || fail=1 - fi - [ -f "${IPSET_DATA_DIR}/${set}.set" ] && chmod 600 "${IPSET_DATA_DIR}/${set}.set" - [ $fail -eq 1 ] && err "Cannot save set ${set}" && unset IFS && return 1 - done - touch ${IPSET_DATA_SAVED_FLAG} || { unset IFS; return 1; } - unset IFS - - # Done: remove backups - rm -rf ${IPSET_DATA_DIR_BACKUP} - rm -rf ${IPSET_DATA_COMPAT_BACKUP} - - return 0 -} - -# Generate a grep regexp matching abbreviated command forms. E.g., for create: -# \(c\|cr\|cre\|crea\|creat\|create\) -cmd_short_expr() { - out= - cmd_len=1 - while [ "${cmd_len}" -le "${#1}" ]; do - [ -z "${out}" ] && out='\(' || out="${out}"'\|' - # shellcheck disable=SC2003 # No POSIX equivalent to expr substr - out="${out}$(expr substr "${1}" 1 "${cmd_len}")" - cmd_len=$((cmd_len + 1)) - done - echo "${out}"'\)' -} - -ipset_restore() { - file="${1}" - - retfile="$(mktemp -q /tmp/ipset.XXXXXX)" - CLEAN_FILES="${CLEAN_FILES} ${retfile}" - - # If restore fails due to invalid lines, remove them and retry - while ! restore_err="$( (${IPSET_BIN} -f "${file}" -! restore 2>&1; echo $? >"${retfile}") | head -n1; exit "$(cat "${retfile}")" )"; do - warn "${restore_err}" - case ${restore_err#*: } in - "No command specified"*) - line="$(grep -m1 -n "^${restore_err##* }" "${file}")" - line="${line%:*}" - ;; - "Missing second mandatory argument to command "*) - cmd="${restore_err##* }" - cmd_expr="$(cmd_short_expr "${cmd}")" - line="$(grep -n '^'"${cmd_expr}" "${file}" | grep -m1 -v '^[0-9]\+\:'"${cmd_expr}"'[[:blank:]]\+[^[:blank:]]\+[[:blank:]]\+[^[:blank:]]\+')" - line="${line%:*}" - ;; - "Missing mandatory argument to command "*) - cmd="${restore_err##* }" - cmd_expr="$(cmd_short_expr "${cmd}")" - line="$(grep -n '^'"${cmd_expr}" "${file}" | grep -m1 -v '^[0-9]\+\:'"${cmd_expr}"'[[:blank:]]\+[^[:blank:]]\+')" - line="${line%:*}" - ;; - "Command "*"is invalid in restore mode"*) - restore_err_cmd="${restore_err#*: }" - restore_err_cmd="${restore_err_cmd#*\`}" - restore_err_cmd="${restore_err_cmd%%\'*}" - cmd="${restore_err_cmd##* }" - cmd_expr="$(cmd_short_expr "${cmd}")" - line="$(grep -m1 -ne '^'"${cmd_expr}"'[[:blank:]]\+' -e '^'"${restore_err_cmd}"'$' "${file}")" - line="${line%:*}" - ;; - "Error in line "*) - line="${restore_err%: *}" - line="${line##* }" - ;; - *) - rm "${retfile}" - CLEAN_FILES="${CLEAN_FILES%* ${retfile}}" - return 1 - ;; - esac - - [ -z "${line}" ] && return 1 - - warn "Skipped invalid entry: $(sed "${line}q;d" "${file}")" - sed -i -e "${line}d" "${file}" - - [ -s "${file}" ] || return 1 - done - - rm "${retfile}" - CLEAN_FILES="${CLEAN_FILES%* ${retfile}}" -} - -load() { - if [ -f ${IPSET_DATA_SAVED_FLAG} ]; then - # If we have a cleanly saved directory with all sets, we can - # delete any left-overs and use it - rm -rf ${IPSET_DATA_DIR_BACKUP} - rm -f ${IPSET_DATA_COMPAT_BACKUP} - else - # If sets weren't cleanly saved, restore from backups - [ -d ${IPSET_DATA_DIR_BACKUP} ] && rm -rf ${IPSET_DATA_DIR} && mv -Tf ${IPSET_DATA_DIR_BACKUP} ${IPSET_DATA_DIR} - [ -f ${IPSET_DATA_COMPAT_BACKUP} ] && rm -f ${IPSET_DATA_COMPAT} && mv -Tf ${IPSET_DATA_COMPAT_BACKUP} ${IPSET_DATA_COMPAT} - fi - - if [ ! -d ${IPSET_DATA_DIR} ] && [ ! -f ${IPSET_DATA_COMPAT} ]; then - info "No existing configuration available, none loaded" - touch ${IPSET_RUN} - return - fi - - # Merge all sets into temporary file - merged="$(mktemp -q /tmp/ipset.XXXXXX)" - CLEAN_FILES="${CLEAN_FILES} ${merged}" - chmod 600 "${merged}" - set +f - if [ -d ${IPSET_DATA_DIR} ]; then - # Copy create commands from each saved set first, then the rest: - # list:set entries depend on other sets, so make sure they all - # get created first - for f in "${IPSET_DATA_DIR}"/*; do - [ "${f}" = "${IPSET_DATA_DIR}/*" ] && break - [ -f "${f}" ] || continue - grep '^c' "${f}" >> "${merged}" - done - for f in "${IPSET_DATA_DIR}"/*; do - [ "${f}" = "${IPSET_DATA_DIR}/*" ] && break - [ -f "${f}" ] || continue - grep -v '^c' "${f}" >> "${merged}" - done - fi - set -f - [ -f ${IPSET_DATA_COMPAT} ] && cat ${IPSET_DATA_COMPAT} >> "${merged}" - - # Drop sets that aren't in saved data, mark conflicts with existing sets - conflicts="" - IFS=" -" - for set in $(${IPSET_BIN} list -n -t); do - grep -q "^create ${set} " "${merged}" && conflicts="${conflicts}|${set}" && continue - - # We can't destroy the set if it's in use, flush it instead - if ! ${IPSET_BIN} destroy "${set}" 2>/dev/null; then - ${IPSET_BIN} flush "${set}" - fi - done - unset IFS - conflicts="${conflicts#|*}" - - # Common case: if we have no conflicts, just restore in one shot - if [ -z "${conflicts}" ]; then - if ! ipset_restore "${merged}"; then - err "Failed to restore configured sets" - exit 1 - fi - rm "${merged}" - CLEAN_FILES="${CLEAN_FILES%* ${merged}}" - touch ${IPSET_RUN} - return - fi - - # Find a salt for md5sum that makes names of saved sets unique - salt=0 - while true; do - unique=1 - IFS=" -" - for set in $(${IPSET_BIN} list -n -t); do - if grep -q "^create $(echo "${salt}${set}" | md5sum | head -c31) " "${merged}"; then - unique=0 - break - fi - done - unset IFS - [ ${unique} -eq 1 ] && break - salt=$((salt + 1)) - done - - # Add sets, mangling names for conflicting sets - mangled="$(mktemp -q /tmp/ipset.XXXXXX)" - CLEAN_FILES="${CLEAN_FILES} ${mangled}" - chmod 600 "${mangled}" - - cat "${merged}" > "${mangled}" - IFS='|' - for set in ${conflicts}; do - new_name=$(echo "${salt}${set}" | md5sum | head -c31) - echo "s/^(add|create) $set /\1 $new_name /" - done | sed -i -r -f - "${mangled}" - unset IFS - if ! ipset_restore "${mangled}"; then - err "Failed to restore configured sets" - exit 1 - fi - - rm "${mangled}" - CLEAN_FILES="${CLEAN_FILES%* ${mangled}}" - - # Swap and delete old sets - IFS='|' - for set in ${conflicts}; do - mangled="$(echo "${salt}${set}" | md5sum | head -c31)" - if ! ${IPSET_BIN} swap "${set}" "${mangled}" 2>/dev/null; then - # This fails if set types are different: try to destroy - # existing set - if ! ${IPSET_BIN} destroy "${set}" 2>/dev/null; then - # Conflicting set is in use, we can only warn - # and flush the existing set - err "Cannot load set \"${set}\", set with same name and conflicting type in use" - ${IPSET_BIN} flush "${set}" - ${IPSET_BIN} destroy "${mangled}" - else - ${IPSET_BIN} rename "${mangled}" "${set}" - fi - else - ${IPSET_BIN} destroy "${mangled}" - fi - done - unset IFS - - rm "${merged}" - CLEAN_FILES="${CLEAN_FILES%* ${merged}}" - touch ${IPSET_RUN} -} - -cleanup() { - ${IPSET_BIN} flush || err "Failed to flush sets" - - # Try to destroy all sets at once. This will fail if some are in use, - # destroy all the other ones in that case - ${IPSET_BIN} destroy 2>/dev/null && return - IFS=" -" - for set in $(${IPSET_BIN} list -n -t); do - if ! ${IPSET_BIN} destroy "${set}"; then - err "Failed to destroy set ${set}" - fi - done - unset IFS + return 0 } stop() { - [ -f ${IPSET_RUN} ] || { info "Not running"; return 0; } - [ "${IPSET_SAVE_ON_STOP}" = "yes" ] && { save || err "Failed to save sets"; } + # Nothing to stop if ip_set module is not loaded. + lsmod | grep -q "^ip_set " + [[ $? -ne 0 ]] && return 6 - # Nothing to stop if the ip_set module is not loaded - lsmod | grep -q "^ip_set " || { info "Not running"; rm ${IPSET_RUN}; return 0; } + flush_n_delete + [[ $? -ne 0 ]] && echo Warning: Not all sets were flushed/deleted - # If the xt_set module is in use, then iptables is using ipset, so - # refuse to stop the service - if mod="$(lsmod | grep ^xt_set)"; then - if [ "$(echo "${mod}" | tr -s ' ' | cut -d' ' -f3)" != "0" ]; then - err "Current iptables configuration requires ipset" && return 1 - fi - fi - - cleanup - - rm ${IPSET_RUN} - return 0 + return 0 } -lock +save() { + # Do not save if ip_set module is not loaded. + lsmod | grep -q "^ip_set " + [[ $? -ne 0 ]] && return 6 + + [[ -z $(${IPSET_BIN} list -name) ]] && return 0 + + ret=0 + TMP_FILE=$(/bin/mktemp -q /tmp/$IPSET.XXXXXX) \ + && CLEAN_FILES+=" $TMP_FILE" \ + && chmod 600 "$TMP_FILE" \ + && ${IPSET_BIN} save > $TMP_FILE 2>/dev/null \ + && [[ -s $TMP_FILE ]] \ + || ret=1 + + if [[ $ret -eq 0 ]]; then + # No need to do anything if the files are the same + if [[ ! -f $IPSET_DATA ]]; then + mv $TMP_FILE $IPSET_DATA && chmod 600 $IPSET_DATA || ret=1 + else + diff -q $TMP_FILE $IPSET_DATA >/dev/null + + if [[ $? -ne 0 ]]; then + if [[ -f $IPSET_DATA ]]; then + cp -f --preserve=timestamps $IPSET_DATA $IPSET_DATA.save \ + && chmod 600 $IPSET_DATA.save \ + || ret=1 + fi + if [[ $ret -eq 0 ]]; then + cp -f --preserve=timestamps $TMP_FILE $IPSET_DATA \ + && chmod 600 $IPSET_DATA \ + || ret=1 + fi + fi + fi + fi + + rm -f $TMP_FILE + return $ret +} + + case "$1" in -start) - load + start) + start + RETVAL=$? ;; -stop) + stop) + check_can_unload || exit 1 + [[ $IPSET_SAVE_ON_STOP = yes ]] && save stop + RETVAL=$? + [[ $RETVAL -eq 6 ]] && echo "${IPSET}: not running" && exit 0 ;; -reload) - cleanup - load + reload) + [[ $IPSET_SAVE_ON_RESTART = yes ]] && save + stop + RETVAL=$? + [[ $RETVAL -eq 6 ]] && echo "${IPSET}: not running" && exit 0 + start + RETVAL=$? ;; -save) - save - ;; -*) - info "Usage: $0 {start|stop|reload|save}" + *) + echo "Usage: $IPSET {start|stop|reload}" >&2 exit 1 esac -exit $? +exit $RETVAL