Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand Down Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@
]
},
"write": {
"ubus": {
"uci": [
"commit",
"apply",
"confirm"
]
},
"uci": [
"podkop"
]
Expand Down
3 changes: 3 additions & 0 deletions podkop/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions podkop/files/etc/config/podkop
Original file line number Diff line number Diff line change
Expand Up @@ -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 ''
Expand Down
16 changes: 16 additions & 0 deletions podkop/files/etc/hotplug.d/iface/99-podkop
Original file line number Diff line number Diff line change
@@ -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
63 changes: 62 additions & 1 deletion podkop/files/usr/bin/podkop
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -2216,6 +2265,14 @@ check_fakeip() {
# clash_api set_group_proxy <group_tag> <proxy_tag>
#######################################

_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
Expand All @@ -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
Expand Down
14 changes: 12 additions & 2 deletions podkop/files/usr/lib/sing_box_config_facade.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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")
;;
Expand Down Expand Up @@ -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"
;;
Expand Down
32 changes: 30 additions & 2 deletions podkop/files/usr/lib/sing_box_config_manager.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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" \
Expand Down Expand Up @@ -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" \
Expand All @@ -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",
Expand All @@ -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)
)]'
}

Expand Down Expand Up @@ -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" \
Expand Down Expand Up @@ -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"
Expand Down