From 64ff82f7d409d5f6b6f5e77706b723eb05665385 Mon Sep 17 00:00:00 2001 From: Pouria Rezaei Date: Fri, 19 Jun 2026 21:48:40 +0330 Subject: [PATCH] Adding dynamic source ip balancing. Signed-off-by: Pouria Rezaei --- assets/ip-list.txt | 1130 +++++++++++++++++ clients/android/app/build.gradle.kts | 16 + .../app/skirk/client/AndroidSkirkEngine.kt | 23 +- clients/android/settings.gradle.kts | 12 + clients/desktop/src-tauri/src/lib.rs | 311 ++++- clients/desktop/src/lib/api.ts | 8 +- cmd/skirk/main.go | 2 +- cmd/skirk/setup.go | 2 +- examples/skirk.config.example.json | 2 +- internal/skirk/config.go | 12 +- internal/skirk/config_test.go | 4 +- internal/skirk/google_ip.go | 314 +++++ internal/skirk/httpclient.go | 41 +- internal/skirk/httpclient_test.go | 4 +- tools/google_api_path_probe.py | 4 +- tools/google_ip.py | 181 +++ tools/google_substrate_probe.py | 4 +- tools/skirk_probe.py | 15 +- tools/storage_throughput_probe.py | 4 +- 19 files changed, 2021 insertions(+), 68 deletions(-) create mode 100644 assets/ip-list.txt create mode 100644 internal/skirk/google_ip.go create mode 100644 tools/google_ip.py diff --git a/assets/ip-list.txt b/assets/ip-list.txt new file mode 100644 index 0000000..13f05cb --- /dev/null +++ b/assets/ip-list.txt @@ -0,0 +1,1130 @@ +34.142.52.222 +147.189.239.80 +34.105.183.195 +35.246.56.4 +34.89.88.93 +34.13.60.28 +35.197.232.140 +195.10.199.166 +34.142.14.86 +46.16.164.222 +35.189.98.22 +35.246.104.26 +34.142.74.53 +34.89.34.110 +34.13.32.57 +195.245.230.98 +35.197.242.54 +35.246.52.155 +35.214.62.8 +34.39.39.195 +34.105.200.154 +34.89.1.62 +35.246.47.78 +195.10.199.31 +147.189.238.160 +35.197.203.230 +34.128.55.246 +46.235.152.8 +34.105.171.88 +34.39.16.117 +34.142.87.5 +34.153.132.164 +216.239.50.20 +34.157.43.218 +195.245.230.200 +34.89.79.79 +34.142.85.111 +35.246.49.19 +34.105.244.223 +195.10.199.14 +35.234.149.216 +34.157.169.129 +216.239.50.48 +216.239.50.90 +31.177.20.42 +46.16.166.64 +34.128.53.191 +34.13.59.8 +35.234.154.214 +35.214.17.250 +34.89.117.219 +35.214.4.67 +34.39.21.100 +35.214.13.2 +34.147.237.165 +35.189.120.254 +34.39.76.36 +34.157.36.49 +34.147.128.183 +34.89.76.254 +34.142.116.49 +34.39.70.169 +34.105.161.85 +216.239.50.137 +34.39.49.127 +208.127.199.246 +34.13.27.245 +34.13.48.187 +35.197.195.173 +35.234.159.75 +35.246.72.5 +34.89.20.74 +35.214.46.35 +46.16.164.135 +34.147.224.80 +34.105.222.35 +195.245.231.77 +34.89.120.3 +34.147.255.232 +35.246.12.51 +109.68.62.17 +34.89.21.210 +91.197.141.4 +223.29.219.193 +35.189.89.150 +35.197.254.162 +46.16.164.45 +85.158.142.33 +34.39.53.74 +165.85.161.78 +46.16.164.144 +35.189.107.238 +35.246.102.144 +195.10.199.70 +34.13.51.79 +194.187.56.100 +34.89.17.46 +34.89.123.160 +35.214.46.12 +34.142.30.225 +195.10.199.250 +46.235.152.54 +85.158.142.26 +34.147.153.202 +34.105.180.240 +35.246.127.223 +35.242.132.74 +35.246.73.216 +35.234.147.109 +195.10.199.28 +195.10.199.150 +35.246.70.57 +35.214.84.114 +34.39.31.224 +34.39.114.18 +35.214.84.40 +216.239.50.61 +75.125.242.198 +216.239.50.92 +35.214.10.61 +147.189.239.172 +35.246.80.228 +35.242.143.83 +35.189.120.104 +34.147.198.242 +34.89.29.206 +35.246.47.20 +35.242.20.87 +34.127.186.194 +34.13.41.228 +147.189.239.12 +34.89.17.86 +35.246.90.34 +35.214.113.78 +195.245.230.246 +31.177.21.128 +35.246.52.117 +195.10.199.64 +31.177.21.193 +8.228.34.6 +34.38.14.48 +34.89.37.170 +35.246.13.210 +34.142.10.18 +34.13.50.220 +34.105.245.48 +34.89.24.96 +31.177.21.34 +91.197.141.177 +34.142.82.76 +34.89.101.253 +35.214.126.69 +34.39.21.55 +34.89.88.104 +35.197.230.60 +35.214.39.58 +35.214.31.205 +35.214.45.237 +216.239.50.148 +34.142.19.96 +34.128.52.73 +46.16.164.24 +137.83.235.18 +195.10.199.159 +35.189.111.176 +195.10.199.186 +34.157.168.66 +31.177.21.98 +46.16.164.117 +8.228.36.207 +91.197.141.210 +35.246.63.171 +91.197.141.84 +147.189.239.162 +35.230.150.244 +34.39.98.234 +35.246.120.176 +34.142.72.147 +34.142.12.65 +137.83.234.224 +46.16.166.112 +35.220.22.144 +31.177.23.4 +194.187.58.100 +34.89.14.230 +34.39.4.114 +34.147.192.247 +34.105.221.241 +34.89.100.191 +34.105.229.217 +34.89.116.162 +35.214.71.173 +34.147.226.25 +34.105.180.21 +216.239.50.64 +35.189.82.196 +35.197.216.82 +91.197.141.150 +216.239.50.116 +34.13.17.19 +148.64.8.144 +195.10.199.144 +35.214.118.157 +137.83.234.70 +34.142.64.156 +35.246.98.83 +35.246.79.115 +208.127.196.104 +35.214.36.229 +85.158.142.42 +34.183.9.104 +35.246.74.136 +195.10.199.126 +137.83.235.141 +35.214.40.156 +34.142.16.221 +35.214.79.128 +34.89.10.5 +35.242.180.51 +35.214.97.21 +195.10.199.147 +91.197.141.242 +34.39.32.154 +195.245.230.42 +34.157.169.73 +34.147.143.194 +35.230.130.73 +35.242.158.104 +35.242.20.111 +34.89.109.195 +34.147.242.230 +198.151.157.77 +91.197.141.212 +46.16.166.128 +35.246.77.78 +34.142.121.139 +31.177.20.48 +195.245.230.82 +35.246.71.187 +35.234.129.226 +34.39.26.43 +35.214.36.122 +34.39.22.1 +31.177.21.42 +46.235.152.48 +35.246.83.21 +35.242.135.14 +35.214.96.180 +34.105.142.92 +195.245.231.25 +34.89.111.247 +34.89.108.168 +85.158.142.112 +35.197.240.34 +91.197.141.172 +35.214.97.175 +34.142.74.177 +46.235.152.38 +195.10.199.98 +34.89.67.113 +35.246.17.200 +35.246.62.174 +34.89.98.96 +195.245.231.28 +34.142.6.76 +34.105.246.23 +195.245.230.41 +195.10.199.140 +35.220.22.195 +34.142.8.49 +34.39.82.89 +34.105.236.14 +34.89.103.223 +34.89.120.48 +34.142.120.126 +137.83.235.146 +195.10.199.220 +148.64.8.137 +34.105.150.11 +46.16.164.82 +34.157.169.204 +34.39.105.79 +34.142.79.118 +34.105.216.115 +34.105.240.149 +35.220.23.70 +35.189.86.36 +195.10.199.232 +34.105.233.129 +34.142.32.112 +208.127.197.89 +34.147.145.226 +34.157.171.171 +34.147.162.82 +104.134.170.242 +35.246.89.16 +34.142.114.199 +35.197.216.206 +35.197.245.213 +35.246.83.210 +216.239.50.146 +34.147.233.148 +34.89.22.237 +34.39.34.104 +34.142.76.252 +35.189.89.162 +35.246.73.169 +35.242.184.58 +35.197.195.48 +147.189.239.32 +35.246.57.148 +91.197.141.189 +34.147.141.194 +34.105.208.191 +34.13.40.31 +34.89.65.95 +35.230.152.12 +31.177.21.16 +35.189.122.26 +34.142.103.229 +34.142.83.42 +34.142.37.229 +34.89.113.224 +34.39.60.117 +35.246.0.199 +35.242.145.227 +35.189.84.46 +34.142.108.122 +35.246.123.108 +34.89.47.181 +34.147.190.61 +34.89.70.86 +34.105.233.95 +34.39.78.125 +172.217.32.30 +216.239.50.128 +34.89.33.170 +35.234.159.40 +34.153.254.217 +34.89.104.166 +46.235.152.42 +195.10.199.54 +34.105.173.99 +35.214.62.56 +35.242.187.17 +91.197.141.142 +147.189.239.20 +195.10.199.227 +46.16.164.104 +195.10.199.212 +195.10.199.46 +34.105.154.228 +34.142.21.98 +34.147.144.148 +91.197.141.73 +104.134.170.250 +91.197.141.224 +34.38.14.34 +31.177.20.128 +35.246.28.31 +34.142.127.96 +35.197.239.33 +34.89.80.209 +34.89.24.200 +35.214.103.49 +35.197.227.162 +35.242.182.63 +34.89.88.144 +34.89.79.174 +8.228.55.203 +34.105.202.250 +34.128.52.234 +34.13.2.67 +35.242.130.107 +34.39.12.201 +35.242.130.116 +35.246.97.232 +216.239.50.96 +35.214.23.131 +34.142.74.168 +195.10.199.18 +34.105.226.250 +34.89.9.42 +34.157.168.83 +165.85.138.46 +34.89.67.128 +152.65.231.124 +35.246.100.154 +35.197.223.101 +34.157.37.222 +34.105.174.146 +34.157.36.63 +35.214.60.135 +35.242.174.122 +216.239.50.16 +35.197.192.19 +35.214.14.154 +34.105.166.29 +147.189.239.100 +195.245.230.105 +35.214.32.161 +35.234.157.225 +46.16.164.14 +34.39.96.75 +35.242.151.24 +35.214.91.111 +208.127.199.248 +35.189.108.98 +35.197.194.90 +34.157.41.128 +35.246.48.117 +34.105.155.125 +34.142.82.222 +34.147.243.218 +169.224.179.40 +85.158.142.41 +148.64.8.28 +35.242.167.93 +34.142.22.126 +34.147.155.73 +34.13.39.30 +31.177.22.6 +137.83.233.150 +35.197.233.186 +46.16.164.40 +34.105.158.206 +195.10.199.84 +34.39.109.81 +34.39.7.199 +35.234.130.51 +195.245.231.1 +35.246.26.50 +46.16.164.30 +147.189.239.144 +34.89.34.137 +35.234.141.48 +35.197.238.245 +34.89.28.50 +34.142.103.68 +147.189.239.72 +46.16.164.162 +35.214.32.173 +35.214.104.4 +34.105.213.90 +34.105.232.53 +34.147.150.93 +34.39.49.231 +34.142.94.224 +35.189.119.245 +35.189.127.234 +34.105.187.119 +34.142.97.254 +216.239.50.114 +31.177.20.17 +34.105.130.119 +34.105.210.221 +195.10.199.242 +34.147.169.110 +31.177.21.18 +35.197.208.250 +35.197.211.233 +34.157.168.183 +34.157.40.108 +34.39.20.138 +195.10.199.228 +34.142.1.219 +35.246.116.135 +35.246.93.151 +35.246.110.41 +195.10.199.68 +195.10.199.101 +34.105.208.72 +34.89.79.169 +104.134.141.250 +216.239.50.192 +35.214.117.234 +35.246.26.91 +172.217.32.65 +31.177.21.130 +34.39.69.160 +35.197.235.239 +35.214.51.191 +31.177.21.96 +35.220.21.119 +34.147.160.140 +46.16.164.76 +35.246.111.73 +34.39.78.89 +34.13.26.180 +34.142.44.17 +34.105.130.166 +144.125.29.61 +34.39.77.24 +34.157.41.250 +34.153.141.161 +34.127.186.23 +34.13.63.139 +34.147.210.62 +34.153.128.214 +195.10.199.238 +35.246.53.255 +35.214.48.73 +35.246.108.54 +34.147.217.243 +34.39.18.118 +34.39.31.232 +195.10.199.215 +216.239.50.232 +35.246.110.108 +34.89.26.146 +35.246.35.66 +195.10.199.88 +31.177.23.20 +35.214.37.27 +91.197.141.57 +35.189.122.148 +34.147.213.76 +195.10.199.44 +195.10.199.63 +147.189.239.104 +152.65.231.128 +35.242.20.42 +195.245.230.106 +35.189.103.216 +34.13.28.34 +34.105.240.74 +104.134.170.249 +34.157.36.130 +34.13.8.120 +34.39.80.56 +34.147.223.92 +172.217.32.48 +216.239.50.228 +35.246.10.105 +34.142.127.241 +35.214.38.174 +35.246.111.128 +35.214.122.61 +34.147.185.175 +34.105.203.162 +34.157.42.68 +91.197.141.202 +46.16.164.89 +195.10.199.252 +35.246.11.248 +85.158.142.18 +34.105.160.195 +46.16.164.239 +35.189.108.32 +35.214.27.31 +34.157.38.54 +35.214.50.124 +34.13.51.47 +34.157.42.221 +85.158.142.2 +35.246.23.53 +195.10.199.131 +216.239.50.102 +35.246.87.249 +35.214.62.139 +46.16.164.90 +34.142.27.189 +35.246.26.132 +34.142.60.227 +34.147.236.133 +34.142.108.31 +91.197.141.122 +147.189.239.36 +34.89.2.128 +35.234.142.7 +34.147.208.238 +34.89.63.156 +46.235.152.32 +147.189.239.10 +34.13.18.185 +34.89.103.82 +35.214.102.64 +34.13.45.165 +216.239.50.32 +34.142.40.156 +35.246.54.254 +35.234.150.33 +34.142.111.218 +195.245.230.33 +34.142.96.32 +35.214.78.246 +34.183.9.149 +216.239.50.226 +35.242.21.52 +34.147.189.175 +35.242.21.14 +34.105.134.118 +34.157.42.170 +147.189.239.166 +35.189.122.141 +195.245.230.66 +34.127.186.118 +35.189.105.13 +34.89.66.41 +91.197.141.37 +34.39.82.236 +35.214.18.112 +31.177.22.20 +34.157.38.29 +34.142.98.189 +35.230.148.146 +34.157.43.64 +147.189.239.132 +216.239.50.252 +34.142.56.142 +85.158.142.244 +35.197.238.46 +104.134.170.240 +216.239.50.104 +34.105.146.16 +34.147.144.253 +35.246.3.134 +34.147.148.249 +34.105.246.162 +35.246.27.131 +34.89.120.115 +34.142.60.163 +216.239.50.152 +34.39.22.176 +35.246.122.58 +91.197.141.246 +34.147.183.43 +85.158.142.216 +31.177.23.6 +31.177.21.41 +195.10.199.163 +34.89.10.103 +34.89.103.253 +148.64.8.31 +195.10.199.8 +35.230.128.59 +34.38.14.44 +35.197.240.30 +34.89.19.139 +223.29.216.94 +34.157.38.159 +91.197.141.153 +35.242.188.14 +34.147.134.39 +34.89.121.189 +34.89.74.125 +34.39.79.194 +35.246.44.186 +35.189.98.142 +35.234.130.207 +34.105.207.54 +34.105.211.57 +34.89.94.151 +46.16.164.95 +195.10.199.82 +46.235.152.2 +35.189.99.132 +34.105.250.204 +34.39.44.161 +35.246.86.196 +34.89.39.167 +34.13.22.174 +34.142.87.215 +185.51.228.128 +147.189.239.22 +35.214.65.107 +195.10.199.117 +34.105.182.221 +34.147.255.101 +34.105.219.33 +34.128.55.122 +35.214.118.165 +34.157.40.34 +34.89.82.86 +34.147.172.22 +195.10.199.81 +34.142.91.50 +35.197.200.104 +91.197.141.182 +35.197.245.44 +35.197.226.251 +34.142.36.171 +35.197.246.137 +35.230.131.68 +172.253.82.8 +34.142.22.133 +35.234.156.182 +35.246.95.33 +195.10.199.208 +34.105.179.135 +198.151.157.64 +34.13.37.200 +31.177.20.34 +34.157.168.158 +165.85.138.107 +34.157.37.57 +34.157.43.150 +85.158.142.128 +34.89.13.232 +35.214.74.211 +216.239.50.120 +34.147.224.87 +35.246.116.96 +34.89.103.197 +34.157.36.117 +137.83.234.72 +34.105.134.203 +195.245.231.26 +195.245.231.44 +34.153.168.109 +35.214.8.80 +147.189.239.86 +147.189.239.79 +34.89.67.215 +34.89.20.161 +34.105.219.185 +35.246.28.125 +35.234.128.172 +195.10.199.224 +8.228.45.121 +34.157.41.210 +34.39.8.87 +195.10.199.94 +91.197.141.165 +34.89.45.221 +34.105.223.100 +85.158.142.9 +34.147.184.63 +34.89.104.124 +31.177.22.22 +91.197.141.218 +34.89.110.186 +34.105.167.2 +35.234.145.223 +35.242.20.176 +35.214.44.112 +35.246.30.247 +35.246.10.52 +137.83.235.26 +34.142.72.41 +35.230.131.171 +35.242.170.19 +34.13.26.110 +46.235.152.34 +35.214.84.212 +31.177.23.18 +34.142.8.43 +35.242.179.140 +35.242.169.18 +195.245.231.20 +216.239.50.84 +35.214.32.222 +34.39.51.226 +91.197.141.234 +34.105.184.40 +35.242.162.58 +165.85.138.89 +195.10.199.50 +34.39.27.110 +35.197.193.74 +31.177.20.130 +35.214.62.116 +172.217.32.1 +34.105.253.163 +195.10.199.112 +34.39.125.107 +35.189.127.177 +35.230.130.29 +35.189.116.215 +91.197.141.127 +35.246.103.24 +34.157.168.255 +34.39.106.23 +172.217.32.96 +31.177.20.192 +35.189.67.112 +34.128.52.16 +34.105.245.20 +35.214.96.86 +34.89.75.50 +34.89.109.2 +195.10.199.188 +172.217.32.16 +91.197.141.136 +208.127.196.219 +31.177.20.64 +34.157.42.243 +35.242.184.127 +35.234.130.131 +34.89.27.175 +91.197.141.166 +195.245.231.41 +35.214.24.162 +195.245.230.65 +35.246.38.156 +34.32.166.1 +35.234.132.75 +195.10.199.108 +195.10.199.180 +35.234.156.26 +195.245.230.74 +147.189.239.160 +34.142.73.212 +195.245.230.1 +35.214.93.90 +35.189.113.188 +195.10.199.172 +195.10.199.196 +195.245.230.9 +35.234.157.73 +35.197.214.106 +137.83.234.125 +34.142.21.115 +34.89.54.216 +35.214.15.125 +35.214.21.147 +34.39.30.108 +35.234.157.171 +195.245.231.18 +34.142.82.19 +34.13.63.244 +34.147.255.60 +35.230.156.252 +165.85.138.150 +35.230.136.108 +195.10.199.53 +35.246.12.120 +34.39.21.59 +195.245.231.72 +195.245.230.17 +35.242.183.255 +35.246.119.32 +195.10.199.16 +216.239.50.158 +34.147.246.224 +144.125.103.5 +34.39.41.173 +34.39.8.152 +153.72.9.93 +35.246.85.41 +35.234.157.210 +35.214.84.183 +35.189.93.62 +35.197.217.211 +34.147.219.172 +35.246.5.52 +172.217.32.32 +35.242.152.68 +35.189.99.137 +147.189.239.176 +35.214.7.131 +31.177.21.65 +34.13.60.118 +34.105.207.40 +195.245.230.26 +195.245.231.9 +172.253.82.18 +34.39.28.151 +91.197.141.104 +35.234.135.151 +195.10.199.58 +34.105.229.16 +35.189.124.62 +34.153.146.151 +34.142.74.122 +34.39.62.16 +35.246.22.62 +34.105.205.132 +34.89.37.6 +34.142.27.94 +195.10.199.156 +46.16.164.19 +35.242.171.190 +216.239.50.190 +148.64.8.146 +216.239.50.144 +35.214.55.37 +34.89.89.21 +34.147.218.206 +85.158.142.10 +185.235.238.128 +195.10.199.104 +85.158.142.208 +172.217.32.28 +85.158.142.34 +147.189.238.192 +216.239.50.251 +34.142.22.40 +34.89.122.134 +172.217.32.24 +35.214.100.207 +31.177.20.97 +195.10.199.183 +34.39.79.36 +46.16.164.63 +195.10.199.24 +208.127.123.241 +34.39.27.176 +35.214.66.240 +35.197.204.251 +34.142.114.162 +34.147.238.90 +35.230.152.152 +35.214.21.224 +195.10.199.122 +34.147.194.121 +34.105.207.126 +85.158.142.1 +35.214.104.252 +34.147.240.227 +35.214.45.226 +35.189.74.48 +46.16.164.111 +34.13.15.169 +34.105.158.183 +34.157.171.48 +195.10.199.136 +34.39.126.52 +34.89.57.69 +34.89.116.51 +34.142.114.43 +35.197.223.37 +34.89.52.66 +35.214.108.216 +35.230.145.124 +195.245.231.36 +34.142.89.171 +147.189.239.40 +34.147.149.248 +46.16.166.120 +35.214.87.231 +147.189.239.224 +34.184.9.201 +31.177.20.33 +172.217.32.88 +34.184.53.110 +195.245.230.34 +137.83.233.98 +35.230.145.247 +34.147.197.41 +34.147.169.70 +35.197.202.137 +46.16.164.203 +195.10.199.60 +34.89.27.2 +35.246.25.50 +31.177.20.194 +35.214.71.161 +46.16.164.42 +35.189.121.13 +34.105.171.171 +35.189.114.86 +216.239.50.240 +35.242.150.189 +85.158.142.136 +35.246.85.139 +172.217.32.92 +34.142.82.80 +34.105.177.160 +34.157.171.247 +34.89.90.7 +34.39.82.181 +35.242.153.23 +35.246.44.91 +34.89.27.93 +34.142.72.73 +137.83.234.158 +34.105.172.47 +34.89.51.46 +35.242.147.36 +34.105.173.91 +195.245.231.33 +34.147.194.241 +34.147.245.76 +195.10.199.249 +195.245.230.97 +195.245.231.66 +46.16.166.116 +35.242.191.114 +195.245.230.90 +147.189.239.168 +35.197.244.123 +216.239.50.176 +34.142.0.132 +34.105.236.37 +34.39.87.226 +137.83.233.177 +195.10.199.92 +34.89.76.214 +8.228.56.192 +35.197.221.39 +34.39.47.101 +35.246.64.244 +195.245.231.34 +35.197.221.185 +34.89.125.200 +172.217.32.2 +195.10.199.56 +34.89.85.126 +46.235.152.16 +195.10.199.118 +34.105.158.164 +195.10.199.148 +34.39.28.166 +35.214.100.29 +91.197.141.28 +34.142.57.149 +31.177.20.98 +35.230.128.247 +35.246.106.11 +35.242.168.92 +34.105.245.168 +34.147.160.42 +35.246.0.253 +34.39.83.186 +35.189.127.54 +35.189.120.116 +35.197.231.186 +137.83.234.157 +147.189.239.1 +35.189.116.84 +46.16.164.58 +35.214.122.143 +46.16.166.160 +195.10.199.41 +147.189.238.140 +34.142.33.201 +147.189.239.64 +34.105.242.204 +35.246.105.128 +35.197.195.202 +208.127.192.6 +195.10.199.223 +35.197.238.67 +172.217.32.66 +195.10.199.236 +35.246.40.213 +34.39.97.113 +35.214.112.116 +195.245.231.42 +34.105.190.187 +195.245.231.12 +34.157.37.235 +195.10.199.128 +34.105.175.88 +34.142.32.66 +35.197.210.12 +195.245.231.10 +34.142.92.53 +31.177.22.2 +34.105.170.142 +34.39.9.153 +35.189.126.222 +195.245.230.18 +216.239.50.138 +34.142.122.152 +104.134.141.240 +35.214.108.66 +198.151.157.68 +34.39.42.47 +195.10.199.143 +91.197.141.71 +34.147.200.235 +34.142.2.103 +172.217.32.125 +147.189.239.192 +34.142.93.183 +35.214.62.197 +35.197.236.147 +35.246.90.81 +35.246.102.248 +34.39.46.10 +195.10.199.135 +46.16.164.32 +172.217.32.4 +91.197.141.154 +34.105.212.105 +34.142.107.127 +34.142.50.151 +34.105.184.203 +34.183.9.52 +195.10.199.12 +34.39.88.6 +35.214.90.153 +195.245.230.25 +147.189.239.146 +35.242.159.183 +85.158.142.152 +35.242.190.204 +34.39.110.248 +34.39.54.19 +35.197.194.183 +35.246.41.147 +34.89.40.150 +46.16.166.115 +195.245.231.251 +34.89.74.165 +35.214.78.217 +195.245.231.4 +85.158.142.218 +216.239.50.248 +34.147.192.59 +35.246.116.141 +172.217.32.112 +35.197.240.45 +35.246.31.209 +35.230.143.235 +147.189.239.164 +91.197.141.146 +34.13.8.49 +34.105.142.75 +34.105.213.101 +137.83.234.21 +195.10.199.184 +34.147.156.30 +31.177.20.41 +31.177.22.4 +35.197.210.26 +147.189.239.220 +34.184.53.243 +172.217.32.80 +34.89.17.234 +85.158.142.25 +35.230.132.16 +35.214.119.167 +34.39.29.214 +91.197.141.250 +34.147.147.102 +195.10.199.72 +34.39.32.69 diff --git a/clients/android/app/build.gradle.kts b/clients/android/app/build.gradle.kts index 5b2d52f..ecee993 100644 --- a/clients/android/app/build.gradle.kts +++ b/clients/android/app/build.gradle.kts @@ -6,6 +6,7 @@ plugins { val repoRoot = layout.projectDirectory.dir("../../..") val generatedSkirkJniLibs = layout.buildDirectory.dir("generated/skirk-go/jniLibs") +val generatedSkirkAssets = layout.buildDirectory.dir("generated/skirk-assets") val generatedHevJniLibs = layout.buildDirectory.dir("generated/hev-tun2socks/jniLibs") val hevSourceDir = repoRoot.dir("third_party/hev-socks5-tunnel") val skirkAppVersion = providers.gradleProperty("skirk.version").orElse("0.1.55").get() @@ -78,6 +79,19 @@ val buildSkirkAndroidSidecar = tasks.register("buildSkirkAndroidSidecar") { } } +val copySkirkAndroidAssets = tasks.register("copySkirkAndroidAssets") { + group = "build" + description = "Copy shared Skirk assets into the Android APK." + inputs.file(repoRoot.file("assets/ip-list.txt")) + outputs.dir(generatedSkirkAssets) + + doLast { + val outputDir = generatedSkirkAssets.get().asFile + outputDir.mkdirs() + repoRoot.file("assets/ip-list.txt").asFile.copyTo(outputDir.resolve("ip-list.txt"), overwrite = true) + } +} + fun androidSdkRoot(): File { val explicit = providers.gradleProperty("android.sdk.path").orNull val env = System.getenv("ANDROID_HOME") ?: System.getenv("ANDROID_SDK_ROOT") @@ -199,6 +213,7 @@ android { getByName("main") { jniLibs.srcDir(generatedSkirkJniLibs) jniLibs.srcDir(generatedHevJniLibs) + assets.srcDir(generatedSkirkAssets) } } @@ -212,6 +227,7 @@ android { tasks.named("preBuild") { dependsOn(buildSkirkAndroidSidecar) dependsOn(buildHevTun2socks) + dependsOn(copySkirkAndroidAssets) } dependencies { diff --git a/clients/android/app/src/main/java/app/skirk/client/AndroidSkirkEngine.kt b/clients/android/app/src/main/java/app/skirk/client/AndroidSkirkEngine.kt index be1734e..d8dfdd4 100644 --- a/clients/android/app/src/main/java/app/skirk/client/AndroidSkirkEngine.kt +++ b/clients/android/app/src/main/java/app/skirk/client/AndroidSkirkEngine.kt @@ -28,6 +28,7 @@ class AndroidSkirkEngine( waitForListenPortRelease(profile) val configFile = writeRuntimeConfig(profile) + val googleIpListFile = prepareGoogleIpListFile() val engine = File(context.applicationInfo.nativeLibraryDir, ENGINE_NAME) check(engine.exists()) { "Skirk engine was not packaged at ${engine.absolutePath}" } @@ -40,12 +41,15 @@ class AndroidSkirkEngine( Log.i(TAG, "Starting ${engine.absolutePath} on SOCKS ${profile.socksAddress} HTTP $httpListen") appendLogLine( logFile, - "android starting mode=${profile.connectionMode} socks=${profile.socksAddress} http=$httpListen", + "android starting mode=${profile.connectionMode} socks=${profile.socksAddress} http=$httpListen googleIpList=${googleIpListFile.absolutePath}", ) - process = ProcessBuilder(buildProcessArgs(engine, configFile, profile, metricsFile)) + val processBuilder = ProcessBuilder(buildProcessArgs(engine, configFile, profile, metricsFile)) .directory(context.filesDir) .redirectErrorStream(true) .redirectOutput(ProcessBuilder.Redirect.appendTo(logFile)) + processBuilder.environment()["SKIRK_GOOGLE_IP_LIST"] = googleIpListFile.absolutePath + processBuilder.environment()["SKIRK_CACHED_LIST"] = googleIpListFile.absolutePath + process = processBuilder .start() .also { child -> watchProcessExit(child, logFile) @@ -237,6 +241,21 @@ class AndroidSkirkEngine( private fun readinessHost(host: String): String = if (host == "0.0.0.0") "127.0.0.1" else host + private fun prepareGoogleIpListFile(): File { + val googleIpDir = File(context.filesDir, "google-ip").apply { mkdirs() } + val target = File(googleIpDir, "ip-list.txt") + runCatching { + context.assets.open("ip-list.txt").use { input -> + target.outputStream().use { output -> + input.copyTo(output) + } + } + }.getOrElse { error -> + throw IllegalStateException("Skirk Android assets did not include ip-list.txt", error) + } + return target + } + private fun writeRuntimeConfig(profile: ClientProfile): File { val configsDir = File(context.filesDir, "configs").apply { mkdirs() } val suffix = if (profile.rawConfig.trim().startsWith("skirk:")) "skirk" else "json" diff --git a/clients/android/settings.gradle.kts b/clients/android/settings.gradle.kts index 9a0d50c..eca514f 100644 --- a/clients/android/settings.gradle.kts +++ b/clients/android/settings.gradle.kts @@ -16,3 +16,15 @@ dependencyResolutionManagement { rootProject.name = "SkirkAndroid" include(":app") + +val sdkPath = providers.gradleProperty("android.sdk.path").orNull + ?: System.getenv("ANDROID_SDK_ROOT") + ?: System.getenv("ANDROID_HOME") +if (!sdkPath.isNullOrBlank()) { + val localPropertiesFile = rootDir.resolve("local.properties") + if (!localPropertiesFile.exists()) { + localPropertiesFile.writeText(""" + sdk.dir=$sdkPath + """.trimIndent() + "\n") + } +} diff --git a/clients/desktop/src-tauri/src/lib.rs b/clients/desktop/src-tauri/src/lib.rs index 527a7e3..71aaefd 100644 --- a/clients/desktop/src-tauri/src/lib.rs +++ b/clients/desktop/src-tauri/src/lib.rs @@ -9,7 +9,8 @@ use std::{ net::{IpAddr, TcpListener, TcpStream}, path::{Path, PathBuf}, process::{Child, Command, Stdio}, - sync::Mutex, + sync::{Mutex, OnceLock}, + sync::atomic::{AtomicUsize, Ordering}, time::{Duration, Instant}, }; use tauri::{Manager, State}; @@ -246,7 +247,7 @@ fn default_http_port() -> u16 { } fn default_google_ip() -> String { - "216.239.38.120".into() + cache_list_path() } fn default_performance_settings() -> ClientPerformanceSettings { @@ -446,8 +447,8 @@ impl DesktopRuntime { let google_ip = parsed .pointer("/route/google_ip") .and_then(Value::as_str) - .unwrap_or(""); - let google_ip = canonical_google_ip(google_ip)?; + .map(normalize_ip_set) + .unwrap_or_else(default_google_ip); let drive_space = parsed .pointer("/drive/space") .and_then(Value::as_str) @@ -655,7 +656,7 @@ impl DesktopRuntime { } let (socks_address, http_address) = listener_addresses_for_mode(&profile, &mode); let route_mode = "google_front_pinned"; - let google_ip = canonical_google_ip(&profile.google_ip)?; + let google_ip = normalize_ip_set(&profile.google_ip); ensure_port_free(&socks_address)?; ensure_port_free(&http_address)?; let skirk = self.resolve_sidecar()?; @@ -845,7 +846,7 @@ impl DesktopRuntime { let tunnel = self.resolve_tunnel_sidecar()?; let log_path = tunnel_log_path(&self.paths); let config_path = tunnel_config_path(&self.paths); - let config = tunnel_config(socks_port, sidecar_process_path, google_ip); + let config = tunnel_config(socks_port, sidecar_process_path, google_ip)?; fs::write( &config_path, serde_json::to_vec_pretty(&config) @@ -1512,25 +1513,205 @@ fn normalize_windows_process_path(path: &str) -> String { path.strip_prefix(r"\\?\").unwrap_or(path).to_string() } -fn parse_google_ip(value: &str) -> Result { + +#[allow(non_upper_case_globals)] +static cache_list: OnceLock> = OnceLock::new(); +static cache_list_rotation: AtomicUsize = AtomicUsize::new(0); + +#[derive(Debug, Clone)] +struct CachedListState { + path: String, + entries: Vec, + loaded_path: String, +} + +fn init_cache_list() -> String { + std::env::var("SKIRK_GOOGLE_IP_LIST").or_else(|_| std::env::var("SKIRK_CACHED_LIST")) + .ok() + .map(|value| value.trim().to_string()) + .filter(|value| !value.is_empty()) + .unwrap_or_else(|| "assets/ip-list.txt".into()) +} + +impl CachedListState { + fn new() -> Self { + Self { + path: init_cache_list(), + entries: Vec::new(), + loaded_path: String::new(), + } + } +} + +fn cache_list_state() -> &'static Mutex { + cache_list.get_or_init(|| Mutex::new(CachedListState::new())) +} + +fn cache_list_path() -> String { + cache_list_state().lock().unwrap().path.clone() +} + +fn set_cache_list_path(spec: &str) { + let mut state = cache_list_state().lock().unwrap(); + let path = spec.trim(); + let path = if path.is_empty() { + init_cache_list() + } else { + path.to_string() + }; + if state.path != path { + state.path = path; + state.entries.clear(); + state.loaded_path.clear(); + } +} + +fn normalize_ip_set(value: &str) -> String { let value = value.trim(); - let value = if value.is_empty() { + if value.is_empty() { default_google_ip() } else { value.to_string() - }; - value - .parse::() - .map_err(|_| "profile route.google_ip must be an IP address".to_string()) + } +} + +fn parse_google_ip(value: &str) -> Result { + resolve_ip_list(value, 1)? + .into_iter() + .next() + .ok_or_else(|| "Google edge IP did not produce any IP".to_string()) +} + +fn resolve_ip_list(value: &str, limit: usize) -> Result, String> { + let value = normalize_ip_set(value); + if limit == 0 { + return Err("google IP list limit must be greater than zero".into()); + } + if let Ok(ip) = value.parse::() { + return Ok(vec![ip]); + } + let list_path = resolve_ip_list_path(&value) + .ok_or_else(|| format!("could not read Google IP list `{value}`"))?; + + { + let cache = cache_list_state().lock().unwrap(); + if cache.loaded_path == list_path.to_string_lossy().to_string() && !cache.entries.is_empty() { + return Ok(cache.entries.iter().copied().take(limit).collect()); + } + } + + let data = fs::read_to_string(&list_path).map_err(|err| { + format!( + "could not read Google IP list `{}`: {err}", + list_path.display() + ) + })?; + let mut ips = Vec::new(); + let mut seen = BTreeSet::new(); + for raw_line in data.lines() { + let mut line = raw_line.trim(); + if line.is_empty() || line.starts_with('#') { + continue; + } + if let Some(idx) = line.find('#') { + line = line[..idx].trim(); + if line.is_empty() { + continue; + } + } + let mut candidates: Vec<&str> = line + .split(|ch: char| ch.is_whitespace() || ch == ',' || ch == ';') + .collect(); + candidates.push(line); + for candidate in candidates { + let candidate = candidate.trim().trim_matches(|c| c == '"' || c == '\''); + if candidate.is_empty() { + continue; + } + let Ok(ip) = candidate.parse::() else { + continue; + }; + if seen.insert(ip) { + ips.push(ip); + } + } + } + if ips.is_empty() { + return Err(format!( + "Google IP list `{}` did not contain a valid IP address", + list_path.display() + )); + } + + let ordered = ips_by_latency(ips); + { + let mut cache = cache_list_state().lock().unwrap(); + cache.path = value.clone(); + cache.entries = ordered.clone(); + cache.loaded_path = list_path.to_string_lossy().to_string(); + } + + let take = limit.min(ordered.len()); + Ok(ordered.into_iter().take(take).collect()) +} + +fn resolve_ip_list_path(spec: &str) -> Option { + let path = Path::new(spec); + if path.exists() { + return Some(path.to_path_buf()); + } + + if let Ok(cwd) = std::env::current_dir() { + for ancestor in cwd.ancestors() { + let candidate = ancestor.join(spec); + if candidate.exists() { + return Some(candidate); + } + let resources_candidate = ancestor.join("resources").join(spec); + if resources_candidate.exists() { + return Some(resources_candidate); + } + } + } + + if let Ok(exe) = std::env::current_exe() { + if let Some(parent) = exe.parent() { + for ancestor in parent.ancestors() { + let candidate = ancestor.join(spec); + if candidate.exists() { + return Some(candidate); + } + let resources_candidate = ancestor.join("resources").join(spec); + if resources_candidate.exists() { + return Some(resources_candidate); + } + } + } + } + + None +} + +fn resolve_google_ip(value: &str) -> Result { + resolve_ip_list(value, 1)? + .into_iter() + .next() + .ok_or_else(|| "Google edge IP did not produce any IP".to_string()) } fn canonical_google_ip(value: &str) -> Result { - Ok(parse_google_ip(value)?.to_string()) + Ok(resolve_google_ip(value)?.to_string()) +} + +fn google_control_cidr(google_ip: &str, limit: usize) -> Result, String> { + Ok(resolve_ip_list(google_ip, limit)? + .into_iter() + .map(|ip| if ip.is_ipv4() { format!("{ip}/32") } else { format!("{ip}/128") }) + .collect()) } fn google_control_cidr(google_ip: &str) -> String { - let ip = - parse_google_ip(google_ip).expect("Google control IP must be canonical before routing"); + let ip = resolve_google_ip(google_ip).expect("Google control IP must be canonical before routing"); if ip.is_ipv4() { format!("{ip}/32") } else { @@ -1538,9 +1719,40 @@ fn google_control_cidr(google_ip: &str) -> String { } } -fn tunnel_config(socks_port: u16, sidecar_process_path: &str, google_ip: &str) -> Value { +fn ips_by_latency(ips: Vec) -> Vec { + if ips.len() <= 1 || std::env::var("SKIRK_GOOGLE_IP_PROBE_DISABLE").ok().as_deref() == Some("1") { + return ips; + } + let mut candidates = Vec::new(); + for ip in ips.iter().take(6).copied() { + candidates.push((measure_google_ip_latency(ip, Duration::from_millis(450)), ip)); + } + candidates.sort_by_key(|(latency, _)| *latency); + let mut ordered: Vec = candidates.into_iter().map(|(_, ip)| ip).collect(); + let skip = ordered.len(); + ordered.extend(ips.into_iter().skip(skip)); + if !ordered.is_empty() { + let rotation = cache_list_rotation.fetch_add(1, Ordering::Relaxed) % ordered.len(); + ordered.rotate_left(rotation); + } + ordered +} + +fn measure_google_ip_latency(ip: IpAddr, timeout: Duration) -> Duration { + let started = Instant::now(); + let socket = std::net::SocketAddr::new(ip, 443); + match std::net::TcpStream::connect_timeout(&socket, timeout) { + Ok(stream) => { + let _ = stream.shutdown(std::net::Shutdown::Both); + started.elapsed().max(Duration::from_millis(1)) + } + Err(_) => timeout + Duration::from_secs(10), + } +} + +fn tunnel_config(socks_port: u16, sidecar_process_path: &str, google_ip: &str) -> Result { let google_control_cidr = google_control_cidr(google_ip); - let google_control_route_cidr = google_control_cidr.clone(); + let google_control_route_cidr = google_control_cidr(google_ip, 12)?; let sidecar_process_names = if cfg!(windows) { json!(["skirk-sidecar.exe", "skirk.exe", "skirk-windows-amd64.exe"]) } else { @@ -1587,7 +1799,7 @@ fn tunnel_config(socks_port: u16, sidecar_process_path: &str, google_ip: &str) - "::1/128", "fc00::/7", "fe80::/10", - google_control_cidr + google_control_cidr.clone() ] } ], @@ -1960,9 +2172,11 @@ fn share_addresses(address: &str) -> Vec { fn discover_lan_ips() -> Vec { let mut ips = Vec::new(); - for target in ["8.8.8.8:80", "1.1.1.1:80"] { + let targets = resolve_ip_list(&default_google_ip(), 4).unwrap_or_default(); + for target in targets { + let target = format!("{target}:80"); if let Ok(socket) = std::net::UdpSocket::bind("0.0.0.0:0") { - if socket.connect(target).is_ok() { + if socket.connect(&target).is_ok() { if let Ok(addr) = socket.local_addr() { let ip = addr.ip().to_string(); if ip != "0.0.0.0" && !ips.contains(&ip) { @@ -2111,7 +2325,8 @@ mod tests { #[test] fn tunnel_config_uses_sing_box_1_13_tun_fields() { - let config = tunnel_config(18080, r"C:\Skirk\skirk-sidecar.exe", "216.239.38.120"); + std::env::set_var("SKIRK_GOOGLE_IP_PROBE_DISABLE", "1"); + let config = tunnel_config(18080, r"C:\Skirk\skirk-sidecar.exe", &default_google_ip()).expect("tunnel config"); let inbound = config .pointer("/inbounds/0") .and_then(Value::as_object) @@ -2145,17 +2360,25 @@ mod tests { config.pointer("/dns/strategy").and_then(Value::as_str), Some("ipv4_only") ); - assert!(config + let route_excludes = config .pointer("/inbounds/0/route_exclude_address") .and_then(Value::as_array) - .expect("route_exclude_address") - .iter() - .any(|value| value.as_str() == Some("216.239.38.120/32"))); + .expect("route_exclude_address"); + let google_control_route_cidr = google_control_cidr(&default_google_ip(), 12) + .expect("google control cidrs"); + let selected_google_cidr = google_control_route_cidr + .first() + .cloned() + .expect("google control cidr"); + assert!(route_excludes.iter().any(|value| value.as_str() == Some(selected_google_cidr.as_str()))); + for expected in google_control_route_cidr.iter().skip(1).take(1) { + assert!(route_excludes.iter().any(|value| value.as_str() == Some(expected.as_str()))); + } assert_eq!( config .pointer("/route/rules/0/ip_cidr/0") .and_then(Value::as_str), - Some("216.239.38.120/32") + Some(selected_google_cidr.as_str()) ); assert_eq!( config @@ -2250,30 +2473,50 @@ mod tests { #[test] fn tunnel_config_uses_custom_google_control_ip() { - let config = tunnel_config(18080, r"C:\Skirk\skirk-sidecar.exe", "8.8.8.8"); + let temp_path = std::env::temp_dir().join(format!( + "skirk-google-ip-test-{}-{}.txt", + std::process::id(), + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("clock") + .as_nanos() + )); + std::fs::write(&temp_path, "127.0.0.1\n").expect("write temp ip list"); + let temp_path = temp_path.to_string_lossy().to_string(); + let config = tunnel_config(18080, r"C:\Skirk\skirk-sidecar.exe", &temp_path).expect("tunnel config"); assert!(config .pointer("/inbounds/0/route_exclude_address") .and_then(Value::as_array) .expect("route_exclude_address") .iter() - .any(|value| value.as_str() == Some("8.8.8.8/32"))); + .any(|value| value.as_str() == Some("127.0.0.1/32"))); assert_eq!( config .pointer("/route/rules/0/ip_cidr/0") .and_then(Value::as_str), - Some("8.8.8.8/32") + Some("127.0.0.1/32") ); } #[test] fn google_control_ip_is_canonicalized() { + let temp_path = std::env::temp_dir().join(format!( + "skirk-google-ip-canon-{}-{}.txt", + std::process::id(), + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("clock") + .as_nanos() + )); + std::fs::write(&temp_path, "127.0.0.1\n127.0.0.2\n").expect("write temp ip list"); + let temp_path = temp_path.to_string_lossy().to_string(); assert_eq!( - canonical_google_ip("").expect("default Google IP"), - "216.239.38.120" + canonical_google_ip(&temp_path).expect("default Google IP"), + "127.0.0.1" ); assert_eq!( - canonical_google_ip(" 8.8.8.8 ").expect("custom Google IP"), - "8.8.8.8" + canonical_google_ip(" 127.0.0.1 ").expect("custom Google IP"), + "127.0.0.1" ); assert!(canonical_google_ip("not-an-ip").is_err()); } @@ -2289,7 +2532,7 @@ mod tests { http_port: 18081, share_lan, route_mode: "google_front_pinned".into(), - google_ip: "216.239.38.120".into(), + google_ip: default_google_ip(), drive_space: "appDataFolder".into(), drive_folder_id: String::new(), performance: default_performance_settings(), diff --git a/clients/desktop/src/lib/api.ts b/clients/desktop/src/lib/api.ts index cef60ed..1f7059f 100644 --- a/clients/desktop/src/lib/api.ts +++ b/clients/desktop/src/lib/api.ts @@ -107,6 +107,10 @@ const isTauriRuntime = typeof window !== "undefined" && "__TAURI_INTERNALS__" in (window as unknown as { __TAURI_INTERNALS__?: unknown }); const useBrowserPreview = import.meta.env.DEV && !isTauriRuntime; +const googleIpList = + import.meta.env.VITE_SKIRK_GOOGLE_IP_LIST?.trim() || + import.meta.env.VITE_SKIRK_CACHED_LIST?.trim() || + "assets/ip-list.txt"; let mockProfiles: ClientProfile[] = [ { @@ -119,7 +123,7 @@ let mockProfiles: ClientProfile[] = [ httpPort: 18081, shareLan: false, routeMode: "google_front_pinned", - googleIp: "216.239.38.120", + googleIp: googleIpList, driveSpace: "appDataFolder", driveFolderId: "", performance: recommendedPerformance(), @@ -220,7 +224,7 @@ const browserPreviewApi = { httpPort, shareLan, routeMode: "google_front_pinned", - googleIp: "216.239.38.120", + googleIp: googleIpList, driveSpace: "appDataFolder", driveFolderId: "", performance: recommendedPerformance(), diff --git a/cmd/skirk/main.go b/cmd/skirk/main.go index a483635..591bc9b 100644 --- a/cmd/skirk/main.go +++ b/cmd/skirk/main.go @@ -1394,7 +1394,7 @@ func sampleConfig(args []string) error { session := fs.String("session", "", "fixed 32-hex session id") proxy := fs.String("proxy", "socks5h://127.0.0.1:1080", "upstream restricted-network proxy") routeMode := fs.String("route-mode", "google_front_pinned", "route mode: direct, real_pinned, google_front, google_front_pinned, google_front_h1, google_front_h1_pinned") - googleIP := fs.String("google-ip", "216.239.38.120", "Google edge IP for pinned routing") + googleIP := fs.String("google-ip", skirk.DefaultGoogleIP(), "Google edge IP for pinned routes (IP, path, or ${SKIRK_GOOGLE_IP_LIST}; defaults to assets/ip-list.txt)") concurrency := fs.Int("concurrency", 8, "Drive upload/download concurrency") if err := fs.Parse(args); err != nil { return err diff --git a/cmd/skirk/setup.go b/cmd/skirk/setup.go index a398483..6dc90ad 100644 --- a/cmd/skirk/setup.go +++ b/cmd/skirk/setup.go @@ -69,7 +69,7 @@ func setupInit(ctx context.Context, args []string) error { exitRoute := fs.String("exit-route", "direct", "exit Google API route: direct, real_pinned, google_front, google_front_pinned, google_front_h1, google_front_h1_pinned") clientProxy := fs.String("client-proxy", "", "optional upstream SOCKS5 URL for client Google API traffic") exitProxy := fs.String("exit-proxy", "", "optional outbound proxy URL for exit target traffic, for example socks5h://127.0.0.1:40000") - googleIP := fs.String("google-ip", "216.239.38.120", "Google edge IP for pinned routes") + googleIP := fs.String("google-ip", skirk.DefaultGoogleIP(), "Google edge IP for pinned routes (IP, path, or ${SKIRK_GOOGLE_IP_LIST}; defaults to assets/ip-list.txt)") listen := fs.String("listen", "127.0.0.1:18080", "client SOCKS5 listen address") chunkSize := fs.Int("chunk-size", 8*1024*1024, "maximum tunnel chunk size") pollMS := fs.Int("poll-ms", 1000, "Drive mailbox poll interval in milliseconds") diff --git a/examples/skirk.config.example.json b/examples/skirk.config.example.json index 88a5f46..f1e3458 100644 --- a/examples/skirk.config.example.json +++ b/examples/skirk.config.example.json @@ -7,7 +7,7 @@ "route": { "mode": "google_front", "proxy": "socks5h://127.0.0.1:1080", - "google_ip": "216.239.38.120", + "google_ip": "${SKIRK_GOOGLE_IP_LIST}", "timeout_seconds": 240 }, "drive": { diff --git a/internal/skirk/config.go b/internal/skirk/config.go index 69f2317..9a0afef 100644 --- a/internal/skirk/config.go +++ b/internal/skirk/config.go @@ -269,12 +269,20 @@ func isRawURLBase64Rune(r rune) bool { r == '_' } +func normalizeGoogleIPSpec(spec string) string { + spec = strings.TrimSpace(os.ExpandEnv(spec)) + if spec == "" { + return "" + } + return spec +} + func (c *Config) ApplyDefaults() { if c.Route.Mode == "" { c.Route.Mode = "real_pinned" } - if c.Route.GoogleIP == "" { - c.Route.GoogleIP = "216.239.38.120" + if c.Route.GoogleIP = normalizeGoogleIPSpec(c.Route.GoogleIP); c.Route.GoogleIP == "" { + c.Route.GoogleIP = defaultGoogleIP() } if c.Route.TimeoutSeconds == 0 { c.Route.TimeoutSeconds = 240 diff --git a/internal/skirk/config_test.go b/internal/skirk/config_test.go index 0f54fc0..5693c36 100644 --- a/internal/skirk/config_test.go +++ b/internal/skirk/config_test.go @@ -240,7 +240,7 @@ func TestOAuthTokenAttemptsIncludeHostileRoutes(t *testing.T) { route := RouteConfig{ Mode: "google_front", Proxy: "socks5h://127.0.0.1:11093", - GoogleIP: "216.239.38.120", + GoogleIP: defaultGoogleIP(), TimeoutSeconds: 240, } attempts := oauthTokenAttempts(route) @@ -280,7 +280,7 @@ func TestTextConfigRoundTrip(t *testing.T) { RefreshToken: "refresh-token", TokenURL: "https://oauth2.googleapis.com/token", }, - Route: RouteConfig{Mode: "google_front_pinned", GoogleIP: "216.239.38.120"}, + Route: RouteConfig{Mode: "google_front_pinned", GoogleIP: defaultGoogleIP()}, Drive: DriveConfig{FolderID: "drive-folder"}, Tunnel: TunnelConfig{Listen: "127.0.0.1:18080", ChunkSize: 1024 * 1024, PollIntervalMS: 1200, Concurrency: 4, CleanupProcessed: true}, } diff --git a/internal/skirk/google_ip.go b/internal/skirk/google_ip.go new file mode 100644 index 0000000..1816e85 --- /dev/null +++ b/internal/skirk/google_ip.go @@ -0,0 +1,314 @@ +package skirk + +import ( + "errors" + "fmt" + "net" + "os" + "path/filepath" + "sort" + "strings" + "sync" + "sync/atomic" + "time" +) + +const defaultGoogleIPListFile = "assets/ip-list.txt" + +type cachedListStore struct { + mu sync.RWMutex + path string + entries []string + loadedPath string + loadedAt time.Time + rotation atomic.Uint64 +} + +var cache_list = &cachedListStore{path: defaultGoogleIPListPath()} + +type googleIPCandidate struct { + ip string + latency time.Duration +} + +func defaultGoogleIP() string { + return cache_list.Path() +} + +func DefaultGoogleIP() string { + return defaultGoogleIP() +} + +func defaultGoogleIPListPath() string { + if value := strings.TrimSpace(os.Getenv("SKIRK_GOOGLE_IP_LIST")); value != "" { + return value + } + if value := strings.TrimSpace(os.Getenv("SKIRK_CACHED_LIST")); value != "" { + return value + } + return defaultGoogleIPListFile +} + +func (c *cachedListStore) Path() string { + c.mu.RLock() + defer c.mu.RUnlock() + if trimmed := strings.TrimSpace(c.path); trimmed != "" { + return trimmed + } + return defaultGoogleIPListPath() +} + +func (c *cachedListStore) SetPath(spec string) { + c.mu.Lock() + defer c.mu.Unlock() + trimmed := strings.TrimSpace(spec) + if trimmed == "" { + trimmed = defaultGoogleIPListPath() + } + if c.path != trimmed { + c.path = trimmed + c.entries = nil + c.loadedPath = "" + c.loadedAt = time.Time{} + c.rotation.Store(0) + } +} + +func (c *cachedListStore) SetEntries(entries []string) { + c.mu.Lock() + defer c.mu.Unlock() + c.entries = normalizeGoogleIPEntries(entries) + c.loadedPath = c.path + c.loadedAt = time.Now().UTC() + c.rotation.Store(0) +} + +func (c *cachedListStore) Entries(spec string, limit int) ([]string, error) { + ips, err := c.resolve(spec, limit) + if err != nil { + return nil, err + } + out := make([]string, len(ips)) + copy(out, ips) + return out, nil +} + +func resolveGoogleIPSpec(spec string) (string, error) { + ips, err := resolveGoogleIPList(spec, 1) + if err != nil { + return "", err + } + if len(ips) == 0 { + return "", errors.New("Google edge IP did not produce any IP") + } + return ips[0], nil +} + +func resolveGoogleIPList(spec string, limit int) ([]string, error) { + return cache_list.Entries(spec, limit) +} + +func (c *cachedListStore) resolve(spec string, limit int) ([]string, error) { + trimmed := strings.TrimSpace(spec) + if trimmed == "" { + trimmed = c.Path() + } + if limit <= 0 { + limit = 12 + } + + if ip := net.ParseIP(trimmed); ip != nil { + return []string{ip.String()}, nil + } + + c.mu.RLock() + cachedPath := c.loadedPath + cachedEntries := append([]string(nil), c.entries...) + c.mu.RUnlock() + + if cachedPath == trimmed && len(cachedEntries) > 0 { + if limit < len(cachedEntries) { + return append([]string(nil), cachedEntries[:limit]...), nil + } + return cachedEntries, nil + } + + data, path, err := readGoogleIPList(trimmed) + if err != nil { + return nil, err + } + raw := normalizeGoogleIPEntries(parseGoogleIPsFromList(string(data))) + if len(raw) == 0 { + return nil, fmt.Errorf("google IP list %q did not contain a valid IP address", path) + } + + ordered := prioritizeGoogleIPsByLatency(raw) + + c.mu.Lock() + c.path = trimmed + c.entries = append([]string(nil), ordered...) + c.loadedPath = trimmed + c.loadedAt = time.Now().UTC() + c.mu.Unlock() + + if limit < len(ordered) { + return append([]string(nil), ordered[:limit]...), nil + } + return ordered, nil +} + +func normalizeGoogleIPEntries(entries []string) []string { + seen := make(map[string]struct{}, len(entries)) + normalized := make([]string, 0, len(entries)) + for _, entry := range entries { + ip := net.ParseIP(strings.TrimSpace(entry)) + if ip == nil { + continue + } + canonical := ip.String() + if _, ok := seen[canonical]; ok { + continue + } + seen[canonical] = struct{}{} + normalized = append(normalized, canonical) + } + return normalized +} + +func parseGoogleIPsFromList(text string) []string { + ips := make([]string, 0, 32) + seen := make(map[string]struct{}, 32) + for _, rawLine := range strings.Split(text, "\n") { + line := strings.TrimSpace(rawLine) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + if idx := strings.IndexByte(line, '#'); idx >= 0 { + line = strings.TrimSpace(line[:idx]) + if line == "" { + continue + } + } + fields := strings.FieldsFunc(line, func(r rune) bool { + switch r { + case ',', ';', ' ', ' ': + return true + default: + return false + } + }) + candidates := append([]string(nil), fields...) + candidates = append(candidates, strings.Trim(line, `"'`)) + for _, field := range candidates { + field = strings.TrimSpace(strings.Trim(field, `"'`)) + if field == "" { + continue + } + ip := net.ParseIP(field) + if ip == nil { + continue + } + canonical := ip.String() + if _, ok := seen[canonical]; ok { + continue + } + seen[canonical] = struct{}{} + ips = append(ips, canonical) + } + } + return ips +} + +func prioritizeGoogleIPsByLatency(ips []string) []string { + if len(ips) <= 1 { + return ips + } + if strings.EqualFold(strings.TrimSpace(os.Getenv("SKIRK_GOOGLE_IP_PROBE_DISABLE")), "1") { + return ips + } + probeTimeout := 450 * time.Millisecond + if raw := strings.TrimSpace(os.Getenv("SKIRK_GOOGLE_IP_PROBE_TIMEOUT_MS")); raw != "" { + if ms, err := time.ParseDuration(raw + "ms"); err == nil && ms >= 100*time.Millisecond && ms <= 5*time.Second { + probeTimeout = ms + } + } + probeCount := len(ips) + if probeCount > 6 { + probeCount = 6 + } + candidates := make([]googleIPCandidate, 0, probeCount) + for _, ip := range ips[:probeCount] { + candidates = append(candidates, googleIPCandidate{ + ip: ip, + latency: measureGoogleIPLatency(ip, probeTimeout), + }) + } + sort.SliceStable(candidates, func(i, j int) bool { + return candidates[i].latency < candidates[j].latency + }) + ordered := make([]string, 0, len(candidates)) + for _, candidate := range candidates { + ordered = append(ordered, candidate.ip) + } + for _, ip := range ips[probeCount:] { + ordered = append(ordered, ip) + } + return ordered +} + +func measureGoogleIPLatency(ip string, timeout time.Duration) time.Duration { + start := time.Now() + conn, err := net.DialTimeout("tcp", net.JoinHostPort(ip, "443"), timeout) + if err != nil { + return timeout + (10 * time.Second) + } + _ = conn.Close() + latency := time.Since(start) + if latency <= 0 { + return time.Millisecond + } + return latency +} + +func readGoogleIPList(spec string) ([]byte, string, error) { + if data, err := os.ReadFile(spec); err == nil { + return data, spec, nil + } + if filepath.IsAbs(spec) { + return nil, spec, fmt.Errorf("could not read Google IP list %q", spec) + } + + candidates := []string{spec} + if cwd, err := os.Getwd(); err == nil { + for dir := cwd; ; dir = filepath.Dir(dir) { + candidates = append(candidates, filepath.Join(dir, spec)) + candidates = append(candidates, filepath.Join(dir, "resources", spec)) + parent := filepath.Dir(dir) + if parent == dir { + break + } + } + } + if exe, err := os.Executable(); err == nil { + for dir := filepath.Dir(exe); ; dir = filepath.Dir(dir) { + candidates = append(candidates, filepath.Join(dir, spec)) + candidates = append(candidates, filepath.Join(dir, "resources", spec)) + parent := filepath.Dir(dir) + if parent == dir { + break + } + } + } + + seen := map[string]struct{}{} + for _, candidate := range candidates { + if _, ok := seen[candidate]; ok { + continue + } + seen[candidate] = struct{}{} + if data, err := os.ReadFile(candidate); err == nil { + return data, candidate, nil + } + } + return nil, spec, fmt.Errorf("could not read Google IP list %q", spec) +} diff --git a/internal/skirk/httpclient.go b/internal/skirk/httpclient.go index 1fd31ae..5a3e7c1 100644 --- a/internal/skirk/httpclient.go +++ b/internal/skirk/httpclient.go @@ -13,6 +13,7 @@ import ( "net/http" "strconv" "strings" + "sync/atomic" "time" utls "github.com/refraction-networking/utls" @@ -53,14 +54,25 @@ func (e *GoogleAPIError) IsStorageQuotaExceeded() bool { } type GoogleHTTPClient struct { - client *http.Client - route RouteConfig + client *http.Client + route RouteConfig + googleIPs []string + googleIPResolve error + googleIPRotation atomic.Uint64 } func NewGoogleHTTPClient(route RouteConfig) *GoogleHTTPClient { if route.TimeoutSeconds == 0 { route.TimeoutSeconds = 240 } + client := &GoogleHTTPClient{route: route} + if shouldPinGoogleIP(route.Mode) { + spec := strings.TrimSpace(route.GoogleIP) + if spec == "" { + spec = defaultGoogleIP() + } + client.googleIPs, client.googleIPResolve = resolveGoogleIPList(spec, 12) + } baseDialer := &net.Dialer{Timeout: 25 * time.Second, KeepAlive: 30 * time.Second} dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) { host, port, err := net.SplitHostPort(addr) @@ -68,8 +80,17 @@ func NewGoogleHTTPClient(route RouteConfig) *GoogleHTTPClient { return nil, err } target := addr - if route.GoogleIP != "" && port == "443" && shouldPinGoogleIP(route.Mode) { - target = net.JoinHostPort(route.GoogleIP, port) + if port == "443" && shouldPinGoogleIP(route.Mode) { + if len(client.googleIPs) > 0 { + idx := client.googleIPRotation.Add(1) - 1 + target = net.JoinHostPort(client.googleIPs[idx%uint64(len(client.googleIPs))], port) + } else if client.googleIPResolve != nil { + return nil, client.googleIPResolve + } else if resolved, err := resolveGoogleIPSpec(route.GoogleIP); err == nil { + target = net.JoinHostPort(resolved, port) + } else { + return nil, err + } } else if host == "" { target = addr } @@ -104,10 +125,8 @@ func NewGoogleHTTPClient(route RouteConfig) *GoogleHTTPClient { ReadIdleTimeout: 30 * time.Second, PingTimeout: 15 * time.Second, } - return &GoogleHTTPClient{ - client: &http.Client{Transport: transport, Timeout: time.Duration(route.TimeoutSeconds) * time.Second}, - route: route, - } + client.client = &http.Client{Transport: transport, Timeout: time.Duration(route.TimeoutSeconds) * time.Second} + return client } tlsDialContext := func(ctx context.Context, network, addr string) (net.Conn, error) { raw, err := dialContext(ctx, network, addr) @@ -149,10 +168,8 @@ func NewGoogleHTTPClient(route RouteConfig) *GoogleHTTPClient { if isGoogleFrontHTTP1Route(route.Mode) { transport.DialTLSContext = tlsDialContext } - return &GoogleHTTPClient{ - client: &http.Client{Transport: transport, Timeout: time.Duration(route.TimeoutSeconds) * time.Second}, - route: route, - } + client.client = &http.Client{Transport: transport, Timeout: time.Duration(route.TimeoutSeconds) * time.Second} + return client } func (c *GoogleHTTPClient) Request(ctx context.Context, method, host, path string, headers map[string]string, body []byte) (*HTTPResult, error) { diff --git a/internal/skirk/httpclient_test.go b/internal/skirk/httpclient_test.go index f735e5a..3a054be 100644 --- a/internal/skirk/httpclient_test.go +++ b/internal/skirk/httpclient_test.go @@ -32,7 +32,7 @@ func TestGoogleFrontRouteProtocolSelection(t *testing.T) { } func TestGoogleHTTPRouteAttemptsAddGoogleSNIFallback(t *testing.T) { - directProxy := RouteConfig{Mode: "direct", Proxy: "socks5h://127.0.0.1:11093", GoogleIP: "216.239.38.120", TimeoutSeconds: 240} + directProxy := RouteConfig{Mode: "direct", Proxy: "socks5h://127.0.0.1:11093", GoogleIP: defaultGoogleIP(), TimeoutSeconds: 240} attempts := googleHTTPRouteAttempts(directProxy) if len(attempts) != 2 { t.Fatalf("direct proxy attempts = %d, want 2", len(attempts)) @@ -46,7 +46,7 @@ func TestGoogleHTTPRouteAttemptsAddGoogleSNIFallback(t *testing.T) { t.Fatalf("clean fronted route attempts = %d, want 1", len(got)) } - pinned := RouteConfig{Mode: "google_front_pinned", Proxy: "socks5h://127.0.0.1:11093", GoogleIP: "216.239.38.120"} + pinned := RouteConfig{Mode: "google_front_pinned", Proxy: "socks5h://127.0.0.1:11093", GoogleIP: defaultGoogleIP()} attempts = googleHTTPRouteAttempts(pinned) if len(attempts) != 1 || attempts[0].Mode != "google_front_pinned" || attempts[0].GoogleIP != pinned.GoogleIP { t.Fatalf("pinned route attempts = %+v", attempts) diff --git a/tools/google_api_path_probe.py b/tools/google_api_path_probe.py index 797ba7a..ba0ba6e 100644 --- a/tools/google_api_path_probe.py +++ b/tools/google_api_path_probe.py @@ -17,11 +17,13 @@ from pathlib import Path from typing import Any +from google_ip import cache_list, resolve_ip + ROOT = Path(__file__).resolve().parents[1] OUT_ROOT = Path(os.environ.get("SKIRK_PROBE_OUT", ROOT / ".skirk-runs" / "probe_results")) PROXY = os.environ.get("SKIRK_SOCKS", "socks5h://127.0.0.1:1080") -GOOGLE_IP = os.environ.get("SKIRK_GOOGLE_IP", "216.239.38.120") +GOOGLE_IP = resolve_ip(os.environ.get("SKIRK_GOOGLE_IP_LIST", os.environ.get("SKIRK_CACHED_LIST", cache_list.path))) PROJECT_ID = os.environ.get("SKIRK_PROJECT_ID", "gen-lang-client-0194763728") REGION = os.environ.get("SKIRK_REGION", "us-east1") diff --git a/tools/google_ip.py b/tools/google_ip.py new file mode 100644 index 0000000..1a7e955 --- /dev/null +++ b/tools/google_ip.py @@ -0,0 +1,181 @@ +from __future__ import annotations + +import ipaddress +import os +import socket +import threading +import time +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +DEFAULT_GOOGLE_IP_LIST = "assets/ip-list.txt" +LEGACY_CACHED_LIST_ENV = "SKIRK_CACHED_LIST" +GOOGLE_IP_LIST_ENV = "SKIRK_GOOGLE_IP_LIST" + + +class CachedList: + def __init__(self, path: str | None = None) -> None: + self._lock = threading.RLock() + self._path = (path or os.environ.get(GOOGLE_IP_LIST_ENV) or os.environ.get(LEGACY_CACHED_LIST_ENV) or DEFAULT_GOOGLE_IP_LIST).strip() or DEFAULT_GOOGLE_IP_LIST + self._entries: list[str] = [] + self._loaded_path: str = "" + self._loaded_at: float = 0.0 + self._rotation = 0 + + @property + def path(self) -> str: + with self._lock: + return self._path + + def set_path(self, path: str | None) -> None: + with self._lock: + new_path = (path or os.environ.get(GOOGLE_IP_LIST_ENV) or os.environ.get(LEGACY_CACHED_LIST_ENV) or DEFAULT_GOOGLE_IP_LIST).strip() or DEFAULT_GOOGLE_IP_LIST + if new_path != self._path: + self._path = new_path + self._entries = [] + self._loaded_path = "" + self._loaded_at = 0.0 + self._rotation = 0 + + def set_entries(self, entries: list[str]) -> None: + with self._lock: + self._entries = _normalize(entries) + self._loaded_path = self._path + self._loaded_at = time.time() + self._rotation = 0 + + def resolve(self, value: str | None, limit: int = 12) -> list[str]: + spec = (value or self.path).strip() or self.path + if limit <= 0: + raise ValueError("limit must be greater than zero") + try: + return [str(ipaddress.ip_address(spec))] + except ValueError: + pass + + with self._lock: + if self._loaded_path == spec and self._entries: + return self._entries[:limit] + + candidates = _candidate_paths(spec) + ips = _read_candidates(candidates) + if not ips: + raise ValueError(f"could not resolve Google IP from {spec!r}") + ordered = _prioritize_by_latency(ips) + with self._lock: + self._entries = ordered + self._loaded_path = spec + self._loaded_at = time.time() + return ordered[:limit] + + def resolve_one(self, value: str | None) -> str: + return self.resolve(value, 1)[0] + + +cache_list = CachedList() + + +def _candidate_paths(spec: str) -> list[Path]: + path = Path(spec) + if path.exists(): + return [path] + + candidates = [ROOT / spec] + cwd = Path.cwd() + candidates.extend(parent / spec for parent in [cwd, *cwd.parents]) + candidates.extend(parent / spec for parent in [ROOT, *ROOT.parents]) + + unique: list[Path] = [] + seen: set[str] = set() + for candidate in candidates: + key = str(candidate.resolve(strict=False)) + if key in seen: + continue + seen.add(key) + unique.append(candidate) + return unique + + +def _normalize(values: list[str]) -> list[str]: + out: list[str] = [] + seen: set[str] = set() + for value in values: + try: + ip = str(ipaddress.ip_address(value.strip())) + except ValueError: + continue + if ip in seen: + continue + seen.add(ip) + out.append(ip) + return out + + +def _read_candidates(candidates: list[Path]) -> list[str]: + ips: list[str] = [] + seen: set[str] = set() + for candidate in candidates: + try: + text = candidate.read_text(encoding="utf-8") + except OSError: + continue + for raw_line in text.splitlines(): + line = raw_line.strip() + if not line or line.startswith("#"): + continue + if "#" in line: + line = line.split("#", 1)[0].strip() + if not line: + continue + fields = [*line.replace(",", " ").replace(";", " ").split(), line] + for field in fields: + field = field.strip().strip("\"\'") + if not field: + continue + try: + ip = str(ipaddress.ip_address(field)) + except ValueError: + continue + if ip in seen: + continue + seen.add(ip) + ips.append(ip) + if ips: + break + return ips + +def _tcp_connect_latency(ip: str, timeout: float = 0.45) -> float: + started = time.perf_counter() + try: + with socket.create_connection((ip, 443), timeout=timeout): + pass + except OSError: + return timeout + 10.0 + return max(time.perf_counter() - started, 0.0001) + + +def _prioritize_by_latency(ips: list[str]) -> list[str]: + if len(ips) <= 1 or os.environ.get("SKIRK_GOOGLE_IP_PROBE_DISABLE", "").strip() == "1": + return ips + timeout = 0.45 + raw = os.environ.get("SKIRK_GOOGLE_IP_PROBE_TIMEOUT_MS", "").strip() + if raw: + try: + ms = int(raw) + if 100 <= ms <= 5000: + timeout = ms / 1000.0 + except ValueError: + pass + probe_count = min(len(ips), 6) + ranked = sorted(((ip, _tcp_connect_latency(ip, timeout)) for ip in ips[:probe_count]), key=lambda item: item[1]) + ordered = [ip for ip, _ in ranked] + ordered.extend(ips[probe_count:]) + return ordered + + +def resolve_ip_list(value: str | None, limit: int = 12) -> list[str]: + return cache_list.resolve(value, limit) + + +def resolve_ip(value: str | None) -> str: + return cache_list.resolve_one(value) diff --git a/tools/google_substrate_probe.py b/tools/google_substrate_probe.py index 30612d5..044a559 100644 --- a/tools/google_substrate_probe.py +++ b/tools/google_substrate_probe.py @@ -16,11 +16,13 @@ from pathlib import Path from typing import Any +from google_ip import cache_list, resolve_ip + ROOT = Path(__file__).resolve().parents[1] OUT_ROOT = Path(os.environ.get("SKIRK_PROBE_OUT", ROOT / ".skirk-runs" / "probe_results")) PROXY = os.environ.get("SKIRK_SOCKS", "socks5h://127.0.0.1:1080") -GOOGLE_IP = os.environ.get("SKIRK_GOOGLE_IP", "216.239.38.120") +GOOGLE_IP = resolve_ip(os.environ.get("SKIRK_GOOGLE_IP_LIST", os.environ.get("SKIRK_CACHED_LIST", cache_list.path))) PROJECT_ID = os.environ.get("SKIRK_PROJECT_ID", "gen-lang-client-0194763728") REGION = os.environ.get("SKIRK_REGION", "us-east1") diff --git a/tools/skirk_probe.py b/tools/skirk_probe.py index cd07713..e7e4272 100644 --- a/tools/skirk_probe.py +++ b/tools/skirk_probe.py @@ -17,10 +17,13 @@ from pathlib import Path from typing import Any +from google_ip import cache_list, resolve_ip + ROOT = Path(__file__).resolve().parents[1] OUT_ROOT = Path(os.environ.get("SKIRK_PROBE_OUT", ROOT / ".skirk-runs" / "probe_results")) DEFAULT_PROXY = "socks5h://127.0.0.1:1080" +GOOGLE_IP = resolve_ip(os.environ.get("SKIRK_GOOGLE_IP_LIST", os.environ.get("SKIRK_CACHED_LIST", cache_list.path))) @dataclass @@ -131,13 +134,13 @@ def probes_from_env() -> list[Probe]: tests = [ Probe("google_generate_204_h1", "https://www.google.com/generate_204", "1.1"), Probe("google_generate_204_h2", "https://www.google.com/generate_204", "2"), - Probe("google_ip_216_239_38_120_h1", "https://www.google.com/generate_204", "1.1", connect_to=["www.google.com:443:216.239.38.120:443"]), + Probe("google_ip_216_239_38_120_h1", "https://www.google.com/generate_204", "1.1", connect_to=[f"www.google.com:443:{GOOGLE_IP}:443"]), Probe("script_google_root_h1", "https://script.google.com/", "1.1"), Probe("script_google_invalid_exec_h1", "https://script.google.com/macros/s/AKfycbxInvalidSkirkProbe/exec", "1.1"), Probe("googleapis_discovery_h1", "https://www.googleapis.com/discovery/v1/apis?fields=kind", "1.1"), Probe("googleapis_discovery_h2", "https://www.googleapis.com/discovery/v1/apis?fields=kind", "2"), Probe("drive_about_unauth_h1", "https://www.googleapis.com/drive/v3/about?fields=user", "1.1"), - Probe("googleapis_drive_via_google_ip_sni_googleapis_h1", "https://www.googleapis.com/drive/v3/about?fields=user", "1.1", connect_to=["www.googleapis.com:443:216.239.38.120:443"]), + Probe("googleapis_drive_via_google_ip_sni_googleapis_h1", "https://www.googleapis.com/drive/v3/about?fields=user", "1.1", connect_to=[f"www.googleapis.com:443:{GOOGLE_IP}:443"]), Probe("run_googleapis_discovery_h1", "https://run.googleapis.com/$discovery/rest?version=v2", "1.1"), Probe("accounts_google_h1", "https://accounts.google.com/", "1.1"), Probe("storage_google_root_h1", "https://storage.googleapis.com/", "1.1"), @@ -181,7 +184,7 @@ def probes_from_env() -> list[Probe]: "https://www.google.com/discovery/v1/apis?fields=kind", "1.1", headers=["Host: www.googleapis.com"], - connect_to=["www.google.com:443:216.239.38.120:443"], + connect_to=[f"www.google.com:443:{GOOGLE_IP}:443"], note="Same as googleapis fronting test, pinned to the Google IP used by several repos.", ), Probe( @@ -189,7 +192,7 @@ def probes_from_env() -> list[Probe]: "https://www.google.com/drive/v3/about?fields=user", "1.1", headers=["Host: www.googleapis.com"], - connect_to=["www.google.com:443:216.239.38.120:443"], + connect_to=[f"www.google.com:443:{GOOGLE_IP}:443"], note="Drive API fronting test pinned to the Google IP used by several repos.", ), Probe( @@ -197,7 +200,7 @@ def probes_from_env() -> list[Probe]: "https://www.google.com/macros/s/AKfycbxInvalidSkirkProbe/exec", "1.1", headers=["Host: script.google.com"], - connect_to=["www.google.com:443:216.239.38.120:443"], + connect_to=[f"www.google.com:443:{GOOGLE_IP}:443"], note="Apps Script fronting test pinned to the Google IP used by several repos.", ), Probe( @@ -212,7 +215,7 @@ def probes_from_env() -> list[Probe]: "https://www.google.com/$discovery/rest?version=v2", "1.1", headers=["Host: run.googleapis.com"], - connect_to=["www.google.com:443:216.239.38.120:443"], + connect_to=[f"www.google.com:443:{GOOGLE_IP}:443"], note="Cloud Run Admin API reachability via Google SNI and pinned Google IP; not a run.app service test.", ), Probe( diff --git a/tools/storage_throughput_probe.py b/tools/storage_throughput_probe.py index 2582441..a1072cb 100644 --- a/tools/storage_throughput_probe.py +++ b/tools/storage_throughput_probe.py @@ -13,12 +13,14 @@ from pathlib import Path from urllib.parse import quote +from google_ip import cache_list, resolve_ip + ROOT = Path(__file__).resolve().parents[1] OUT_ROOT = Path(os.environ.get("SKIRK_PROBE_OUT", ROOT / ".skirk-runs" / "probe_results")) CLOUD_RESOURCES = Path(os.environ.get("SKIRK_CLOUD_RESOURCES", ROOT / ".skirk-runs" / "cloud_resources")) PROXY = os.environ.get("SKIRK_SOCKS", "socks5h://127.0.0.1:1080") -GOOGLE_IP = os.environ.get("SKIRK_GOOGLE_IP", "216.239.38.120") +GOOGLE_IP = resolve_ip(os.environ.get("SKIRK_GOOGLE_IP_LIST", os.environ.get("SKIRK_CACHED_LIST", cache_list.path))) PROJECT_ID = os.environ.get("SKIRK_PROJECT_ID", "gen-lang-client-0194763728") REGION = os.environ.get("SKIRK_REGION", "us-east1") GCLOUD_ENV = os.environ.copy() | {"PATH": f"{Path.home() / 'google-cloud-sdk/bin'}:{os.environ.get('PATH', '')}"}