qzeleza / kvas

vpn и shadowsocks клиент для роутеров keenetic
Other
907 stars 55 forks source link

Расшаривание гостевых #199

Closed AltGrF13 closed 3 weeks ago

AltGrF13 commented 1 month ago

Собственно, проблемы сейчас:

  1. В 1.1.9-beta_6 DNS сейчас висит на нестандартном порту, и для его работы в #195 мы прероутим DNS-трафик на нужный порт. Чтобы закрыть уязвимость, делаем мы это строго для br0. Эти правила теперь нужно размножать для каждой сети из гостевых.

  2. В #185 был более-менее причёсан код по расшариванию гостевых для VPN. Но сами правила времени рассмотреть тогда не было, сейчас исправил.

    2.1. Правила для шаринга трафика в случае включенного и отключенного ускорения должны быть разными.

    2.2. Там та же уязвимость и нагрузка, что были с правилами DNS. Вся магия обхода строится через

        ip4tables PREROUTING -t "${table}" -m set --match-set "${IPSET_TABLE_NAME}" dst -j ${VPN_IPTABLES_CHAIN} &>/dev/null
        ip4tables OUTPUT     -t "${table}" -m set --match-set "${IPSET_TABLE_NAME}" dst -j ${VPN_IPTABLES_CHAIN} &>/dev/null

    Про использование OUTPUT я уже как-то писал, что обычно это правила, когда всё уже пропустили и пытаемся запрыгнуть на подножку уходящего поезда. Когда надо прям обязательно словить все сети. В своё время у SS его убрали, и всё хорошо работает и поныне. Правила PREROUTING сейчас вешаются на все сетевые интерфейсы. Все 30–50 сетевых интерфейсов сверяют трафик с таблицей IPSET_TABLE_NAME, ещё и дважды (возможно западение скорости). И что самое ужасное, это делают и внешние сетевые интерфейсы (скорее всего, у нас снова дыра в безопасности).

Для проверки подключим КВАС через VPN. Ни одну гостевую сеть для обхода я не добавил, но там везде обход работает. По правильному, правило с PREROUTING должно вешаться лишь на br0. И в 4 местах, затронутых в #185, соответствующие правила должны добавляться.

При этом в маркировке для VPN с отключенным ускорением мы правильно ограничены и PREROUTING, и интерфейсом.

Исправления обоих пунктов для VPN и SS пришлю отдельными сообщениями, сейчас они тестируются.

AltGrF13 commented 1 month ago

В случае основного VPN нужно

  1. Для локальной сети

1.1. роутинг DNS уже пропатчен в #195.

1.2. маркировка для обхода. opt/etc/ndm/ndm:ip4_firewall_vpn_mark вместо

        ip4tables PREROUTING -t "${table}" -m set --match-set "${IPSET_TABLE_NAME}" dst -j ${VPN_IPTABLES_CHAIN} &>/dev/null
        ip4tables OUTPUT     -t "${table}" -m set --match-set "${IPSET_TABLE_NAME}" dst -j ${VPN_IPTABLES_CHAIN} &>/dev/null

надо

        ip4tables PREROUTING -t "${table}" -i br0 -m set --match-set "${IPSET_TABLE_NAME}" dst -j "${VPN_IPTABLES_CHAIN}" &>/dev/null
  1. Включение роутинга DNS и маркировка для обхода в гостевых

2.1. При вызове из консоли

2.1.1. в случае IKEv2. opt/etc/ndm/ndm:ikev2_net_access_add

ikev2_net_access_add() {
    local ikev2_settings=$(curl -s "${LOCALHOST_IP}:79/rci/crypto/virtual-ip-server-ikev2")

    # вероятное изменение настроек роутера
    ikev2_setup "${ikev2_settings}"

    local net_pool=$(echo "${ikev2_settings}" | grep -F -- 'pool-start' | cut -d':' -f2 | sed 's/[\,\" ]//g;')
    ready "Добавление гостевого интерфейса \"VPN-сервер IKEv2\" [${net_pool}] завершено" && {
        if has_ssr_enable ; then
            ip4_add_selected_guest_to_ssr_network 'ikev2'
        else
            ip4_add_selected_guest_to_vpn_network 'ikev2'
        fi

        # сохранение в конфигурацию КВАС
        add_ikev2_net_to_config
    } && when_alert "УСПЕШНО" || when_bad "С ОШИБКОЙ"
}

