diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js index c8fae381..7ffda6ec 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/main.js @@ -711,7 +711,7 @@ async function getDashboardSections() { }) ); const data = configSections.filter( - (section) => section.connection_type !== "block" && section[".type"] !== "settings" + (section) => section.connection_type !== "block" && section.connection_type !== "exclusion" && section[".type"] !== "settings" ).map((section) => { if (section.connection_type === "proxy") { if (section.proxy_config_type === "url") { @@ -4286,7 +4286,9 @@ function renderDiagnosticSystemInfoWidget() { value: version }; } - if (version !== `v${diagnosticsSystemInfo.podkop_latest_version}`) { + const normalizedVersion = version.replace(/^v/, ''); + const normalizedLatest = diagnosticsSystemInfo.podkop_latest_version.replace(/^v/, ''); + if (normalizedVersion !== normalizedLatest) { logger.debug( "[DIAGNOSTIC]", "diagnosticsSystemInfo", diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js index dc619bcb..e3f20e54 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/section.js @@ -7,6 +7,15 @@ function createSectionContent(section) { let o = section.option( + form.Flag, + "enabled", + _("Enabled"), + _("Enable or disable this section without deleting it"), + ); + o.default = "1"; + o.rmempty = false; + + o = section.option( form.ListValue, "connection_type", _("Connection Type"), diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js index 6c89ed34..8195f214 100644 --- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js +++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/settings.js @@ -311,7 +311,7 @@ function createSettingsContent(section) { for (const secName in sections) { const sec = sections[secName]; - if (sec[".type"] === "section") { + if (sec[".type"] === "section" && sec.connection_type !== "block" && sec.connection_type !== "exclusion") { this.keylist.push(secName); this.vallist.push(secName); } diff --git a/luci-app-podkop/root/usr/share/rpcd/acl.d/luci-app-podkop.json b/luci-app-podkop/root/usr/share/rpcd/acl.d/luci-app-podkop.json index 6d0eabcb..389f701a 100644 --- a/luci-app-podkop/root/usr/share/rpcd/acl.d/luci-app-podkop.json +++ b/luci-app-podkop/root/usr/share/rpcd/acl.d/luci-app-podkop.json @@ -20,6 +20,13 @@ ] }, "write": { + "ubus": { + "uci": [ + "commit", + "apply", + "confirm" + ] + }, "uci": [ "podkop" ] diff --git a/podkop/Makefile b/podkop/Makefile index 5cef0c1e..862124d3 100644 --- a/podkop/Makefile +++ b/podkop/Makefile @@ -58,6 +58,9 @@ define Package/podkop/install $(INSTALL_DIR) $(1)/usr/lib/podkop $(CP) ./files/usr/lib/* $(1)/usr/lib/podkop/ + $(INSTALL_DIR) $(1)/etc/hotplug.d/iface + $(INSTALL_BIN) ./files/etc/hotplug.d/iface/99-podkop $(1)/etc/hotplug.d/iface/99-podkop + sed -i -e 's/__COMPILED_VERSION_VARIABLE__/$(PKG_VERSION)/g' $(1)/usr/lib/podkop/constants.sh endef diff --git a/podkop/files/etc/config/podkop b/podkop/files/etc/config/podkop index 27003cfa..bb7936a6 100644 --- a/podkop/files/etc/config/podkop +++ b/podkop/files/etc/config/podkop @@ -22,6 +22,7 @@ config settings 'settings' #list routing_excluded_ips '192.168.1.3' config section 'main' + option enabled '1' option connection_type 'proxy' option proxy_config_type 'url' option proxy_string '' diff --git a/podkop/files/etc/hotplug.d/iface/99-podkop b/podkop/files/etc/hotplug.d/iface/99-podkop new file mode 100644 index 00000000..ada2d958 --- /dev/null +++ b/podkop/files/etc/hotplug.d/iface/99-podkop @@ -0,0 +1,16 @@ +#!/bin/sh + +# Restore podkop ip rules and routes after network restart (#190) +# When the network service restarts, kernel flushes ip rules and routes, +# but podkop is not notified to recreate them. + +[ "$ACTION" = "ifup" ] || exit 0 + +# Only act if podkop service is running +[ -f /var/run/podkop.pid ] || pgrep -f "/usr/bin/podkop" > /dev/null 2>&1 || exit 0 + +# Check if the fwmark rule is missing +if ! ip rule list | grep -q "fwmark 0x00100000/0x00100000 lookup podkop"; then + logger -t podkop "ip rules missing after interface $INTERFACE came up, restoring" + /etc/init.d/podkop reload +fi diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop index e26164a0..c5c3b9bd 100755 --- a/podkop/files/usr/bin/podkop +++ b/podkop/files/usr/bin/podkop @@ -83,8 +83,15 @@ check_requirements() { fi } +is_section_enabled() { + local enabled + config_get_bool enabled "$1" "enabled" 1 + [ "$enabled" -eq 1 ] +} + _check_outbound_section() { local section="$1" + is_section_enabled "$section" || return 0 local proxy_string interface outbound_json urltest_proxy_links config_get proxy_string "$section" "proxy_string" @@ -118,6 +125,17 @@ start_main() { br_netfilter_disable + # Wait for WAN connectivity before NTP sync (#195) + local wan_wait=0 + while [ $wan_wait -lt 30 ]; do + if ip route show default 2>/dev/null | grep -q .; then + break + fi + log "Waiting for WAN (default route)... ($wan_wait/30)" "debug" + sleep 1 + wan_wait=$((wan_wait + 1)) + done + # Sync time for DoH/DoT /usr/sbin/ntpd -q -p 194.190.168.1 -p 216.239.35.0 -p 216.239.35.4 -p 162.159.200.1 -p 162.159.200.123 @@ -233,6 +251,7 @@ validate_service() { process_validate_service() { local section="$1" + is_section_enabled "$section" || return 0 local community_lists config_get community_lists "$section" "community_lists" if [ -n "$community_lists" ]; then @@ -312,6 +331,9 @@ create_nft_rules() { nft add chain inet "$NFT_TABLE_NAME" mangle_output '{ type route hook output priority -150; policy accept; }' nft add chain inet "$NFT_TABLE_NAME" proxy '{ type filter hook prerouting priority -100; policy accept; }' + # Skip DNAT'd traffic (port-forwarded connections) to avoid breaking WAN->LAN forwarding (#291) + nft add rule inet "$NFT_TABLE_NAME" mangle ct status dnat return + nft add rule inet "$NFT_TABLE_NAME" mangle iifname "@$NFT_INTERFACE_SET_NAME" ip daddr "@$NFT_COMMON_SET_NAME" meta l4proto tcp meta mark set "$NFT_FAKEIP_MARK" counter nft add rule inet "$NFT_TABLE_NAME" mangle iifname "@$NFT_INTERFACE_SET_NAME" ip daddr "@$NFT_COMMON_SET_NAME" meta l4proto udp meta mark set "$NFT_FAKEIP_MARK" counter nft add rule inet "$NFT_TABLE_NAME" mangle iifname "@$NFT_INTERFACE_SET_NAME" ip daddr "$SB_FAKEIP_INET4_RANGE" meta l4proto tcp meta mark set "$NFT_FAKEIP_MARK" counter @@ -432,6 +454,7 @@ dnsmasq_restore() { } add_cron_job() { + is_section_enabled "$1" || return 0 ## Future: make a check so that it doesn't recreate many times local community_lists remote_domain_lists remote_subnet_lists update_interval config_get community_lists "$section" "community_lists" @@ -620,6 +643,7 @@ sing_box_configure_outbounds() { configure_outbound_handler() { local section="$1" + is_section_enabled "$section" || return 0 local connection_type config_get connection_type "$section" "connection_type" @@ -840,6 +864,7 @@ sing_box_configure_route() { include_source_ips_in_routing_handler() { local section="$1" + is_section_enabled "$section" || return 0 local fully_routed_ips rule_tag config_get fully_routed_ips "$section" "fully_routed_ips" @@ -910,6 +935,7 @@ exclude_source_ip_from_routing_handler() { configure_routing_for_section_lists() { local section="$1" + is_section_enabled "$section" || return 0 log "Configuring routing for '$section' section" if ! section_has_enabled_lists "$section"; then @@ -1213,6 +1239,7 @@ sing_box_additional_inbounds() { configure_section_mixed_proxy() { local section="$1" + is_section_enabled "$section" || return 0 local mixed_inbound_enabled mixed_proxy_port mixed_inbound_tag mixed_outbound_tag mixed_proxy_address config_get_bool mixed_inbound_enabled "$section" "mixed_proxy_enabled" 0 @@ -1270,6 +1297,7 @@ sing_box_config_check() { import_community_subnet_lists() { local section="$1" + is_section_enabled "$section" || return 0 local community_lists config_get community_lists "$section" "community_lists" if [ -n "$community_lists" ]; then @@ -1340,6 +1368,7 @@ import_community_service_subnet_list_handler() { import_domains_from_remote_domain_lists() { local section="$1" + is_section_enabled "$section" || return 0 local remote_domain_lists config_get remote_domain_lists "$section" "remote_domain_lists" if [ -n "$remote_domain_lists" ]; then @@ -1393,6 +1422,7 @@ import_domains_from_remote_plain_file() { import_subnets_from_remote_subnet_lists() { local section="$1" + is_section_enabled "$section" || return 0 local remote_subnet_lists config_get remote_subnet_lists "$section" "remote_subnet_lists" if [ -n "$remote_subnet_lists" ]; then @@ -1521,6 +1551,7 @@ get_download_detour_tag() { _determine_first_outbound_section() { local section="$1" + is_section_enabled "$section" || return 0 local connection_type config_get connection_type "$section" "connection_type" @@ -1593,6 +1624,24 @@ get_service_listen_address() { local interface="lan" network_get_ipaddr service_listen_address "$interface" + # Fallback: try interfaces from source_network_interfaces setting + if [ -z "$service_listen_address" ]; then + local source_interfaces + config_get source_interfaces "settings" "source_network_interfaces" + for iface in $source_interfaces; do + # Try to find the UCI network interface corresponding to this device + local net_iface + for net_iface in $(ubus list network.interface.* 2>/dev/null | sed 's/network.interface.//'); do + local dev + dev=$(ubus call network.interface."$net_iface" status 2>/dev/null | jsonfilter -e '@.device' 2>/dev/null) + if [ "$dev" = "$iface" ]; then + network_get_ipaddr service_listen_address "$net_iface" + [ -n "$service_listen_address" ] && break 2 + fi + done + done + fi + if [ -z "$service_listen_address" ]; then log "Failed to determine the listening IP address. Please open an issue to report this problem: https://github.com/itdoginfo/podkop/issues" "error" return 1 @@ -2216,6 +2265,14 @@ check_fakeip() { # clash_api set_group_proxy ####################################### +_get_first_urltest_testing_url() { + local section="$1" + [ -n "$TEST_URL" ] && return + local url + config_get url "$section" "urltest_testing_url" + [ -n "$url" ] && TEST_URL="$url" +} + clash_api() { local action="$1" local clash_api_controller_address CLASH_URL TEST_URL @@ -2224,7 +2281,11 @@ clash_api() { clash_api_controller_address="127.0.0.1" fi CLASH_URL="$clash_api_controller_address:$SB_CLASH_API_CONTROLLER_PORT" - TEST_URL="https://www.gstatic.com/generate_204" + + # Use user-configured URLTest URL if available, otherwise fall back to default (#250) + TEST_URL="" + config_foreach _get_first_urltest_testing_url "section" + [ -z "$TEST_URL" ] && TEST_URL="https://www.gstatic.com/generate_204" local enable_yacd_wan_access yacd_secret_key auth_header config_get_bool enable_yacd_wan_access "settings" "enable_yacd_wan_access" 0 diff --git a/podkop/files/usr/lib/sing_box_config_facade.sh b/podkop/files/usr/lib/sing_box_config_facade.sh index 6887e207..b26e6c59 100644 --- a/podkop/files/usr/lib/sing_box_config_facade.sh +++ b/podkop/files/usr/lib/sing_box_config_facade.sh @@ -94,15 +94,16 @@ sing_box_cf_add_proxy_outbound() { )" ;; vless) - local tag host port uuid flow packet_encoding + local tag host port uuid flow packet_encoding tcp_multi_path tag=$(get_outbound_tag_by_section "$section") host=$(url_get_host "$url") port=$(url_get_port "$url") uuid=$(url_get_userinfo "$url") flow=$(url_get_query_param "$url" "flow") packet_encoding=$(url_get_query_param "$url" "packetEncoding") + tcp_multi_path=$(url_get_query_param "$url" "tcpMultiPath") - config=$(sing_box_cm_add_vless_outbound "$config" "$tag" "$host" "$port" "$uuid" "$flow" "" "$packet_encoding") + config=$(sing_box_cm_add_vless_outbound "$config" "$tag" "$host" "$port" "$uuid" "$flow" "" "$packet_encoding" "$tcp_multi_path") config=$(_add_outbound_security "$config" "$tag" "$url") config=$(_add_outbound_transport "$config" "$tag" "$url") ;; @@ -256,6 +257,15 @@ _add_outbound_transport() { sing_box_cm_set_grpc_transport_for_outbound "$config" "$outbound_tag" "$grpc_service_name" ) ;; + httpupgrade) + local hu_host hu_path + hu_host=$(url_get_query_param "$url" "host") + hu_path=$(url_get_query_param "$url" "path") + + config=$( + sing_box_cm_set_httpupgrade_transport_for_outbound "$config" "$outbound_tag" "$hu_host" "$hu_path" + ) + ;; *) log "Unknown transport '$transport' detected." "error" ;; diff --git a/podkop/files/usr/lib/sing_box_config_manager.sh b/podkop/files/usr/lib/sing_box_config_manager.sh index 9a0af62c..140dafb0 100644 --- a/podkop/files/usr/lib/sing_box_config_manager.sh +++ b/podkop/files/usr/lib/sing_box_config_manager.sh @@ -537,7 +537,7 @@ sing_box_cm_add_shadowsocks_outbound() { local network="$7" local udp_over_tcp="$8" local plugin="$9" - local plugin_opts="${10}" + local plugin_opts="${10:-}" echo "$config" | jq \ --arg tag "$tag" \ @@ -598,6 +598,7 @@ sing_box_cm_add_vless_outbound() { local flow="$6" local network="$7" local packet_encoding="$8" + local tcp_multi_path="${9:-}" echo "$config" | jq \ --arg tag "$tag" \ @@ -607,6 +608,7 @@ sing_box_cm_add_vless_outbound() { --arg flow "$flow" \ --arg network "$network" \ --arg packet_encoding "$packet_encoding" \ + --arg tcp_multi_path "$tcp_multi_path" \ '.outbounds += [( { type: "vless", @@ -618,6 +620,7 @@ sing_box_cm_add_vless_outbound() { + (if $flow != "" then {flow: $flow} else {} end) + (if $network != "" then {network: $network} else {} end) + (if $packet_encoding != "" then {packet_encoding: $packet_encoding} else {} end) + + (if $tcp_multi_path == "true" or $tcp_multi_path == "1" then {tcp_multi_path: true} else {} end) )]' } @@ -690,7 +693,7 @@ sing_box_cm_add_hysteria2_outbound() { local obfuscator_password="$7" local upload_mbps="$8" local download_mbps="$9" - local network="${10}" + local network="${10:-}" echo "$config" | jq \ --arg tag "$tag" \ @@ -781,6 +784,31 @@ sing_box_cm_set_grpc_transport_for_outbound() { # Example: # CONFIG=$(sing_box_cm_set_ws_transport_for_outbound "$CONFIG" "vless-tls-ws-out" "/path" "example.com") ####################################### +sing_box_cm_set_httpupgrade_transport_for_outbound() { + local config="$1" + local tag="$2" + local host="$3" + local path="$4" + + echo "$config" | jq \ + --arg tag "$tag" \ + --arg host "$host" \ + --arg path "$path" \ + '.outbounds |= map( + if .tag == $tag then + . + { + transport: ( + { type: "httpupgrade" } + + (if $host != "" then {host: $host} else {} end) + + (if $path != "" then {path: $path} else {} end) + ) + } + else + . + end + )' +} + sing_box_cm_set_ws_transport_for_outbound() { local config="$1" local tag="$2"