Ну и к теме этой задачи не относится, но ikev2_setup сыпал warning'ами, исправил:

ikev2_setup() {
    ikev2_data="${1}"
    enable=${2:-true}

    ikev2_dns=$(get_router_ip)
    #TODO: вынести получение параметра в функцию
    dns_server=$(echo "${ikev2_data}" | grep -F -- 'dns-server' | cut -d':' -f2 | sed 's/[\,\" ]//g;')
    enable=$(echo "${ikev2_data}"     | grep -F    'enable'     | cut -d':' -f2 | sed 's/[\,\" ]//g;')
    nat=$(echo "${ikev2_data}"        | grep -F    'nat'        | cut -d':' -f2 | sed 's/[\,\" ]//g;')
    if [ "${dns_server}" != "${ikev2_dns}" ] || [ "${enable}" != true ] || [ "${nat}" != true ] ; then
        # Если DNS отличен от IP роутера, или интерфейс, или NAT отключены
        pool_size=$(echo "${ikev2_data}"   | grep -F -- 'pool-size'   | cut -d':' -f2 | sed 's/[\,\" ]//g;')
        pool_start=$(echo "${ikev2_data}"  | grep -F -- 'pool-start'  | cut -d':' -f2 | sed 's/[\,\" ]//g;')
        multi_login=$(echo "${ikev2_data}" | grep -F -- 'multi-login' | cut -d':' -f2 | sed 's/[\,\" ]//g;')
        sa_compat=$(echo "${ikev2_data}"   | grep -F -- 'sa-compat'   | cut -d':' -f2 | sed 's/[\,\" ]//g;')

        curl -s -d '{"enable": '"${enable}"', "nat": true, "dns-server": "'"${ikev2_dns}"'", "pool-size": "'"${pool_size}"'", "pool-start": "'"${pool_start}"'", "multi-login": "'"${multi_login}"'", "sa-compat": "'"${sa_compat}"'"}' \
         "${LOCALHOST_IP}:79/rci/crypto/virtual-ip-server-ikev2" &> /dev/null
        sleep 1
    fi
}

2.1.2. в случае именованных интерфейсов при вызове из консоли. bin/libs/vpn:bridge_access_add, правила напрямую не добавляются, это произойдёт по триггеру после перезапуска Dnsmasq. Правок нет.

2.2. Триггерное добавление — это в opt/etc/ndm/ndm цепочка вызовов

ip4_add_selected_guest_to_vpn_network() { local net_interface="${1}" if [ -z "${net_interface}" ] ; then error "[${FUNCNAME}] Не передан обязательный параметр — имя интерфейса" fi

if echo "${net_interface}" | grep -q ikev2 ; then
    net_interface=$(get_entware_ikev2_inface)

    # IKEv2 не имеет отдельного сетевого интерфейса, поэтому запишем и что добавляем, и используемый СИ
    local submessage="IKEv2 (${net_interface} "
    local net_pool=$(get_ikev2_net_pool)
    local iptables_filter="-s ${net_pool} "
else
    local submessage="${net_interface} ("
    local net_pool=$(get_guest_net "${net_interface}")
    local iptables_filter=''
fi
iptables_filter="${iptables_filter}-i ${net_interface}"

log_warning "Подключаем правила гостевого трафика ${submessage}${net_pool}) для VPN."
ip4_add_to_dns_routing "${iptables_filter}"
if fastnet_enabled ; then
    if ! iptables-save | grep -F 'PREROUTING' | grep -F -- "${iptables_filter}" | grep -F "${IPSET_TABLE_NAME}" | grep -Fq "${VPN_IPTABLES_CHAIN}" ; then
        # без echo дублирование пробелов (что даёт warning и проблему наличия)
        iptables -A PREROUTING -w -t mangle $(echo "${iptables_filter}") -m set --match-set "${IPSET_TABLE_NAME}" dst -j "${VPN_IPTABLES_CHAIN}"
    fi
else
    if [ -z "${net_pool}" ] ; then
        error "[${FUNCNAME}] От функции get_guest_net не получен обязательный результат — пул адресов"
    fi

    # случай основного VPN и отключенного ускорения требует дополнительной проверки
    # но именно таким код был изначально, т.е. он точно не "ухудшится"
    if ! iptables-save | grep -F 'POSTROUTING' | grep -F "${net_pool}" | grep -F "${net_interface}" | grep -Fq 'MASQUERADE' ; then
        iptables -A POSTROUTING -w -t nat -s "${net_pool}" -o "${net_interface}" -j MASQUERADE
    fi
fi

}


3. **Отключение роутинга DNS и маркировки для обхода в гостевых**

3.1. в `bin/libs/vpn`:`cmd_bridge_vpn_access_del` добавить строчку
    if echo "${guest_bridge_id}" | grep -iq ikev2 ; then

3.2. в bin/libs/vpn:bridge_vpn_access_del вместо

#                ip4_firewall_rm_selected_guest_net "${guest_bridge_id}"
                ip4_firewall_rm_vpn_selected_guest_net "${guest_bridge_id}"

надо

            if has_ssr_enable ; then
                ip4_firewall_rm_ssr_selected_guest_net "${guest_bridge_id}"
            else
                ip4_firewall_rm_vpn_selected_guest_net "${guest_bridge_id}"
            fi

3.3. opt/etc/ndm/ndm:ikev2_net_access_del

ikev2_net_access_del() {
    ready "Удаление интерфейса \"VPN-сервер IKEv2\" завершено" && {
        if has_ssr_enable ; then
            ip4_firewall_rm_ssr_selected_guest_net 'ikev2'
        else
            ip4_firewall_rm_vpn_selected_guest_net 'ikev2'
        fi
    } && when_alert "УСПЕШНО" || when_bad "С ОШИБКОЙ"
}

3.4. opt/etc/ndm/ndm:ip4_firewall_rm_vpn_selected_guest_net

# Отключение перехвата dns-запросов в dnsmasq
#Example input param:-s 192.168.3.0/24 -i eth3
#Example input param:-i sstp+
ip4_delete_from_dns_routing() {
    local iptables_filter="${1}"

    local router_ip=$(get_router_ip)
    for protocol in tcp udp ; do
        if ! ip4save | grep -F 'PREROUTING' | grep -F -- "${iptables_filter}" | grep -F "${protocol}" | grep -F 53 | grep -F 'DNAT' | grep -F "${router_ip}" | grep -Fq "${DNS_PORT}" ; then
            continue
        fi

        # без echo дублирование пробелов (что даёт warning и проблему наличия)
        iptables -D PREROUTING -w -t nat $(echo "${iptables_filter}") -p "${protocol}" --dport 53 -j DNAT --to-destination "${router_ip}":"${DNS_PORT}"
    done
}

ip4_firewall_rm_vpn_selected_guest_net() {
    local net_interface="${1}"
    if [ -z "${net_interface}" ] ; then
        error "[${FUNCNAME}] Не передан обязательный параметр — имя интерфейса"
    fi

    if echo "${net_interface}" | grep -q ikev2 ; then
        net_interface=$(get_entware_ikev2_inface)

        # IKEv2 не имеет отдельного сетевого интерфейса, поэтому запишем и что добавляем, и используемый СИ
        local submessage="IKEv2 (${net_interface} "
        local net_pool=$(get_ikev2_net_pool)
        local iptables_filter="-s ${net_pool} "
    else
        local submessage="${net_interface} ("
        local net_pool=$(get_guest_net "${net_interface}")
        local iptables_filter=''
    fi
    iptables_filter="${iptables_filter}-i ${net_interface}"

    log_warning "Отключаем правила гостевого трафика ${submessage}${net_pool}) для VPN."
    ip4_delete_from_dns_routing "${iptables_filter}"
    if fastnet_enabled ; then
        if iptables-save | grep -F 'PREROUTING' | grep -F -- "${iptables_filter}" | grep -F "${IPSET_TABLE_NAME}" | grep -Fq "${VPN_IPTABLES_CHAIN}" ; then
            # без echo дублирование пробелов (что даёт warning и проблему наличия)
            iptables -D PREROUTING -w -t mangle $(echo "${iptables_filter}") -m set --match-set "${IPSET_TABLE_NAME}" dst -j "${VPN_IPTABLES_CHAIN}"
        fi
    else
        if [ -z "${net_pool}" ] ; then
            error "[${FUNCNAME}] От функции get_guest_net не получен обязательный результат — пул адресов"
        fi

        # случай основного VPN и отключенного ускорения требует дополнительной проверки
        # но именно таким код был изначально, т.е. он точно не "ухудшится"
        if iptables-save | grep -F 'POSTROUTING' | grep -F "${net_pool}" | grep -F "${net_interface}" | grep -Fq 'MASQUERADE' ; then
            iptables -D POSTROUTING -w -t nat -s "${net_pool}" -o "${net_interface}" -j MASQUERADE
        fi
    fi
}

Массово пришлось, но наконец-то и код стал более-менее консистентным. При этом исправилась ещё пара мелочей.

AltGrF13 commented 1 month ago

Проведём тест (для связки VPN + ускорение)

  1. Роутер рестартим, обход в домашней сети работает, в правилах видим прокидку трафика и DNS (при этом только для локалки, никаких внешних интерфейсов): image

  2. Переходим на гостевую сеть устройством. Обход не работает. Через kvas vpn net add добавим обход гостевой, смотрим правила: image Появился и проброс DNS, и трафика. Обход на устройстве заработал.

  3. Через kvas vpn net del удалим обход гостевой, смотрим правила: image Всё подчистилось. Обход в гостевой, соответственно, работать перестал.

  4. Уходим тестируемым устройством на мобильную сеть, подключаемся к роутеру через IKEv2. Обход не работает. Накатываем патч #194, через kvas vpn net add добавим обход IKEv2, смотрим правила: image Появился и проброс DNS, и трафика. Обход на устройстве заработал.

  5. Через kvas vpn net del удалим обход IKEv2, смотрим правила: image Всё подчистилось. Обход в IKEv2, соответственно, работать перестал.

qzeleza commented 1 month ago

Большое благодарю за проделанную работу. Правки внесены в следующую бету. Выйдет на этой неделе.

AltGrF13 commented 1 month ago

Правки внесены в следующую бету. Выйдет на этой неделе.

Я сейчас дофикшиваю код для случая основного соединения ShadowSocks. Если не сложно, дождитесь ещё его)

qzeleza commented 1 month ago

Я сейчас дофикшиваю код для случая основного соединения ShadowSocks. Если не сложно, дождитесь ещё его)

Добро, жду.

AltGrF13 commented 4 weeks ago

В случае ShadowSocks

Буду придерживаться нумерации, как в VPN. Всё, что требуется для работы. Даже если пункты уже сделаны, иначе что-то можно упустить.

  1. Для локальной сети

1.1. роутинг DNS уже пропатчен в #195.

1.2. роутинг данных в opt/etc/ndm/ndm:ip4_firewall_set_ssr_rules уже ограничен локальным интерфейсом, изменений не требуется.

  1. Включение роутинга DNS и данных для обхода в гостевых

2.1. При вызове из консоли

2.1.1. в случае IKEv2. opt/etc/ndm/ndm:ikev2_net_access_add, уже исправлен в случае с VPN.

2.1.2. в случае именованных интерфейсов. Тоже никаких изменений по сравнению со случаем с VPN.

2.2. Триггерное добавление — это в opt/etc/ndm/ndm цепочка вызовов: ip4_add_guest_to_ssr_network ip4_add_selected_guest_to_ssr_network, её по аналогии с VPN нужно доработать:

ip4_add_selected_guest_to_ssr_network() {
    local net_interface="${1}"
    local net_pool=${2}

    [ -z "${net_interface}" ] && {
        error "[${FUNCNAME}] Отсутствует обязательный параметр — имя сетевого интерфейса"
        exit 1
    }

    if echo "${net_interface}" | grep -Fq 'ikev2' ; then
        net_interface=$(get_entware_ikev2_inface)
        [ -z "${net_pool}" ] && {
            net_pool=$(get_ikev2_net_pool)
        }

        # IKEv2 не имеет отдельного сетевого интерфейса, поэтому запишем и что добавляем, и используемый СИ
        local submessage="IKEv2 (${net_interface} "
        local iptables_filter="-s ${net_pool} "
    else
        [ -z "${net_pool}" ] && {
            # в случае SS+неIKEv2 используется лишь в лог-сообщении для единобразия
            net_pool=$(get_guest_net "${net_interface}")
        }

        local submessage="${net_interface} ("
        local iptables_filter=''
    fi
    iptables_filter="${iptables_filter}-i ${net_interface}"

    log_warning "Подключаем правила гостевого трафика ${submessage}${net_pool}) для ShadowSocks."

    ip4_add_to_dns_routing "${iptables_filter}"

    local ss_port=$(get_config_value SSR_DNS_PORT)
    for protocol in tcp udp ; do
        if ip4save | grep -F 'PREROUTING' | grep -F -- "${iptables_filter}" | grep -F "${protocol}" | grep -F "${IPSET_TABLE_NAME}" | grep -F 'REDIRECT' | grep -Fq "${ss_port}" ; then
            continue
        fi

        # без echo дублирование пробелов (что даёт warning и проблему наличия)
        iptables -A PREROUTING -w -t nat $(echo "${iptables_filter}") -p "${protocol}" -m set --match-set "${IPSET_TABLE_NAME}" dst -j REDIRECT --to-port ${ss_port}
    done
}
  1. Отключение роутинга DNS и данных для обхода в гостевых

3.1. в случае VPN уже были исправлены cmd_bridge_vpn_access_del, ikev2_net_access_del и bridge_vpn_access_del.

3.2. остаётся лишь базовая обёртка для этого случая opt/etc/ndm/ndm:ip4_firewall_rm_ssr_selected_guest_net

ip4_firewall_rm_ssr_selected_guest_net() {
    local net_interface="${1}"
    local net_pool=${2}

    [ -z "${net_interface}" ] && {
        error "[${FUNCNAME}] Отсутствует обязательный параметр — имя сетевого интерфейса"
        exit 1
    }

    if echo "${net_interface}" | grep -Fq 'ikev2' ; then
        net_interface=$(get_entware_ikev2_inface)
        [ -z "${net_pool}" ] && {
            net_pool=$(get_ikev2_net_pool)
        }

        # IKEv2 не имеет отдельного сетевого интерфейса, поэтому запишем и что добавляем, и используемый СИ
        local submessage="IKEv2 (${net_interface} "
        local iptables_filter="-s ${net_pool} "
    else
        [ -z "${net_pool}" ] && {
            # в случае SS+неIKEv2 используется лишь в лог-сообщении для единобразия
            net_pool=$(get_guest_net "${net_interface}")
        }

        local submessage="${net_interface} ("
        local iptables_filter=''
    fi
    iptables_filter="${iptables_filter}-i ${net_interface}"

    log_warning "Отключаем правила гостевого трафика ${submessage}${net_pool}) для ShadowSocks."

    ip4_delete_from_dns_routing "${iptables_filter}"

    local ss_port=$(get_config_value SSR_DNS_PORT)
    for protocol in tcp udp ; do
        if ! ip4save | grep -F 'PREROUTING' | grep -F -- "${iptables_filter}" | grep -F "${protocol}" | grep -F "${IPSET_TABLE_NAME}" | grep -F 'REDIRECT' | grep -Fq "${ss_port}" ; then
            continue
        fi

        # без echo дублирование пробелов (что даёт warning и проблему наличия)
        iptables -D PREROUTING -w -t nat $(echo "${iptables_filter}") -p "${protocol}" -m set --match-set "${IPSET_TABLE_NAME}" dst -j REDIRECT --to-port ${ss_port}
    done
}

К сожалению, не имею сейчас возможности проверить эти правки. Но код обеих обёрток достаточно единобразен с другими случаями.

qzeleza commented 3 weeks ago

Благодарю Вас. Правки внес в основной код 7 беты, появится до конца недели.