├── .github └── ISSUE_TEMPLATE ├── .gitignore ├── .travis.yml ├── README.md ├── _config.yml ├── assets ├── build │ ├── ci.sh │ └── release.sh ├── packaging │ ├── 7zCon.sfx │ ├── addto-startup.vbs │ ├── gae.user.json.example │ ├── get-latest-goproxy.cmd │ ├── get-latest-goproxy.sh │ ├── goproxy-gtk.desktop │ ├── goproxy-gtk.png │ ├── goproxy-gtk.py │ ├── goproxy-gui.exe │ ├── goproxy-macos.command │ ├── goproxy-macos.icns │ └── goproxy.sh ├── taskbar │ ├── make.bash │ ├── resource.h │ ├── small.ico │ ├── taskbar.c │ ├── taskbar.ico │ └── taskbar.rc └── test │ └── test.go ├── httpproxy ├── filters │ ├── auth │ │ ├── auth.go │ │ └── auth.json │ ├── autoproxy │ │ ├── 17monipdb.dat │ │ ├── autoproxy.go │ │ ├── autoproxy.json │ │ ├── autoproxy_index.go │ │ ├── autoproxy_iphtml.go │ │ ├── autoproxy_mobileconfig.go │ │ ├── autoproxy_pac.go │ │ ├── gfwlist.txt │ │ └── ip.html │ ├── autorange │ │ ├── autorange.go │ │ ├── autorange.json │ │ └── pipe.go │ ├── direct │ │ ├── direct.go │ │ └── direct.json │ ├── filters.go │ ├── filtersctx.go │ ├── gae │ │ ├── gae.go │ │ ├── gae.json │ │ ├── gaeserver.go │ │ └── gaetransport.go │ ├── php │ │ ├── php.go │ │ ├── php.json │ │ ├── phpserver.go │ │ └── phptransport.go │ ├── rewrite │ │ ├── rewrite.go │ │ └── rewrite.json │ ├── ssh2 │ │ ├── ssh2.go │ │ ├── ssh2.json │ │ └── ssh2server.go │ ├── stripssl │ │ ├── rootca.go │ │ ├── stripssl.go │ │ └── stripssl.json │ └── vps │ │ ├── vps.go │ │ ├── vps.json │ │ └── vpsserver.go ├── handler.go ├── helpers │ ├── algothrim.go │ ├── console.go │ ├── console_posix.go │ ├── console_windows.go │ ├── dialer.go │ ├── dialer2.go │ ├── fragment.go │ ├── fragment_read_test.go │ ├── fragment_writeto_test.go │ ├── hostmatcher.go │ ├── hostmatcher_test.go │ ├── importca.go │ ├── importca_windows.go │ ├── io.go │ ├── listener.go │ ├── lookup.go │ ├── lookup_windows.go │ ├── media.go │ ├── media_test.go │ ├── net.go │ ├── net_linux.go │ ├── os.go │ ├── reader.go │ ├── reflect.go │ ├── reflect_test.go │ ├── resolver.go │ ├── tlsciphers.go │ ├── tlsversion.go │ ├── transport.go │ └── transport_test.go ├── httpproxy.go ├── httpproxy.json ├── proxy │ ├── direct.go │ ├── dummy_resolver.go │ ├── http1.go │ ├── http2.go │ ├── https.go │ ├── per_host.go │ ├── per_host_test.go │ ├── proxy.go │ ├── proxy_test.go │ ├── quic.go │ ├── socks4.go │ ├── socks5.go │ └── ssh2.go └── storage │ ├── file.go │ ├── json.go │ ├── storage.go │ └── zip.go ├── main.go └── make.bash /.github/ISSUE_TEMPLATE: -------------------------------------------------------------------------------- 1 | (如果您是遇到本软件使用问题而并不清楚具体原因的话,请尽量提供更多信息,如软件版本,日志截图,地级运营商信息等。否则问题可能会被忽略或关闭) 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | cache/ 3 | *.db 4 | *.exe 5 | *.exe~ 6 | *.key 7 | *.user.json 8 | addto-startup.vbs 9 | goproxy.log 10 | goproxy.log.*.gz 11 | goproxy.sh 12 | goproxy-gtk.desktop 13 | goproxy-gtk.py 14 | goproxy-logo.png 15 | goproxy-macos.command 16 | goproxy-gui.exe 17 | proxy.pac 18 | GoProxy.crt 19 | GoProxy.key 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: false 3 | 4 | language: bash 5 | 6 | script: 7 | - cd /tmp 8 | - bash -xe < <(curl -kL https://github.com/phuslu/goproxy/raw/master/assets/build/ci.sh) 9 | 10 | branches: 11 | only: 12 | - master 13 | 14 | addons: 15 | apt: 16 | packages: 17 | - g++-mingw-w64 18 | - gcc-4.8-multilib-arm-linux-gnueabihf 19 | - gcc-arm-linux-gnueabihf 20 | - lftp 21 | - libc6-dev-armhf-cross 22 | - p7zip-full 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## GoProxy 2 | 3 | 4 | * [讨论区](../../issues/?q=sort:updated-desc+is:open) 5 | 6 | ### 文档 7 | * [简易教程](../wiki/SimpleGuide.md) 8 | * [配置介绍](../wiki/ConfigIntroduce.md) 9 | * [编译步骤](../wiki/HowToBuild.md) 10 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /assets/build/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | export GITHUB_USER=${GITHUB_USER:-phuslu} 6 | export GITHUB_USER_NAME=${GITHUB_USER_NAME:-"Phus Lu"} 7 | export GITHUB_USER_EMAIL=${GITHUB_USER_EMAIL:-phuslu@hotmail.com} 8 | export GITHUB_REPO=goproxy 9 | export GITHUB_CI_REPO=goproxy-ci 10 | export GITHUB_TAG=${GITHUB_TAG} 11 | export GITHUB_TOKEN=${GITHUB_TOKEN:-$(cat ~/GITHUB_TOKEN)} 12 | export GITHUB_CI_REPO_RELEASE_INFO_TXT=github-ci-release-info.txt 13 | 14 | if [ -z "$GITHUB_TOKEN" ]; then 15 | echo Please set GITHUB_TOKEN envar 16 | exit 1 17 | fi 18 | 19 | for CMD in curl tar bzip2 head git awk sha1sum 20 | do 21 | if ! type -p ${CMD}; then 22 | echo -e "\e[1;31mtool ${CMD} is not installed, abort.\e[0m" 23 | exit 1 24 | fi 25 | done 26 | 27 | trap 'rm -rf $HOME/tmp.*.${GITHUB_REPO}; exit' SIGINT SIGQUIT SIGTERM 28 | 29 | WORKING_DIR=${HOME}/tmp.$$.${GITHUB_REPO} 30 | mkdir -p $WORKING_DIR 31 | pushd ${WORKING_DIR} 32 | 33 | if [ -n "${GITHUB_TAG}" ]; then 34 | RELEASE_URL=https://github.com/phuslu/goproxy-ci/releases/tag/${GITHUB_TAG} 35 | else 36 | RELEASE_URL=https://github.com/phuslu/goproxy-ci/releases/latest 37 | fi 38 | 39 | RELEASE_FILES=$(curl -ksSL ${RELEASE_URL} | grep -oP '(goproxy|source)[^/]*\.(tar|gz|bz2|xz|7z|zip)' | uniq) 40 | RELEASE_MESSAGE="https://github.com/phuslu/goproxy-ci/releases/tag/$(echo ${RELEASE_FILES} | grep -oP 'r\d\d\d\d' | head -1)" 41 | 42 | pushd $(mktemp -d -p .) 43 | git init 44 | git config user.name "${GITHUB_USER_NAME}" 45 | git config user.email "${GITHUB_USER_EMAIL}" 46 | git remote add origin https://${GITHUB_USER}:${GITHUB_TOKEN}@github.com/${GITHUB_USER}/${GITHUB_CI_REPO} 47 | git fetch origin master 48 | git checkout -b master FETCH_HEAD 49 | env GIT_COMMITTER_DATE='Mon Jul 12 10:00 2010 +0800' git commit --amend --no-edit --allow-empty --date='Mon Jul 12 10:00 2010 +0800' -m "${RELEASE_MESSAGE}" -m "${RELEASE_FILES}" 50 | git push -f origin master 51 | popd 52 | 53 | if false; then 54 | 55 | GITHUB_RELEASE_URL=https://github.com/aktau/github-release/releases/download/v0.6.2/linux-amd64-github-release.tar.bz2 56 | GITHUB_RELEASE_BIN=$(pwd)/$(curl -kL ${GITHUB_RELEASE_URL} | tar xjpv | head -1) 57 | 58 | if [ -z "${GITHUB_TAG}" ]; then 59 | GITHUB_TAG=$(${GITHUB_RELEASE_BIN} info -u ${GITHUB_USER} -r ${GITHUB_CI_REPO} | grep -m 1 -oP '\- \Kr\d+') 60 | fi 61 | 62 | ${GITHUB_RELEASE_BIN} info -u ${GITHUB_USER} -r ${GITHUB_CI_REPO} -t ${GITHUB_TAG} > ${GITHUB_CI_REPO_RELEASE_INFO_TXT} 63 | RELEASE_NAME=$(cat ${GITHUB_CI_REPO_RELEASE_INFO_TXT} | grep -oP "name: '\K.+?(?=',)") 64 | RELEASE_NOTE=$(cat ${GITHUB_CI_REPO_RELEASE_INFO_TXT} | grep -oP "description: '\K.+?(?=',)") 65 | RELEASE_FILES=$(cat ${GITHUB_CI_REPO_RELEASE_INFO_TXT} | grep -oP 'artifact: \K\S+\.(7z|zip|gz|bz2|xz|tar)') 66 | 67 | for FILE in ${RELEASE_FILES} 68 | do 69 | echo Downloading ${FILE} from https://github.com/${GITHUB_USER}/${GITHUB_CI_REPO}.git#${GITHUB_TAG} 70 | ${GITHUB_RELEASE_BIN} download --user ${GITHUB_USER} --repo ${GITHUB_CI_REPO} --tag ${GITHUB_TAG} --name "$FILE" 71 | done 72 | 73 | ${GITHUB_RELEASE_BIN} delete --user ${GITHUB_USER} --repo ${GITHUB_REPO} --tag ${GITHUB_REPO} || true 74 | 75 | pushd $(mktemp -d -p .) 76 | git init 77 | git config user.name "${GITHUB_USER}" 78 | git config user.email "${GITHUB_USER}@noreply.github.com" 79 | git remote add origin https://${GITHUB_USER}:${GITHUB_TOKEN}@github.com/${GITHUB_USER}/${GITHUB_REPO} 80 | git fetch origin ${GITHUB_REPO} 81 | git checkout -b release FETCH_HEAD 82 | git commit --amend --no-edit --allow-empty 83 | git tag ${GITHUB_REPO} 84 | git push -f origin ${GITHUB_REPO} 85 | popd 86 | 87 | RELEASE_NOTE=$(printf "%s\n\n|sha1|filename|\n|------|------|\n%s" "${RELEASE_NOTE}" "$(sha1sum ${RELEASE_FILES}| awk '{print "|"$1"|"$2"|"}')") 88 | 89 | ${GITHUB_RELEASE_BIN} release --user ${GITHUB_USER} --repo ${GITHUB_REPO} --tag ${GITHUB_REPO} --name "${RELEASE_NAME}" --description "${RELEASE_NOTE}" 90 | 91 | for FILE in $(echo ${RELEASE_FILES} | sort -r) 92 | do 93 | echo Uploading ${FILE} to https://github.com/${GITHUB_USER}/${GITHUB_REPO}.git#${GITHUB_TAG} 94 | ${GITHUB_RELEASE_BIN} upload --user ${GITHUB_USER} --repo ${GITHUB_REPO} --tag ${GITHUB_REPO} --name "${FILE}" --file "${FILE}" 95 | done 96 | 97 | fi 98 | 99 | popd 100 | rm -rf ${WORKING_DIR} 101 | -------------------------------------------------------------------------------- /assets/packaging/7zCon.sfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goproxy0/goproxy/39611867adb372fa95ff8643d4e07cf357320d0e/assets/packaging/7zCon.sfx -------------------------------------------------------------------------------- /assets/packaging/addto-startup.vbs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goproxy0/goproxy/39611867adb372fa95ff8643d4e07cf357320d0e/assets/packaging/addto-startup.vbs -------------------------------------------------------------------------------- /assets/packaging/gae.user.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "AppIDs": [ 3 | "goagenta", 4 | ], 5 | // "EnableQuic": true, 6 | // "DisableHTTP2": true, 7 | // "EnableRemoteDNS": true, 8 | // "ForceHTTP2": true, 9 | // "SSLVerify": true, 10 | // "ForceIPv6": true, 11 | // "DisableIPv6": false, 12 | "HostMap" : { 13 | "google_hk": [ 14 | "216.58.209.101","2404:6800:4005:800::2002","www.l.google.com", 15 | ] 16 | }, 17 | } -------------------------------------------------------------------------------- /assets/packaging/get-latest-goproxy.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | setlocal enableextensions 4 | setlocal enabledelayedexpansion 5 | 6 | cd /d "%~dp0" 7 | 8 | ( 9 | echo Set Http = CreateObject^("WinHttp.WinHttpRequest.5.1"^) 10 | echo Set Stream = CreateObject^("Adodb.Stream"^) 11 | echo Set Environment = CreateObject^("WScript.Shell"^).Environment^("Process"^) 12 | echo If Not Environment^("HTTP_PROXY"^) = "" then 13 | echo Http.SetProxy 2, Environment^("HTTP_PROXY"^), "" 14 | echo Http.Option^(4^) = 256 15 | echo End If 16 | echo Http.SetTimeouts 30*1000, 30*1000, 30*1000, 120*1000 17 | echo Http.Open "GET", WScript.Arguments.Item^(0^), False 18 | echo Http.Send 19 | echo Http.WaitForResponse 5 20 | echo If Not Http.Status = 200 then 21 | echo WScript.Quit 1 22 | echo End If 23 | echo Stream.Type = 1 24 | echo Stream.Open 25 | echo Stream.Write Http.ResponseBody 26 | echo Stream.SaveToFile WScript.Arguments.Item^(1^), 2 27 | )>~.txt 28 | move /y ~.txt ~gdownload.vbs >NUL 29 | 30 | netstat -an| findstr LISTENING | findstr ":8087" >NUL && ( 31 | set HTTP_PROXY=127.0.0.1:8087 32 | ) 33 | 34 | set has_user_json=0 35 | if exist "httpproxy.json" ( 36 | for %%I in (*.user.json) do ( 37 | set has_user_json=1 38 | ) 39 | if "!has_user_json!" == "0" ( 40 | echo Please backup your config as .user.json 41 | goto quit 42 | ) 43 | ) 44 | 45 | forfiles /? 1>NUL 2>NUL && ( 46 | forfiles /P cache /M *.crt /D -90 /C "cmd /c del /f @path" 2>NUL 47 | ) 48 | 49 | for %%I in (*.user.json) do ( 50 | set USER_JSON_FILE=%%I 51 | set /p USER_JSON_LINE= NUL && ( 53 | set USER_JSON_URL=!USER_JSON_LINE:* =! 54 | echo Update !USER_JSON_FILE! with !USER_JSON_URL! 55 | cscript /nologo ~gdownload.vbs "!USER_JSON_URL!" "!USER_JSON_FILE!" 56 | ) 57 | ) 58 | 59 | reg query "HKLM\Hardware\Description\System\CentralProcessor\0" | find /i "x86" && ( 60 | set filename_prefix=goproxy_windows_386 61 | ) || ( 62 | set filename_prefix=goproxy_windows_amd64 63 | ) 64 | 65 | if exist "goproxy.exe" ( 66 | for /f "usebackq" %%I in (`goproxy.exe -version`) do ( 67 | echo %%I | findstr /r "r[0-9][0-9][0-9][0-9][0-9]*" >NUL && ( 68 | set localversion=%%I 69 | ) 70 | ) 71 | ) 72 | if not "%localversion%" == "" ( 73 | echo 0. Local GoProxy version %localversion% 74 | ) 75 | 76 | set remoteversion= 77 | ( 78 | title 1. Checking GoProxy Version 79 | echo 1. Checking GoProxy Version 80 | cscript /nologo ~gdownload.vbs https://github.com/phuslu/goproxy-ci/commits/master ~goproxy_tag.txt 81 | ) && ( 82 | for /f "usebackq tokens=2 delims=-." %%I in (`findstr "%filename_prefix%-r" ~goproxy_tag.txt`) do ( 83 | set remoteversion=%%I 84 | ) 85 | ) || ( 86 | echo Cannot detect !filename_prefix! version 87 | goto quit 88 | ) 89 | del /f ~goproxy_tag.txt 90 | if "!remoteversion!" == "" ( 91 | echo Cannot detect !filename_prefix! version 92 | goto quit 93 | ) 94 | 95 | if "!localversion!" neq "r9999" ( 96 | if "!localversion!" geq "!remoteversion!" ( 97 | echo. 98 | echo Your Goproxy already update to latest. 99 | goto quit 100 | ) 101 | ) 102 | 103 | set filename=!filename_prefix!-!remoteversion!.7z 104 | 105 | ( 106 | title 2. Downloading %filename% 107 | echo 2. Downloading %filename% 108 | cscript /nologo ~gdownload.vbs https://github.com/phuslu/goproxy-ci/releases/download/!remoteversion!/%filename% "~%filename%" 109 | if not exist "~%filename%" ( 110 | echo Cannot download %filename% 111 | goto quit 112 | ) 113 | ) && ( 114 | title 3. Extract GoProxy files 115 | echo 3. Extract GoProxy files 116 | move /y ~%filename% ~%filename%.exe 117 | del /f ~gdownload.vbs 2>NUL 118 | for %%I in ("goproxy.exe" "goproxy-gui.exe") do ( 119 | if exist "%%~I" ( 120 | move /y "%%~I" "~%%~nI.%localversion%.%%~xI.tmp" 121 | ) 122 | ) 123 | ~%filename%.exe -y || echo "Failed to update GoProxy, please retry." 124 | ) 125 | 126 | :quit 127 | del /f ~* 1>NUL 2>NUL 128 | pause 129 | -------------------------------------------------------------------------------- /assets/packaging/get-latest-goproxy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | export LATEST=${LATEST:-false} 4 | 5 | set -e 6 | 7 | for CMD in curl sed expr tar; 8 | do 9 | if ! command -v ${CMD} >/dev/null; then 10 | echo "${CMD} is not installed, abort." 11 | exit 1 12 | fi 13 | done 14 | 15 | linkpath=$(ls -l "$0" | sed "s/.*->\s*//") 16 | cd "$(dirname "$0")" && test -f "$linkpath" && cd "$(dirname "$linkpath")" || true 17 | 18 | if [ -f "httpproxy.json" ]; then 19 | if ! ls *.user.json ; then 20 | echo "Please backup your config as .user.json" 21 | exit 1 22 | fi 23 | fi 24 | 25 | FILENAME_PREFIX= 26 | case $(uname -s)/$(uname -m) in 27 | Linux/x86_64 ) 28 | FILENAME_PREFIX=goproxy_linux_amd64 29 | ;; 30 | Linux/i686|Linux/i386 ) 31 | FILENAME_PREFIX=goproxy_linux_386 32 | ;; 33 | Linux/aarch64|Linux/arm64 ) 34 | FILENAME_PREFIX=goproxy_linux_arm64 35 | ;; 36 | Linux/arm* ) 37 | FILENAME_PREFIX=goproxy_linux_arm 38 | if grep -q ld-linux-armhf.so ./goproxy; then 39 | FILENAME_PREFIX=goproxy_linux_arm_cgo 40 | fi 41 | ;; 42 | Linux/mips64el ) 43 | FILENAME_PREFIX=goproxy_linux_mips64le 44 | ;; 45 | Linux/mips64 ) 46 | FILENAME_PREFIX=goproxy_linux_mips64 47 | ;; 48 | Linux/mipsel ) 49 | FILENAME_PREFIX=goproxy_linux_mipsle 50 | ;; 51 | Linux/mips ) 52 | FILENAME_PREFIX=goproxy_linux_mips 53 | if hexdump -s 5 -n 1 $SHELL | grep -q 0001; then 54 | FILENAME_PREFIX=goproxy_linux_mipsle 55 | fi 56 | ;; 57 | FreeBSD/x86_64 ) 58 | FILENAME_PREFIX=goproxy_freebsd_amd64 59 | ;; 60 | FreeBSD/i686|FreeBSD/i386 ) 61 | FILENAME_PREFIX=goproxy_freebsd_386 62 | ;; 63 | Darwin/x86_64 ) 64 | FILENAME_PREFIX=goproxy_macos_amd64 65 | ;; 66 | Darwin/i686|Darwin/i386 ) 67 | FILENAME_PREFIX=goproxy_macos_386 68 | ;; 69 | * ) 70 | echo "Unsupported platform: $(uname -a)" 71 | exit 1 72 | ;; 73 | esac 74 | 75 | if ./goproxy -version >/dev/null 2>&1; then 76 | GOPROXY_OS=$(./goproxy -os) 77 | GOPROXY_ARCH=$(./goproxy -arch) 78 | if test "${GOPROXY_OS}" = "darwin"; then 79 | GOPROXY_OS=macos 80 | fi 81 | FILENAME_PREFIX=goproxy_${GOPROXY_OS}_${GOPROXY_ARCH} 82 | fi 83 | 84 | LOCALVERSION=$(./goproxy -version 2>/dev/null || :) 85 | echo "0. Local Goproxy version ${LOCALVERSION}" 86 | 87 | if test "${http_proxy}" = ""; then 88 | if netstat -an | grep -i tcp | grep LISTEN | grep '[:\.]8087'; then 89 | echo "Set http_proxy=http://127.0.0.1:8087" 90 | export http_proxy=http://127.0.0.1:8087 91 | export https_proxy=http://127.0.0.1:8087 92 | fi 93 | fi 94 | 95 | for USER_JSON_FILE in *.user.json; do 96 | USER_JSON_LINE=$(head -1 ${USER_JSON_FILE} | tr -d '\r') 97 | if echo "${USER_JSON_LINE}" | grep -q AUTO_UPDATE_URL; then 98 | USER_JSON_URL=${USER_JSON_LINE#* } 99 | echo "Update ${USER_JSON_FILE} with ${USER_JSON_URL}" 100 | curl -fk "${USER_JSON_URL}" >${USER_JSON_FILE}.tmp 101 | mv ${USER_JSON_FILE}.tmp ${USER_JSON_FILE} 102 | fi 103 | done 104 | 105 | echo "1. Checking GoProxy Version" 106 | if test "${LATEST}" = "false"; then 107 | FILENAME=$(curl -k https://github.com/phuslu/goproxy-ci/commits/master | grep -oE "${FILENAME_PREFIX}-r[0-9]+.[0-9a-z\.]+" | head -1) 108 | else 109 | FILENAME=$(curl -kL https://github.com/phuslu/goproxy-ci/releases/latest | grep -oE "${FILENAME_PREFIX}-r[0-9]+.[0-9a-z\.]+" | head -1) 110 | fi 111 | REMOTEVERSION=$(echo ${FILENAME} | awk -F'.' '{print $1}' | awk -F'-' '{print $2}') 112 | if test -z "${REMOTEVERSION}"; then 113 | echo "Cannot detect ${FILENAME_PREFIX} version" 114 | exit 1 115 | fi 116 | 117 | if expr "${LOCALVERSION#r*}" ">=" "${REMOTEVERSION#r*}" >/dev/null; then 118 | echo "Your GoProxy already update to latest" 119 | exit 1 120 | fi 121 | 122 | echo "2. Downloading ${FILENAME}" 123 | curl -kL https://github.com/phuslu/goproxy-ci/releases/download/${REMOTEVERSION}/${FILENAME} >${FILENAME}.tmp 124 | mv -f ${FILENAME}.tmp ${FILENAME} 125 | 126 | echo "3. Extracting ${FILENAME}" 127 | rm -rf ${FILENAME%.*} 128 | case ${FILENAME##*.} in 129 | xz ) 130 | xz -d ${FILENAME} 131 | ;; 132 | bz2 ) 133 | bzip2 -d ${FILENAME} 134 | ;; 135 | gz ) 136 | gzip -d ${FILENAME} 137 | ;; 138 | * ) 139 | echo "Unsupported archive format: ${FILENAME}" 140 | exit 1 141 | esac 142 | 143 | DIRNAME=$(tar -tf ${FILENAME%.*} | grep '/$' | head -1) 144 | if test -n "${DIRNAME}"; then 145 | rm -rf tmp 146 | mkdir tmp 147 | tar -xvpf ${FILENAME%.*} -C tmp 148 | mv -f tmp/${DIRNAME}/* . 149 | rm -rf tmp 150 | else 151 | tar -xvpf ${FILENAME%.*} 152 | fi 153 | rm -f ${FILENAME%.*} 154 | 155 | echo "4. Done" 156 | -------------------------------------------------------------------------------- /assets/packaging/goproxy-gtk.desktop: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env xdg-open 2 | [Desktop Entry] 3 | Type=Application 4 | Name=GoProxy 5 | Comment=GoProxy GTK Launcher 6 | Categories=Network;Proxy; 7 | Exec=bash -c "export PATH=$PATH:`dirname '%k'`; exec goproxy-gtk.py" 8 | Icon=applications-internet 9 | Terminal=false 10 | StartupNotify=true 11 | -------------------------------------------------------------------------------- /assets/packaging/goproxy-gtk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goproxy0/goproxy/39611867adb372fa95ff8643d4e07cf357320d0e/assets/packaging/goproxy-gtk.png -------------------------------------------------------------------------------- /assets/packaging/goproxy-gui.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goproxy0/goproxy/39611867adb372fa95ff8643d4e07cf357320d0e/assets/packaging/goproxy-gui.exe -------------------------------------------------------------------------------- /assets/packaging/goproxy-macos.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goproxy0/goproxy/39611867adb372fa95ff8643d4e07cf357320d0e/assets/packaging/goproxy-macos.icns -------------------------------------------------------------------------------- /assets/packaging/goproxy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ### BEGIN INIT INFO 4 | # Provides: goproxy 5 | # Required-Start: $network 6 | # Required-Stop: 7 | # Should-Start: 8 | # Should-Stop: 9 | # Default-Start: 2 3 4 5 10 | # Default-Stop: 0 1 6 11 | # Short-Description: start and stop daemon script 12 | # Description: a daemon script 13 | ### END INIT INFO 14 | 15 | set -e 16 | 17 | EXECUTABLE=$(basename "${0}" | sed -r 's/\.[^.]+$//') 18 | PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:${PATH} 19 | SUDO=$(test $(id -u) = 0 || echo sudo) 20 | 21 | linkpath=$(ls -l "$0" | sed "s/.*->\s*//") 22 | cd "$(dirname "$0")" && test -f "$linkpath" && cd "$(dirname "$linkpath")" || true 23 | 24 | start() { 25 | local log_file=${EXECUTABLE}.log 26 | log_file=$(cd $(dirname ${log_file}); echo $(pwd -P)/$(basename ${log_file})) 27 | 28 | test $(ulimit -n) -lt 65535 && ulimit -n 65535 29 | if command -v nohup >/dev/null ; then 30 | nohup ./${EXECUTABLE} >>${log_file} 2>&1 & 31 | else 32 | echo "please install nohup" 33 | exit 1 34 | fi 35 | 36 | local pid=$! 37 | echo -n "Starting ${EXECUTABLE}(${pid}): " 38 | sleep 1 39 | if ls /proc/${pid}/cmdline >/dev/null 2>&1; then 40 | echo "OK" 41 | else 42 | echo "Failed" 43 | fi 44 | 45 | if test -f ${log_file}; then 46 | if test -d /etc/logrotate.d; then 47 | cat < /etc/logrotate.d/${EXECUTABLE} 48 | ${log_file} { 49 | daily 50 | copytruncate 51 | missingok 52 | notifempty 53 | rotate 2 54 | compress 55 | } 56 | EOF 57 | fi 58 | fi 59 | } 60 | 61 | stop() { 62 | for pid in $( (ps ax 2>/dev/null || ps) | awk "/${EXECUTABLE}(\\s|\$)/{print \$1}") 63 | do 64 | local exe=$(ls -l /proc/${pid}/exe 2>/dev/null | sed "s/.*->\s*//" | sed 's/\s*(deleted)\s*//') 65 | local cwd=$(ls -l /proc/${pid}/cwd 2>/dev/null | sed "s/.*->\s*//" | sed 's/\s*(deleted)\s*//') 66 | if test "$(basename "$exe")" = "${EXECUTABLE}" -a "$cwd" = "$(pwd)"; then 67 | echo -n "Stopping ${EXECUTABLE}(${pid}): " 68 | if kill $pid; then 69 | echo "OK" 70 | else 71 | echo "Failed" 72 | fi 73 | fi 74 | done 75 | } 76 | 77 | restart() { 78 | stop 79 | sleep 1 80 | start 81 | } 82 | 83 | autostart() { 84 | if ! command -v crontab >/dev/null ; then 85 | echo "ERROR: please install cron" 86 | fi 87 | (crontab -l | grep -v 'goproxy.sh'; echo "*/1 * * * * pgrep 'goproxy$' >/dev/null || $(pwd)/goproxy.sh start") | crontab - 88 | } 89 | 90 | usage() { 91 | echo "Usage: [sudo] $(basename "$0") {start|stop|restart|autostart}" >&2 92 | exit 1 93 | } 94 | 95 | if [ -n "${SUDO}" ]; then 96 | echo "ERROR: Please run as root" 97 | exit 1 98 | fi 99 | 100 | case "$1" in 101 | start) 102 | start 103 | ;; 104 | stop) 105 | stop 106 | ;; 107 | restart) 108 | restart 109 | ;; 110 | autostart) 111 | autostart 112 | ;; 113 | *) 114 | usage 115 | ;; 116 | esac 117 | 118 | exit $? 119 | -------------------------------------------------------------------------------- /assets/taskbar/make.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | export GOARCH=${GOARCH:-amd64} 6 | 7 | if [ "${GOARCH}" = "amd64" ]; then 8 | BUILDROOT=amd64 9 | WINDRES=x86_64-w64-mingw32-windres 10 | CXX=x86_64-w64-mingw32-g++ 11 | else 12 | BUILDROOT=386 13 | WINDRES=i686-w64-mingw32-windres 14 | CXX=i686-w64-mingw32-g++ 15 | fi 16 | 17 | if ! ${WINDRES} --version >/dev/null; then 18 | WINDRES=windres 19 | fi 20 | 21 | build () { 22 | mkdir -p ${BUILDROOT} 23 | ${WINDRES} taskbar.rc -O coff -o ${BUILDROOT}/taskbar.res 24 | ${CXX} -Wall -Os -s -Wl,--subsystem,windows -o ${BUILDROOT}/taskbar.o -c taskbar.c 25 | ${CXX} -static -Os -s -o ${BUILDROOT}/goproxy-gui.exe ${BUILDROOT}/taskbar.o ${BUILDROOT}/taskbar.res -lwininet 26 | } 27 | 28 | clean () { 29 | rm -rf ${BUILDROOT} 30 | } 31 | 32 | case $1 in 33 | build) 34 | build 35 | ;; 36 | clean) 37 | clean 38 | ;; 39 | *) 40 | build 41 | ;; 42 | esac 43 | -------------------------------------------------------------------------------- /assets/taskbar/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Developer Studio generated include file. 3 | // Used by taskbar.rc 4 | // 5 | #define IDS_CMDLINE 1 6 | #define IDS_ENVIRONMENT 2 7 | #define IDS_PROXYLIST 3 8 | #define IDI_ICON1 101 9 | #define IDI_ICON2 104 10 | #define IDI_TASKBAR 105 11 | #define IDI_SMALL 106 12 | 13 | // Next default values for new objects 14 | // 15 | #ifdef APSTUDIO_INVOKED 16 | #ifndef APSTUDIO_READONLY_SYMBOLS 17 | #define _APS_NEXT_RESOURCE_VALUE 107 18 | #define _APS_NEXT_COMMAND_VALUE 40001 19 | #define _APS_NEXT_CONTROL_VALUE 1000 20 | #define _APS_NEXT_SYMED_VALUE 101 21 | #endif 22 | #endif 23 | -------------------------------------------------------------------------------- /assets/taskbar/small.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goproxy0/goproxy/39611867adb372fa95ff8643d4e07cf357320d0e/assets/taskbar/small.ico -------------------------------------------------------------------------------- /assets/taskbar/taskbar.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goproxy0/goproxy/39611867adb372fa95ff8643d4e07cf357320d0e/assets/taskbar/taskbar.ico -------------------------------------------------------------------------------- /assets/taskbar/taskbar.rc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goproxy0/goproxy/39611867adb372fa95ff8643d4e07cf357320d0e/assets/taskbar/taskbar.rc -------------------------------------------------------------------------------- /assets/test/test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | ) 7 | 8 | func main() { 9 | v, err := net.LookupIP("www.google.com") 10 | fmt.Printf("%+v, err=%#v\n", v, err) 11 | } 12 | -------------------------------------------------------------------------------- /httpproxy/filters/auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "context" 5 | "encoding/base64" 6 | "net" 7 | "net/http" 8 | "runtime" 9 | "strings" 10 | "time" 11 | 12 | "github.com/cloudflare/golibs/lrucache" 13 | "github.com/phuslu/glog" 14 | 15 | "../../filters" 16 | "../../storage" 17 | ) 18 | 19 | const ( 20 | filterName string = "auth" 21 | ) 22 | 23 | type Config struct { 24 | CacheSize int 25 | Basic []struct { 26 | Username string 27 | Password string 28 | } 29 | WhiteList []string 30 | } 31 | 32 | type Filter struct { 33 | Config 34 | AuthCache lrucache.Cache 35 | Basic map[string]string 36 | WhiteList map[string]struct{} 37 | } 38 | 39 | func init() { 40 | filters.Register(filterName, func() (filters.Filter, error) { 41 | filename := filterName + ".json" 42 | config := new(Config) 43 | err := storage.LookupStoreByFilterName(filterName).UnmarshallJson(filename, config) 44 | if err != nil { 45 | glog.Fatalf("UnmarshallJson(%#v) failed: %s", filename, err) 46 | } 47 | return NewFilter(config) 48 | }) 49 | } 50 | 51 | func NewFilter(config *Config) (filters.Filter, error) { 52 | f := &Filter{ 53 | Config: *config, 54 | AuthCache: lrucache.NewMultiLRUCache(uint(runtime.NumCPU()), uint(config.CacheSize)), 55 | Basic: make(map[string]string), 56 | WhiteList: make(map[string]struct{}), 57 | } 58 | 59 | for _, v := range config.Basic { 60 | f.Basic[v.Username] = v.Password 61 | } 62 | 63 | for _, v := range config.WhiteList { 64 | f.WhiteList[v] = struct{}{} 65 | } 66 | 67 | return f, nil 68 | } 69 | 70 | func (f *Filter) FilterName() string { 71 | return filterName 72 | } 73 | 74 | func (f *Filter) Request(ctx context.Context, req *http.Request) (context.Context, *http.Request, error) { 75 | if auth := req.Header.Get("Proxy-Authorization"); auth != "" { 76 | req.Header.Del("Proxy-Authorization") 77 | f.AuthCache.Set(req.RemoteAddr, auth, time.Now().Add(time.Hour)) 78 | } 79 | return ctx, req, nil 80 | } 81 | 82 | func (f *Filter) RoundTrip(ctx context.Context, req *http.Request) (context.Context, *http.Response, error) { 83 | 84 | if ip, _, err := net.SplitHostPort(req.RemoteAddr); err == nil { 85 | if _, ok := f.WhiteList[ip]; ok { 86 | return ctx, nil, nil 87 | } 88 | } 89 | 90 | if auth, ok := f.AuthCache.GetNotStale(req.RemoteAddr); ok && auth != nil { 91 | parts := strings.SplitN(auth.(string), " ", 2) 92 | if len(parts) == 2 { 93 | switch parts[0] { 94 | case "Basic": 95 | if userpass, err := base64.StdEncoding.DecodeString(parts[1]); err == nil { 96 | parts := strings.Split(string(userpass), ":") 97 | user := parts[0] 98 | pass := parts[1] 99 | pass1, ok := f.Basic[user] 100 | if ok && pass == pass1 { 101 | return ctx, nil, nil 102 | } 103 | } 104 | default: 105 | glog.Errorf("Unrecognized auth type: %#v", parts[0]) 106 | break 107 | } 108 | } 109 | } 110 | 111 | glog.V(1).Infof("UnAuthenticated URL %v from %#v", req.URL.String(), req.RemoteAddr) 112 | 113 | noAuthResponse := &http.Response{ 114 | StatusCode: http.StatusProxyAuthRequired, 115 | Header: http.Header{ 116 | "Proxy-Authenticate": []string{"Basic realm=\"GoProxy Authentication Required\""}, 117 | }, 118 | Request: req, 119 | Close: true, 120 | ContentLength: -1, 121 | } 122 | 123 | return ctx, noAuthResponse, nil 124 | } 125 | -------------------------------------------------------------------------------- /httpproxy/filters/auth/auth.json: -------------------------------------------------------------------------------- 1 | { 2 | "CacheSize": 4096, 3 | "Basic": [ 4 | { 5 | "Username": "admin", 6 | "Password": "admin" 7 | } 8 | ], 9 | "WhiteList": [ 10 | "127.0.0.1" 11 | ] 12 | } -------------------------------------------------------------------------------- /httpproxy/filters/autoproxy/17monipdb.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goproxy0/goproxy/39611867adb372fa95ff8643d4e07cf357320d0e/httpproxy/filters/autoproxy/17monipdb.dat -------------------------------------------------------------------------------- /httpproxy/filters/autoproxy/autoproxy.json: -------------------------------------------------------------------------------- 1 | { 2 | "SiteFilters": { 3 | "Enabled": false, 4 | "Rules": { 5 | "live.github.com": "direct", 6 | "api.pureapk.com": "direct", 7 | "download.pureapk.com": "direct", 8 | "dl.winudf.com": "direct", 9 | "pastebin.com": "php", 10 | "rarbg.to": "direct", 11 | "www.rfa.org": "php", 12 | "www.slideshare.net": "php", 13 | }, 14 | }, 15 | "RegionFilters": { 16 | "Enabled": false, 17 | "DataFile": "17monipdb.dat", 18 | "EnableRemoteDNS": false, 19 | "DNSServer": "114.114.114.114", 20 | "DNSCacheSize": 4096, 21 | "Rules": { 22 | "default": "", 23 | "中国": "direct", 24 | "局域网": "direct", 25 | "保留地址": "direct", 26 | }, 27 | "IPRules": { 28 | "108.160.166.92": "", 29 | "110.249.209.42": "", 30 | "118.5.49.6": "", 31 | "120.192.83.163": "", 32 | "123.129.254.12": "", 33 | "123.129.254.13": "", 34 | "123.129.254.14": "", 35 | "123.129.254.15": "", 36 | "125.211.213.132": "", 37 | "128.121.126.139": "", 38 | "159.106.121.75": "", 39 | "169.132.13.103": "", 40 | "183.221.250.11": "", 41 | "185.85.13.155": "", 42 | "188.5.4.96": "", 43 | "189.163.17.5": "", 44 | "192.67.198.6": "", 45 | "197.4.4.12": "", 46 | "202.106.1.2": "", 47 | "202.181.7.85": "", 48 | "202.98.24.122": "", 49 | "202.98.24.124": "", 50 | "202.98.24.125": "", 51 | "203.161.230.171": "", 52 | "203.98.7.65": "", 53 | "207.12.88.98": "", 54 | "208.56.31.43": "", 55 | "209.145.54.50": "", 56 | "209.220.30.174": "", 57 | "209.36.73.33": "", 58 | "211.138.34.204": "", 59 | "211.138.74.132": "", 60 | "211.94.66.147": "", 61 | "211.98.70.195": "", 62 | "211.98.70.226": "", 63 | "211.98.70.227": "", 64 | "211.98.71.195": "", 65 | "213.169.251.35": "", 66 | "216.221.188.182": "", 67 | "216.234.179.13": "", 68 | "218.93.250.18": "", 69 | "220.165.8.172": "", 70 | "220.165.8.174": "", 71 | "220.250.64.20": "", 72 | "221.179.46.190": "", 73 | "23.89.5.60": "", 74 | "243.185.187.39": "", 75 | "249.129.46.48": "", 76 | "253.157.14.165": "", 77 | "31.13.74.40": "", 78 | "37.61.54.158": "", 79 | "4.36.66.178": "", 80 | "42.123.125.237": "", 81 | "46.82.174.68": "", 82 | "49.2.123.56": "", 83 | "54.76.135.1": "", 84 | "59.24.3.173": "", 85 | "60.19.29.22": "", 86 | "61.131.208.210": "", 87 | "61.131.208.211": "", 88 | "64.33.88.161": "", 89 | "64.33.99.47": "", 90 | "64.66.163.251": "", 91 | "65.104.202.252": "", 92 | "65.160.219.113": "", 93 | "66.45.252.237": "", 94 | "72.14.205.104": "", 95 | "72.14.205.99": "", 96 | "74.125.127.113": "", 97 | "77.4.7.92": "", 98 | "78.16.49.15": "", 99 | "8.7.198.45": "", 100 | "92.242.144.2": "", 101 | "93.46.8.89": "", 102 | }, 103 | }, 104 | "IndexFiles": { 105 | "Enabled": true, 106 | "SeverName": "", 107 | "Files": [ 108 | "proxy.pac", 109 | "GoProxyAPN.mobileconfig", 110 | "GoProxy.crt", 111 | "ip.html", 112 | ] 113 | }, 114 | "GFWList": { 115 | "Enabled": true, 116 | "URL": "https://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist.txt", 117 | "File": "gfwlist.txt", 118 | "Encoding": "base64", 119 | "Expiry": 86400, 120 | "Duration": 3600, 121 | }, 122 | "MobileConfig": { 123 | "Enabled": true, 124 | }, 125 | "IPHTML": { 126 | "Enabled": true, 127 | "WhiteList": [ 128 | "127.0.0.1", 129 | "192.168.*.*", 130 | ], 131 | }, 132 | "BlackList": { 133 | "Enabled": false, 134 | "SiteRules": [ 135 | "122.225.103.120", 136 | "139.129.85.85", 137 | "202.102.41.15", 138 | "221.231.6.79", 139 | "61.160.149.75", 140 | "61.160.183.252", 141 | "ad.1111cpc.com", 142 | "ad.xildiere.com", 143 | "btlaunch.baidu.com", 144 | "c.cnzz.com", 145 | "click.hm.baidu.com", 146 | "cm.g.doubleclick.net", 147 | "cm.miaozhen.atm.youku.com", 148 | "cm.pos.baidu.com", 149 | "cpro.baidu.com", 150 | "dl.9xu.com", 151 | "eclick.baidu.com", 152 | "g.ggxt.net", 153 | "googleads.g.doubleclick.net", 154 | "hjskon.com", 155 | "hm.baidu.com", 156 | "imageplus.baidu.com", 157 | "news.766ba.net", 158 | "pagead2.googlesyndication.com", 159 | "pos.baidu.com", 160 | "rwq.youle55.com", 161 | "s*.cnzz.com", 162 | "slb.tiangoutai.com", 163 | "ssp.1111cpc.com", 164 | "static.xihuwoool.cn", 165 | "static.youxiaoad.com", 166 | "t12.baidu.com", 167 | "ubmcmm.baidustatic.com", 168 | "view.adtrident.com", 169 | "wen.pingannian.com", 170 | "wn.pos.baidu.com", 171 | "www.adtrident.com", 172 | "www.assoc-amazon.cn", 173 | "www.clb6.net", 174 | "z*.cnzz.com", 175 | ], 176 | }, 177 | } 178 | -------------------------------------------------------------------------------- /httpproxy/filters/autoproxy/autoproxy_index.go: -------------------------------------------------------------------------------- 1 | package autoproxy 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "html/template" 8 | "io/ioutil" 9 | "mime" 10 | "net" 11 | "net/http" 12 | "path/filepath" 13 | "strings" 14 | 15 | "../../filters" 16 | ) 17 | 18 | func (f *Filter) IndexFilesRoundTrip(ctx context.Context, req *http.Request) (context.Context, *http.Response, error) { 19 | filename := req.URL.Path[1:] 20 | 21 | if filename == "" { 22 | const tpl = ` 23 | 24 | 25 | 26 | 27 | 28 | Index of / 29 | 30 | 31 |

Index of /

32 |
Name

33 |
{{ range $key, $value := .IndexFiles }}
 34 | 📄 {{ $value }}{{ end }}
35 |
{{.Branding}}, remote ip {{.Remote}}
36 | 37 | ` 38 | t, err := template.New("index").Parse(tpl) 39 | if err != nil { 40 | return ctx, nil, err 41 | } 42 | 43 | remote, _, err := net.SplitHostPort(req.RemoteAddr) 44 | if err == nil && f.RegionLocator != nil { 45 | if li, err := f.RegionLocator.Find(remote); err == nil { 46 | regions := []string{li.Country} 47 | for i, r := range []string{li.Region, li.City, li.Isp} { 48 | if r != "" && r != "N/A" && r != regions[i] { 49 | regions = append(regions, r) 50 | } 51 | } 52 | remote = fmt.Sprintf("%s (%s)", remote, strings.Join(regions, " ")) 53 | } 54 | } 55 | 56 | data := struct { 57 | IndexFiles []string 58 | Remote string 59 | Branding string 60 | }{ 61 | IndexFiles: f.IndexFiles, 62 | Remote: remote, 63 | Branding: filters.GetBranding(ctx), 64 | } 65 | 66 | b := new(bytes.Buffer) 67 | err = t.Execute(b, data) 68 | if err != nil { 69 | return ctx, nil, err 70 | } 71 | 72 | return ctx, &http.Response{ 73 | StatusCode: http.StatusOK, 74 | Header: http.Header{ 75 | "Content-Type": []string{"text/html"}, 76 | }, 77 | Request: req, 78 | Close: true, 79 | ContentLength: int64(b.Len()), 80 | Body: ioutil.NopCloser(b), 81 | }, nil 82 | } 83 | 84 | resp, err := f.Store.Get(filename) 85 | if err != nil { 86 | return ctx, nil, err 87 | } 88 | defer resp.Body.Close() 89 | 90 | data, err := ioutil.ReadAll(resp.Body) 91 | if err != nil { 92 | return ctx, nil, err 93 | } 94 | 95 | contentType := mime.TypeByExtension(filepath.Ext(filename)) 96 | if contentType == "" { 97 | contentType = http.DetectContentType(data) 98 | } 99 | 100 | return ctx, &http.Response{ 101 | StatusCode: http.StatusOK, 102 | Header: http.Header{ 103 | "Content-Type": []string{contentType}, 104 | }, 105 | Request: req, 106 | Close: true, 107 | ContentLength: int64(len(data)), 108 | Body: ioutil.NopCloser(bytes.NewReader(data)), 109 | }, nil 110 | } 111 | -------------------------------------------------------------------------------- /httpproxy/filters/autoproxy/autoproxy_iphtml.go: -------------------------------------------------------------------------------- 1 | package autoproxy 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "html/template" 8 | "io/ioutil" 9 | "net" 10 | "net/http" 11 | "strings" 12 | 13 | "../../storage" 14 | ) 15 | 16 | const ( 17 | IPHTMLFilename string = "ip.html" 18 | ) 19 | 20 | func (f *Filter) IPHTMLRoundTrip(ctx context.Context, req *http.Request) (context.Context, *http.Response, error) { 21 | 22 | resp, err := f.Store.Get(IPHTMLFilename) 23 | if err != nil { 24 | return ctx, nil, err 25 | } 26 | defer resp.Body.Close() 27 | 28 | tpl0, err := ioutil.ReadAll(resp.Body) 29 | if err != nil { 30 | return ctx, nil, err 31 | } 32 | 33 | tpl := strings.Replace(string(tpl0), "IPHTMLVISABLE", "1", 1) 34 | 35 | t, err := template.New("ip").Parse(tpl) 36 | if err != nil { 37 | return ctx, nil, err 38 | } 39 | 40 | var msg string 41 | 42 | switch req.Method { 43 | case http.MethodPost: 44 | host, _, err := net.SplitHostPort(req.RemoteAddr) 45 | if err != nil { 46 | return ctx, nil, err 47 | } 48 | ip := net.ParseIP(host) 49 | if ip == nil { 50 | return ctx, nil, fmt.Errorf("Invaild RemoteAddr: %+v", req.RemoteAddr) 51 | } 52 | if !(ip.IsLoopback() || f.IPHTMLWhiteList.Match(host)) { 53 | return ctx, nil, fmt.Errorf("Post from a non-local address: %+v", req.RemoteAddr) 54 | } 55 | 56 | store := storage.LookupStoreByFilterName("gae") 57 | //rawips := req.FormValue("rawips") 58 | jsonips := req.FormValue("jsonips") 59 | filename := "gae.user.json" 60 | if storage.IsNotExist(store.Head(filename)) { 61 | filename = "gae.json" 62 | } 63 | if len(jsonips) > 0 { 64 | s := jsonips 65 | for _, sep := range []string{" ", "\t", "\r", "\n"} { 66 | s = strings.Replace(s, sep, "", -1) 67 | } 68 | 69 | ips := strings.Split(strings.Trim(s, "\","), "\",\"") 70 | jsonips = "\r\n\t\t\t\"" + strings.Join(ips, "\",\r\n\t\t\t\"") + "\",\r\n\t\t" 71 | 72 | resp, err := store.Get(filename) 73 | if err != nil { 74 | return ctx, nil, err 75 | } 76 | defer resp.Body.Close() 77 | 78 | data, err := ioutil.ReadAll(resp.Body) 79 | if err != nil { 80 | return ctx, nil, err 81 | } 82 | 83 | content := string(data) 84 | if n := strings.Index(content, "HostMap"); n > -1 { 85 | tmp := content[n:] 86 | tmp = tmp[strings.Index(tmp, "[")+1 : strings.Index(tmp, "]")] 87 | content = strings.Replace(content, tmp, jsonips, -1) 88 | if _, err = store.Put(filename, http.Header{}, ioutil.NopCloser(strings.NewReader(content))); err != nil { 89 | return ctx, nil, err 90 | } 91 | msg = fmt.Sprintf("Updated %d IP to %s.", len(ips), filename) 92 | } 93 | } 94 | } 95 | data := struct { 96 | Message string 97 | }{ 98 | Message: msg, 99 | } 100 | b := new(bytes.Buffer) 101 | err = t.Execute(b, data) 102 | if err != nil { 103 | return ctx, nil, err 104 | } 105 | 106 | return ctx, &http.Response{ 107 | StatusCode: http.StatusOK, 108 | Header: http.Header{ 109 | "Content-Type": []string{"text/html"}, 110 | }, 111 | Request: req, 112 | Close: true, 113 | ContentLength: int64(b.Len()), 114 | Body: ioutil.NopCloser(b), 115 | }, nil 116 | 117 | } 118 | -------------------------------------------------------------------------------- /httpproxy/filters/autoproxy/autoproxy_mobileconfig.go: -------------------------------------------------------------------------------- 1 | package autoproxy 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io/ioutil" 7 | "net" 8 | "net/http" 9 | "strings" 10 | ) 11 | 12 | func (f *Filter) ProxyMobileConfigRoundTrip(ctx context.Context, req *http.Request) (context.Context, *http.Response, error) { 13 | host, port, err := net.SplitHostPort(req.Host) 14 | if err != nil { 15 | host = req.Host 16 | port = "80" 17 | } 18 | 19 | s := fmt.Sprintf(` 20 | 21 | 22 | 23 | PayloadContent 24 | 25 | 26 | APNs 27 | 28 | 29 | Name 30 | 3gnet 31 | ProxyServer 32 | %s 33 | ProxyPort 34 | %s 35 | 36 | 37 | AttachAPN 38 | 39 | Name 40 | 3gnet 41 | 42 | PayloadDescription 43 | Configures cellular data settings 44 | PayloadDisplayName 45 | Cellular 46 | PayloadIdentifier 47 | com.apple.cellular.50342D3D-1F0A-4AC2-BBBB-F91BE4303D36 48 | PayloadType 49 | com.apple.cellular 50 | PayloadUUID 51 | ED9A04BB-F7C4-451F-B541-1FDBA4BA3715 52 | PayloadVersion 53 | 1 54 | 55 | 56 | PayloadDisplayName 57 | GoProxy APN 58 | PayloadIdentifier 59 | F38E3192-7193-4E00-AD3D-859572FE28E7 60 | PayloadRemovalDisallowed 61 | 62 | PayloadType 63 | Configuration 64 | PayloadUUID 65 | B075216F-63A8-498C-B8CC-8BBE85221FEE 66 | PayloadVersion 67 | 1 68 | 69 | 70 | `, host, port) 71 | 72 | resp := &http.Response{ 73 | StatusCode: http.StatusOK, 74 | Header: http.Header{}, 75 | Request: req, 76 | Close: true, 77 | ContentLength: int64(len(s)), 78 | Body: ioutil.NopCloser(strings.NewReader(s)), 79 | } 80 | 81 | return ctx, resp, nil 82 | } 83 | -------------------------------------------------------------------------------- /httpproxy/filters/autoproxy/ip.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Index of /ip.html 6 |
7 |
8 | 9 | 10 |
11 | IPv4 12 | IPv6 13 |
14 |
15 | 16 |

{{.Message}}

17 |

写入功能仅在通过网络打开时可用 18 |
默认地址 19 |

20 |
21 |
22 | 67 | -------------------------------------------------------------------------------- /httpproxy/filters/autorange/autorange.json: -------------------------------------------------------------------------------- 1 | { 2 | "Sites": [ 3 | "*.ahcdn.com", 4 | "*.atm.youku.com", 5 | "*.c.docs.google.com", 6 | "*.c.youtube.com", 7 | "*.d.rncdn3.com", 8 | "*.edgecastcdn.net", 9 | "*.mms.vlog.xuite.net", 10 | "*.xvideos.com", 11 | "*av.vimeo.com", 12 | "archive.rthk.hk", 13 | "av.voanews.com", 14 | "cdn*.public.extremetube.phncdn.com", 15 | "cdn*.public.tube8.com", 16 | "cdn*.video.pornhub.phncdn.com", 17 | "s*.last.fm", 18 | "smile-*.nicovideo.jp", 19 | "video*.modimovie.com", 20 | "video.*.fbcdn.net", 21 | "videos.flv*.redtubefiles.com", 22 | "vs*.thisav.com", 23 | "x*.last.fm", 24 | ], 25 | "SupportFilters": [ 26 | "autoproxy", 27 | "direct", 28 | "gae", 29 | "php", 30 | "vps", 31 | ], 32 | "MaxSize": 1572864, 33 | "BufSize": 8192, 34 | "Threads": 3, 35 | } -------------------------------------------------------------------------------- /httpproxy/filters/direct/direct.go: -------------------------------------------------------------------------------- 1 | package direct 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "fmt" 7 | "net" 8 | "net/http" 9 | "net/url" 10 | "time" 11 | 12 | "github.com/cloudflare/golibs/lrucache" 13 | "github.com/phuslu/glog" 14 | 15 | "../../filters" 16 | "../../helpers" 17 | "../../proxy" 18 | "../../storage" 19 | ) 20 | 21 | const ( 22 | filterName string = "direct" 23 | ) 24 | 25 | type Config struct { 26 | Transport struct { 27 | Dialer struct { 28 | Timeout int 29 | KeepAlive int 30 | DualStack bool 31 | DNSCacheExpiry int 32 | DNSCacheSize uint 33 | } 34 | Proxy struct { 35 | Enabled bool 36 | URL string 37 | } 38 | TLSClientConfig struct { 39 | InsecureSkipVerify bool 40 | ClientSessionCacheSize int 41 | } 42 | DisableKeepAlives bool 43 | DisableCompression bool 44 | TLSHandshakeTimeout int 45 | MaxIdleConnsPerHost int 46 | } 47 | } 48 | 49 | type Filter struct { 50 | Config 51 | filters.RoundTripFilter 52 | transport *http.Transport 53 | } 54 | 55 | func init() { 56 | filters.Register(filterName, func() (filters.Filter, error) { 57 | filename := filterName + ".json" 58 | config := new(Config) 59 | err := storage.LookupStoreByFilterName(filterName).UnmarshallJson(filename, config) 60 | if err != nil { 61 | glog.Fatalf("storage.ReadJsonConfig(%#v) failed: %s", filename, err) 62 | } 63 | return NewFilter(config) 64 | }) 65 | } 66 | 67 | func NewFilter(config *Config) (filters.Filter, error) { 68 | d := &helpers.Dialer{ 69 | Dialer: &net.Dialer{ 70 | KeepAlive: time.Duration(config.Transport.Dialer.KeepAlive) * time.Second, 71 | Timeout: time.Duration(config.Transport.Dialer.Timeout) * time.Second, 72 | DualStack: config.Transport.Dialer.DualStack, 73 | }, 74 | Resolver: &helpers.Resolver{ 75 | LRUCache: lrucache.NewLRUCache(config.Transport.Dialer.DNSCacheSize), 76 | DNSExpiry: time.Duration(config.Transport.Dialer.DNSCacheExpiry) * time.Second, 77 | BlackList: lrucache.NewLRUCache(1024), 78 | }, 79 | } 80 | 81 | if ips, err := helpers.LocalIPv4s(); err == nil { 82 | for _, ip := range ips { 83 | d.Resolver.BlackList.Set(ip.String(), struct{}{}, time.Time{}) 84 | } 85 | for _, s := range []string{"127.0.0.1", "::1"} { 86 | d.Resolver.BlackList.Set(s, struct{}{}, time.Time{}) 87 | } 88 | } 89 | 90 | tr := &http.Transport{ 91 | Dial: d.Dial, 92 | TLSClientConfig: &tls.Config{ 93 | InsecureSkipVerify: config.Transport.TLSClientConfig.InsecureSkipVerify, 94 | ClientSessionCache: tls.NewLRUClientSessionCache(config.Transport.TLSClientConfig.ClientSessionCacheSize), 95 | }, 96 | TLSHandshakeTimeout: time.Duration(config.Transport.TLSHandshakeTimeout) * time.Second, 97 | MaxIdleConnsPerHost: config.Transport.MaxIdleConnsPerHost, 98 | DisableCompression: config.Transport.DisableCompression, 99 | } 100 | 101 | if config.Transport.Proxy.Enabled { 102 | fixedURL, err := url.Parse(config.Transport.Proxy.URL) 103 | if err != nil { 104 | glog.Fatalf("url.Parse(%#v) error: %s", config.Transport.Proxy.URL, err) 105 | } 106 | 107 | switch fixedURL.Scheme { 108 | case "http", "https": 109 | tr.Proxy = http.ProxyURL(fixedURL) 110 | tr.Dial = nil 111 | tr.DialTLS = nil 112 | default: 113 | dialer, err := proxy.FromURL(fixedURL, d, nil) 114 | if err != nil { 115 | glog.Fatalf("proxy.FromURL(%#v) error: %s", fixedURL.String(), err) 116 | } 117 | 118 | tr.Dial = dialer.Dial 119 | tr.DialTLS = nil 120 | tr.Proxy = nil 121 | } 122 | } 123 | 124 | return &Filter{ 125 | Config: *config, 126 | transport: tr, 127 | }, nil 128 | } 129 | 130 | func (f *Filter) FilterName() string { 131 | return filterName 132 | } 133 | 134 | func (f *Filter) RoundTrip(ctx context.Context, req *http.Request) (context.Context, *http.Response, error) { 135 | switch req.Method { 136 | case "CONNECT": 137 | glog.V(2).Infof("%s \"DIRECT %s %s %s\" - -", req.RemoteAddr, req.Method, req.Host, req.Proto) 138 | rconn, err := f.transport.Dial("tcp", req.Host) 139 | if err != nil { 140 | return ctx, nil, err 141 | } 142 | 143 | rw := filters.GetResponseWriter(ctx) 144 | 145 | hijacker, ok := rw.(http.Hijacker) 146 | if !ok { 147 | return ctx, nil, fmt.Errorf("http.ResponseWriter(%#v) does not implments http.Hijacker", rw) 148 | } 149 | 150 | flusher, ok := rw.(http.Flusher) 151 | if !ok { 152 | return ctx, nil, fmt.Errorf("http.ResponseWriter(%#v) does not implments http.Flusher", rw) 153 | } 154 | 155 | rw.WriteHeader(http.StatusOK) 156 | flusher.Flush() 157 | 158 | lconn, _, err := hijacker.Hijack() 159 | if err != nil { 160 | return ctx, nil, fmt.Errorf("%#v.Hijack() error: %v", hijacker, err) 161 | } 162 | defer lconn.Close() 163 | 164 | go helpers.IOCopy(rconn, lconn) 165 | helpers.IOCopy(lconn, rconn) 166 | 167 | return ctx, filters.DummyResponse, nil 168 | default: 169 | helpers.FixRequestURL(req) 170 | helpers.FixRequestHeader(req) 171 | resp, err := f.transport.RoundTrip(req) 172 | 173 | if err != nil { 174 | return ctx, nil, err 175 | } 176 | 177 | if req.RemoteAddr != "" { 178 | glog.V(2).Infof("%s \"DIRECT %s %s %s\" %d %s", req.RemoteAddr, req.Method, req.URL.String(), req.Proto, resp.StatusCode, resp.Header.Get("Content-Length")) 179 | } 180 | 181 | return ctx, resp, err 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /httpproxy/filters/direct/direct.json: -------------------------------------------------------------------------------- 1 | { 2 | "Transport": { 3 | "Dialer": { 4 | "Timeout": 10, 5 | "KeepAlive": 180, 6 | "DualStack": false, 7 | "DNSCacheExpiry": 600, 8 | "DNSCacheSize": 8192 9 | }, 10 | "Proxy": { 11 | "Enabled": false, 12 | "URL": "socks5://127.0.0.1:1080", 13 | }, 14 | "TLSClientConfig": { 15 | "InsecureSkipVerify": false, 16 | "ClientSessionCacheSize": 1000 17 | }, 18 | "DisableKeepAlives": false, 19 | "DisableCompression": false, 20 | "TLSHandshakeTimeout": 8, 21 | "MaxIdleConnsPerHost": 16 22 | } 23 | } -------------------------------------------------------------------------------- /httpproxy/filters/filters.go: -------------------------------------------------------------------------------- 1 | package filters 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "sync" 7 | ) 8 | 9 | var ( 10 | DummyRequest *http.Request = &http.Request{} 11 | DummyResponse *http.Response = &http.Response{} 12 | ) 13 | 14 | type Filter interface { 15 | FilterName() string 16 | } 17 | 18 | type RequestFilter interface { 19 | Filter 20 | Request(context.Context, *http.Request) (context.Context, *http.Request, error) 21 | } 22 | 23 | type RoundTripFilter interface { 24 | Filter 25 | RoundTrip(context.Context, *http.Request) (context.Context, *http.Response, error) 26 | } 27 | 28 | type ResponseFilter interface { 29 | Filter 30 | Response(context.Context, *http.Response) (context.Context, *http.Response, error) 31 | } 32 | 33 | var ( 34 | mu = new(sync.Mutex) 35 | mm = make(map[string]*sync.Mutex) 36 | fnm = make(map[string]func() (Filter, error)) 37 | fm = make(map[string]Filter) 38 | ) 39 | 40 | func Register(name string, New func() (Filter, error)) { 41 | mu.Lock() 42 | defer mu.Unlock() 43 | if _, ok := fnm[name]; !ok { 44 | fnm[name] = New 45 | fm[name] = nil 46 | mm[name] = new(sync.Mutex) 47 | } 48 | } 49 | 50 | func GetFilter(name string) (Filter, error) { 51 | if f, ok := fm[name]; ok && f != nil { 52 | return f, nil 53 | } 54 | 55 | mu := mm[name] 56 | mu.Lock() 57 | defer mu.Unlock() 58 | 59 | if f, ok := fm[name]; ok && f != nil { 60 | return f, nil 61 | } 62 | 63 | f, err := fnm[name]() 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | fm[name] = f 69 | 70 | return f, nil 71 | 72 | } 73 | -------------------------------------------------------------------------------- /httpproxy/filters/filtersctx.go: -------------------------------------------------------------------------------- 1 | package filters 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "net/http" 7 | ) 8 | 9 | const ( 10 | contextKey int = 0x3f71df90 // fmt.Sprintf("%x", md5.Sum([]byte("phuslu")))[:8] 11 | ) 12 | 13 | type racer struct { 14 | h http.Handler 15 | ln net.Listener 16 | rw http.ResponseWriter 17 | rtf RoundTripFilter 18 | b string 19 | } 20 | 21 | func NewContext(ctx context.Context, h http.Handler, ln net.Listener, rw http.ResponseWriter, brand string) context.Context { 22 | return context.WithValue(ctx, contextKey, &racer{h, ln, rw, nil, brand}) 23 | } 24 | 25 | func GetHandler(ctx context.Context) http.Handler { 26 | return ctx.Value(contextKey).(*racer).h 27 | } 28 | 29 | func GetListener(ctx context.Context) net.Listener { 30 | return ctx.Value(contextKey).(*racer).ln 31 | } 32 | 33 | func GetResponseWriter(ctx context.Context) http.ResponseWriter { 34 | return ctx.Value(contextKey).(*racer).rw 35 | } 36 | 37 | func GetRoundTripFilter(ctx context.Context) RoundTripFilter { 38 | return ctx.Value(contextKey).(*racer).rtf 39 | } 40 | 41 | func GetBranding(ctx context.Context) string { 42 | return ctx.Value(contextKey).(*racer).b 43 | } 44 | 45 | func SetRoundTripFilter(ctx context.Context, filter RoundTripFilter) { 46 | ctx.Value(contextKey).(*racer).rtf = filter 47 | } 48 | 49 | func WithString(ctx context.Context, name, value string) context.Context { 50 | return context.WithValue(ctx, name, value) 51 | } 52 | 53 | func String(ctx context.Context, name string) string { 54 | value := ctx.Value(name) 55 | if value == nil { 56 | return "" 57 | } 58 | 59 | s, ok := value.(string) 60 | if !ok { 61 | return "" 62 | } 63 | 64 | return s 65 | } 66 | 67 | func WithBool(ctx context.Context, name string, value bool) context.Context { 68 | return context.WithValue(ctx, name, value) 69 | } 70 | 71 | func Bool(ctx context.Context, name string) (bool, bool) { 72 | value := ctx.Value(name) 73 | if value == nil { 74 | return false, false 75 | } 76 | 77 | v, ok := value.(bool) 78 | if !ok { 79 | return false, false 80 | } 81 | 82 | return v, true 83 | } 84 | -------------------------------------------------------------------------------- /httpproxy/filters/gae/gae.json: -------------------------------------------------------------------------------- 1 | { 2 | "AppIDs": [ 3 | "goagenta" 4 | ], 5 | "CustomDomains": [ 6 | ], 7 | "Password": "", 8 | "SSLVerify": false, 9 | "DisableIPv6": false, 10 | "ForceIPv6": false, 11 | "DisableHTTP2": true, 12 | "ForceHTTP2": false, 13 | "EnableQuic": false, 14 | "EnableDeadProbe": true, 15 | "EnableRemoteDNS": false, 16 | "HostMap" : { 17 | "google_hk": [ 18 | "mail.google.com", 19 | "mail.l.google.com", 20 | "maps.l.google.com", 21 | "scholar.google.com", 22 | "scholar.google.com.hk", 23 | "www.l.google.com", 24 | ], 25 | "google_cn": [ 26 | "www.g.cn", 27 | "www.google.cn", 28 | ] 29 | }, 30 | "SiteToAlias": { 31 | "*.doubleclick.net": "google_cn", 32 | "*.google-analytics.com": "google_cn", 33 | "*.google.cn": "google_cn", 34 | "*.googlesyndication.com": "google_cn", 35 | "*.googletagmanager.com": "google_cn", 36 | "*.googletagservices.com": "google_cn", 37 | "csi.gstatic.com": "google_cn", 38 | "fonts.gstatic.com": "google_cn", 39 | "dl.google.com": "google_cn", 40 | "fonts.googleapis.com": "google_cn", 41 | "*.appspot.com": "google_hk", 42 | "*.ggpht.com": "google_hk", 43 | "*.google.com": "google_hk", 44 | "*.google.co.jp": "google_hk", 45 | "*.google.co.kr": "google_hk", 46 | "*.google.co.uk": "google_hk", 47 | "*.google.com.hk": "google_hk", 48 | "*.google.com.sg": "google_hk", 49 | "*.google.com.tw": "google_hk", 50 | "*.googleapis.com": "google_hk", 51 | "*.googlecode.com": "google_hk", 52 | "*.googlegroups.com": "google_hk", 53 | "*.googleusercontent.com": "google_hk", 54 | "*.gstatic.com": "google_hk", 55 | "*.ytimg.com": "google_hk", 56 | "upload.youtube.com": "google_hk", 57 | // "*.youtube.com": "google_hk", 58 | }, 59 | "ForceGAE": [ 60 | // "*.drive.google.com", 61 | "appengine.google.com", 62 | "books.google.com", 63 | "business.google.com", 64 | "clients1.google.com", 65 | "clients2.google.com", 66 | "clients3.google.com", 67 | "clients4.google.com", 68 | "domains.google.com", 69 | "plus.google.com/$", 70 | "tools.google.com", 71 | "www.google.com/maps", 72 | "www.google.com/patents", 73 | "youtube.googleapis.com", 74 | ], 75 | "ForceBrotli": [ 76 | ], 77 | "TLSConfig": { 78 | "Version": "TLSv1.2", 79 | "ClientSessionCacheSize": 1024, 80 | "Ciphers": [ 81 | "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", 82 | "TLS_RSA_WITH_AES_128_CBC_SHA256", 83 | "TLS_RSA_WITH_AES_256_CBC_SHA256", 84 | "TLS_RSA_WITH_3DES_EDE_CBC_SHA", 85 | ], 86 | "ServerName": [ 87 | "download.windowsupdate.com", 88 | "www.apple.com", 89 | "www.bing.com", 90 | "www.microsoft.com", 91 | // "googleads.g.doubleclick.net", 92 | // "pubads.g.doubleclick.net", 93 | // "www.google-analytics.com", 94 | // "ad.doubleclick.net", 95 | // "appleid.apple.com", 96 | // "hkg12s09-in-f14.1e100.net", 97 | // "hkg12s09-in-f17.1e100.net", 98 | // "hkg12s09-in-f3.1e100.net", 99 | // "hkg12s09-in-f4.1e100.net", 100 | ], 101 | }, 102 | "GoogleG2PKP": "7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=", 103 | "GoogleG3PKP": "f8NnEFZxQ4ExFOhSN7EiFWtiudZQVD2oY60uauV/n78=", 104 | "FakeOptions": { 105 | "*": [ 106 | "Access-Control-Allow-Credentials: true", 107 | "Access-Control-Allow-Headers: Accept, Authorization, Content-Type, If-Modified-Since", 108 | "Access-Control-Allow-Methods: GET, POST, HEAD, PUT, DELETE, OPTIONS, PATCH", 109 | "Access-Control-Allow-Origin: *", 110 | "Access-Control-Expose-Headers: Cache-Control, Content-Encoding, Content-Length, Content-Type, Date, Expires, Server, Vary, X-Google-GFE-Backend-Request-Cost, X-FB-Debug, X-Loader-Length", 111 | "Access-Control-Max-Age: 1728000", 112 | "Status: 200", 113 | "Vary: Origin", 114 | "Vary: X-Origin", 115 | ], 116 | }, 117 | "DNSServers": [ 118 | "114.114.114.114", 119 | "114.114.115.115", 120 | "8.8.4.4", 121 | "8.8.8.8", 122 | "2001:4860:4860::8844", 123 | "2001:4860:4860::8888", 124 | "2001:470:20::2", 125 | ], 126 | "IPBlackList": [ 127 | "159.106.121.75", 128 | "203.98.7.65", 129 | "243.185.187.39", 130 | "37.61.54.158", 131 | "59.24.3.173", 132 | "46.82.174.68", 133 | "78.16.49.15", 134 | "8.7.198.45", 135 | "93.46.8.89", 136 | ], 137 | "Transport": { 138 | "Dialer": { 139 | "DNSCacheExpiry": 864000, 140 | "DNSCacheSize": 8192, 141 | "SocketReadBuffer": 0, 142 | "DualStack": false, 143 | "KeepAlive": 5, 144 | "Level": 20, 145 | "Timeout": 5, 146 | }, 147 | "Proxy": { 148 | "Enabled": false, 149 | "URL": "socks5://127.0.0.1:1080", 150 | }, 151 | "DisableKeepAlives": false, 152 | "IdleConnTimeout": 5, 153 | "MaxIdleConnsPerHost": 32, 154 | "ResponseHeaderTimeout": 16, 155 | "RetryDelay": 0.5, 156 | "RetryTimes": 3, 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /httpproxy/filters/gae/gaeserver.go: -------------------------------------------------------------------------------- 1 | package gae 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "compress/flate" 7 | "encoding/binary" 8 | "fmt" 9 | "io" 10 | "io/ioutil" 11 | "math/rand" 12 | "net/http" 13 | "net/url" 14 | "strings" 15 | "sync" 16 | "sync/atomic" 17 | "time" 18 | 19 | "github.com/phuslu/glog" 20 | 21 | "../../helpers" 22 | ) 23 | 24 | type Servers struct { 25 | curURL atomic.Value 26 | muURL sync.RWMutex 27 | urls1 []url.URL 28 | urls2 []url.URL 29 | password string 30 | sslVerify bool 31 | } 32 | 33 | func NewServers(urls []url.URL, password string, sslVerify bool) *Servers { 34 | server := &Servers{ 35 | urls1: urls, 36 | urls2: []url.URL{}, 37 | password: password, 38 | sslVerify: sslVerify, 39 | } 40 | server.curURL.Store(server.urls1[0]) 41 | return server 42 | } 43 | 44 | func (s *Servers) ToggleBadServer(fetchserver url.URL) { 45 | s.muURL.Lock() 46 | defer s.muURL.Unlock() 47 | urls := []url.URL{} 48 | for _, u := range s.urls1 { 49 | if u.Host != fetchserver.Host { 50 | urls = append(urls, u) 51 | } 52 | } 53 | s.urls1 = urls 54 | s.urls2 = append(s.urls2, fetchserver) 55 | if len(s.urls1) == 0 { 56 | s.urls1, s.urls2 = s.urls2, s.urls1 57 | rand.Shuffle(len(s.urls1), func(i int, j int) { 58 | s.urls1[i], s.urls1[j] = s.urls1[j], s.urls1[i] 59 | }) 60 | } 61 | s.curURL.Store(s.urls1[0]) 62 | } 63 | 64 | func (s *Servers) EncodeRequest(req *http.Request, fetchserver url.URL, deadline time.Duration, brotli bool) (*http.Request, error) { 65 | var err error 66 | var b bytes.Buffer 67 | 68 | helpers.FixRequestURL(req) 69 | 70 | w, err := flate.NewWriter(&b, flate.BestCompression) 71 | if err != nil { 72 | return nil, err 73 | } 74 | 75 | options := "" 76 | if deadline > 0 { 77 | options = fmt.Sprintf("deadline=%d", deadline/time.Second) 78 | } 79 | if brotli { 80 | options += ",brotli" 81 | } 82 | if s.password != "" { 83 | options += ",password=" + s.password 84 | } 85 | if s.sslVerify { 86 | options += ",sslverify" 87 | } 88 | 89 | fmt.Fprintf(w, "%s %s HTTP/1.1\r\n", req.Method, req.URL.String()) 90 | fmt.Fprintf(w, "X-Urlfetch-Options: %s\r\n", options) 91 | req.Header.WriteSubset(w, helpers.ReqWriteExcludeHeader) 92 | w.Close() 93 | 94 | b0 := make([]byte, 2) 95 | binary.BigEndian.PutUint16(b0, uint16(b.Len())) 96 | 97 | req1 := &http.Request{ 98 | Method: http.MethodPost, 99 | URL: &fetchserver, 100 | Host: fetchserver.Host, 101 | Header: http.Header{ 102 | "User-Agent": []string{""}, 103 | }, 104 | } 105 | 106 | if req.ContentLength > 0 { 107 | req1.ContentLength = int64(len(b0)+b.Len()) + req.ContentLength 108 | req1.Body = helpers.NewMultiReadCloser(bytes.NewReader(b0), &b, req.Body) 109 | } else { 110 | req1.ContentLength = int64(len(b0) + b.Len()) 111 | req1.Body = helpers.NewMultiReadCloser(bytes.NewReader(b0), &b) 112 | } 113 | 114 | return req1, nil 115 | } 116 | 117 | func (s *Servers) DecodeResponse(resp *http.Response) (resp1 *http.Response, err error) { 118 | if resp.StatusCode != http.StatusOK { 119 | return resp, nil 120 | } 121 | 122 | var hdrLen uint16 123 | if err = binary.Read(resp.Body, binary.BigEndian, &hdrLen); err != nil { 124 | return 125 | } 126 | 127 | hdrBuf := make([]byte, hdrLen) 128 | if _, err = io.ReadFull(resp.Body, hdrBuf); err != nil { 129 | return 130 | } 131 | 132 | resp1, err = http.ReadResponse(bufio.NewReader(flate.NewReader(bytes.NewReader(hdrBuf))), resp.Request) 133 | if err != nil { 134 | return 135 | } 136 | 137 | const cookieKey string = "Set-Cookie" 138 | if cookies, ok := resp1.Header[cookieKey]; ok && len(cookies) == 1 { 139 | parts := strings.Split(cookies[0], ", ") 140 | 141 | parts1 := make([]string, 0) 142 | for i := 0; i < len(parts); i++ { 143 | c := parts[i] 144 | if i == 0 || strings.Contains(strings.Split(c, ";")[0], "=") { 145 | parts1 = append(parts1, c) 146 | } else { 147 | parts1[len(parts1)-1] = parts1[len(parts1)-1] + ", " + c 148 | } 149 | } 150 | 151 | if len(parts1) > 1 { 152 | glog.Warningf("FetchServer(%+v) is not a goproxy GAE server, please upgrade!", resp.Request.Host) 153 | resp1.Header.Del(cookieKey) 154 | for i := 0; i < len(parts1); i++ { 155 | resp1.Header.Add(cookieKey, parts1[i]) 156 | } 157 | } 158 | } 159 | 160 | if resp1.StatusCode >= http.StatusBadRequest { 161 | switch { 162 | case resp.Body == nil: 163 | break 164 | case resp1.Body == nil: 165 | resp1.Body = resp.Body 166 | default: 167 | b, _ := ioutil.ReadAll(resp1.Body) 168 | if b != nil && len(b) > 0 { 169 | resp1.Body = helpers.NewMultiReadCloser(bytes.NewReader(b), resp.Body) 170 | } else { 171 | resp1.Body = resp.Body 172 | } 173 | } 174 | } else { 175 | resp1.Body = resp.Body 176 | } 177 | 178 | return 179 | } 180 | 181 | func (s *Servers) PickFetchServer(req *http.Request, base int) url.URL { 182 | perfer := !helpers.IsStaticRequest(req) 183 | 184 | if base > 0 { 185 | perfer = false 186 | } 187 | 188 | if req.Method == http.MethodPost { 189 | perfer = true 190 | } 191 | 192 | if perfer { 193 | return s.curURL.Load().(url.URL) 194 | } else { 195 | s.muURL.RLock() 196 | defer s.muURL.RUnlock() 197 | return s.urls1[rand.Intn(len(s.urls1))] 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /httpproxy/filters/php/php.go: -------------------------------------------------------------------------------- 1 | package php 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "net" 7 | "net/http" 8 | "net/url" 9 | "time" 10 | 11 | "github.com/cloudflare/golibs/lrucache" 12 | "github.com/phuslu/glog" 13 | "github.com/phuslu/net/http2" 14 | 15 | "../../filters" 16 | "../../helpers" 17 | "../../proxy" 18 | "../../storage" 19 | ) 20 | 21 | const ( 22 | filterName string = "php" 23 | ) 24 | 25 | type Config struct { 26 | Servers []struct { 27 | URL string 28 | Password string 29 | SSLVerify bool 30 | Host string 31 | } 32 | Transport struct { 33 | Dialer struct { 34 | Timeout int 35 | KeepAlive int 36 | DualStack bool 37 | DNSCacheExpiry int 38 | DNSCacheSize uint 39 | } 40 | Proxy struct { 41 | Enabled bool 42 | URL string 43 | } 44 | DisableKeepAlives bool 45 | DisableCompression bool 46 | TLSHandshakeTimeout int 47 | MaxIdleConnsPerHost int 48 | } 49 | } 50 | 51 | type Filter struct { 52 | Config 53 | Transport *Transport 54 | } 55 | 56 | func init() { 57 | filters.Register(filterName, func() (filters.Filter, error) { 58 | filename := filterName + ".json" 59 | config := new(Config) 60 | err := storage.LookupStoreByFilterName(filterName).UnmarshallJson(filename, config) 61 | if err != nil { 62 | glog.Fatalf("storage.ReadJsonConfig(%#v) failed: %s", filename, err) 63 | } 64 | return NewFilter(config) 65 | }) 66 | } 67 | 68 | func NewFilter(config *Config) (filters.Filter, error) { 69 | servers := make([]Server, 0) 70 | for _, s := range config.Servers { 71 | u, err := url.Parse(s.URL) 72 | if err != nil { 73 | return nil, err 74 | } 75 | 76 | server := Server{ 77 | URL: u, 78 | Password: s.Password, 79 | SSLVerify: s.SSLVerify, 80 | Host: s.Host, 81 | } 82 | 83 | servers = append(servers, server) 84 | } 85 | 86 | d0 := &net.Dialer{ 87 | KeepAlive: time.Duration(config.Transport.Dialer.KeepAlive) * time.Second, 88 | Timeout: time.Duration(config.Transport.Dialer.Timeout) * time.Second, 89 | DualStack: config.Transport.Dialer.DualStack, 90 | } 91 | 92 | d := &helpers.Dialer{ 93 | Dialer: d0, 94 | Resolver: &helpers.Resolver{ 95 | LRUCache: lrucache.NewLRUCache(config.Transport.Dialer.DNSCacheSize), 96 | DNSExpiry: time.Duration(config.Transport.Dialer.DNSCacheExpiry) * time.Second, 97 | }, 98 | Level: 2, 99 | } 100 | 101 | for _, server := range servers { 102 | if server.Host != "" { 103 | d.Resolver.LRUCache.Set(server.URL.Hostname(), server.Host, time.Time{}) 104 | } 105 | } 106 | 107 | tr := &http.Transport{ 108 | Dial: d.Dial, 109 | TLSClientConfig: &tls.Config{ 110 | InsecureSkipVerify: false, 111 | ClientSessionCache: tls.NewLRUClientSessionCache(1000), 112 | }, 113 | TLSHandshakeTimeout: time.Duration(config.Transport.TLSHandshakeTimeout) * time.Second, 114 | MaxIdleConnsPerHost: config.Transport.MaxIdleConnsPerHost, 115 | } 116 | 117 | if config.Transport.Proxy.Enabled { 118 | fixedURL, err := url.Parse(config.Transport.Proxy.URL) 119 | if err != nil { 120 | glog.Fatalf("url.Parse(%#v) error: %s", config.Transport.Proxy.URL, err) 121 | } 122 | 123 | switch fixedURL.Scheme { 124 | case "http", "https": 125 | tr.Proxy = http.ProxyURL(fixedURL) 126 | tr.Dial = nil 127 | tr.DialTLS = nil 128 | default: 129 | dialer, err := proxy.FromURL(fixedURL, d, nil) 130 | if err != nil { 131 | glog.Fatalf("proxy.FromURL(%#v) error: %s", fixedURL.String(), err) 132 | } 133 | 134 | tr.Dial = dialer.Dial 135 | tr.DialTLS = nil 136 | tr.Proxy = nil 137 | } 138 | } 139 | 140 | if tr.TLSClientConfig != nil { 141 | err := http2.ConfigureTransport(tr) 142 | if err != nil { 143 | glog.Warningf("PHP: Error enabling Transport HTTP/2 support: %v", err) 144 | } 145 | } 146 | 147 | return &Filter{ 148 | Config: *config, 149 | Transport: &Transport{ 150 | RoundTripper: tr, 151 | Servers: servers, 152 | }, 153 | }, nil 154 | } 155 | 156 | func (p *Filter) FilterName() string { 157 | return filterName 158 | } 159 | 160 | func (f *Filter) RoundTrip(ctx context.Context, req *http.Request) (context.Context, *http.Response, error) { 161 | resp, err := f.Transport.RoundTrip(req) 162 | if err != nil { 163 | return ctx, nil, err 164 | } else { 165 | glog.V(2).Infof("%s \"PHP %s %s %s\" %d %s", req.RemoteAddr, req.Method, req.URL.String(), req.Proto, resp.StatusCode, resp.Header.Get("Content-Length")) 166 | } 167 | return ctx, resp, nil 168 | } 169 | -------------------------------------------------------------------------------- /httpproxy/filters/php/php.json: -------------------------------------------------------------------------------- 1 | { 2 | "Servers": [ 3 | { 4 | "URL": "https://example.com/", 5 | "Password": "123456", 6 | "SSLVerify": false, 7 | "Host": "", 8 | } 9 | ], 10 | "Transport": { 11 | "Dialer": { 12 | "Timeout": 10, 13 | "KeepAlive": 180, 14 | "DualStack": false, 15 | "DNSCacheExpiry": 3600, 16 | "DNSCacheSize": 8192 17 | }, 18 | "Proxy": { 19 | "Enabled": false, 20 | "URL": "http://127.0.0.1:8087", 21 | }, 22 | "DisableKeepAlives": false, 23 | "DisableCompression": false, 24 | "TLSHandshakeTimeout": 4, 25 | "MaxIdleConnsPerHost": 16 26 | } 27 | } -------------------------------------------------------------------------------- /httpproxy/filters/php/phpserver.go: -------------------------------------------------------------------------------- 1 | package php 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "compress/flate" 7 | "encoding/binary" 8 | "fmt" 9 | "io" 10 | "net/http" 11 | "net/url" 12 | 13 | "../../helpers" 14 | ) 15 | 16 | type Server struct { 17 | URL *url.URL 18 | Password string 19 | SSLVerify bool 20 | Host string 21 | } 22 | 23 | func (s *Server) encodeRequest(req *http.Request) (*http.Request, error) { 24 | var err error 25 | var b bytes.Buffer 26 | 27 | helpers.FixRequestURL(req) 28 | 29 | w, err := flate.NewWriter(&b, flate.BestCompression) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | fmt.Fprintf(w, "%s %s HTTP/1.1\r\n", req.Method, req.URL.String()) 35 | req.Header.WriteSubset(w, helpers.ReqWriteExcludeHeader) 36 | fmt.Fprintf(w, "X-Urlfetch-Password: %s\r\n", s.Password) 37 | if s.URL.Scheme == "https" { 38 | io.WriteString(w, "X-Urlfetch-Https: 1\r\n") 39 | } 40 | if s.SSLVerify { 41 | io.WriteString(w, "X-Urlfetch-SSLVerify: 1\r\n") 42 | } 43 | io.WriteString(w, "\r\n") 44 | if err != nil { 45 | return nil, err 46 | } 47 | w.Close() 48 | 49 | b0 := make([]byte, 2) 50 | binary.BigEndian.PutUint16(b0, uint16(b.Len())) 51 | 52 | req1 := &http.Request{ 53 | Method: http.MethodPost, 54 | URL: s.URL, 55 | Host: s.URL.Host, 56 | Header: http.Header{}, 57 | } 58 | 59 | if req1.URL.Scheme == "https" { 60 | req1.Header.Set("User-Agent", "a") 61 | } 62 | 63 | if s.URL.Scheme == "http" { 64 | for _, key := range []string{"User-Agent", "Accept", "Accept-Encoding", "Accept-Language"} { 65 | if value := req.Header.Get(key); value != "" { 66 | req1.Header.Set(key, value) 67 | } 68 | } 69 | } 70 | 71 | if req.ContentLength > 0 { 72 | req1.ContentLength = int64(len(b0)+b.Len()) + req.ContentLength 73 | req1.Body = helpers.NewMultiReadCloser(bytes.NewReader(b0), &b, req.Body) 74 | } else { 75 | req1.ContentLength = int64(len(b0) + b.Len()) 76 | req1.Body = helpers.NewMultiReadCloser(bytes.NewReader(b0), &b) 77 | } 78 | 79 | return req1, nil 80 | } 81 | 82 | func (s *Server) decodeResponse(resp *http.Response) (resp1 *http.Response, err error) { 83 | if resp.StatusCode != http.StatusOK { 84 | return resp, nil 85 | } 86 | 87 | if s.Password != "" && resp.Header.Get("Content-Type") == "image/gif" && resp.Body != nil { 88 | resp.Body = helpers.NewXorReadCloser(resp.Body, []byte(s.Password)) 89 | } 90 | 91 | resp, err = http.ReadResponse(bufio.NewReader(resp.Body), resp.Request) 92 | return resp, err 93 | } 94 | -------------------------------------------------------------------------------- /httpproxy/filters/php/phptransport.go: -------------------------------------------------------------------------------- 1 | package php 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "net/http" 7 | 8 | "../../helpers" 9 | ) 10 | 11 | type Transport struct { 12 | http.RoundTripper 13 | Servers []Server 14 | } 15 | 16 | func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) { 17 | i := 0 18 | 19 | if helpers.IsStaticRequest(req) { 20 | i = rand.Intn(len(t.Servers)) 21 | } 22 | 23 | server := t.Servers[i] 24 | 25 | req1, err := server.encodeRequest(req) 26 | if err != nil { 27 | return nil, fmt.Errorf("PHP encodeRequest: %s", err.Error()) 28 | } 29 | 30 | res, err := t.RoundTripper.RoundTrip(req1) 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | resp, err := server.decodeResponse(res) 36 | return resp, err 37 | } 38 | -------------------------------------------------------------------------------- /httpproxy/filters/rewrite/rewrite.go: -------------------------------------------------------------------------------- 1 | package rewrite 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | 7 | "github.com/phuslu/glog" 8 | 9 | "../../filters" 10 | "../../storage" 11 | ) 12 | 13 | const ( 14 | filterName string = "rewrite" 15 | ) 16 | 17 | type Config struct { 18 | UserAgent struct { 19 | Enabled bool 20 | Value string 21 | } 22 | Host struct { 23 | Enabled bool 24 | RewriteBy string 25 | } 26 | } 27 | 28 | type Filter struct { 29 | Config 30 | UserAgentEnabled bool 31 | UserAgentValue string 32 | HostEnabled bool 33 | HostRewriteBy string 34 | } 35 | 36 | func init() { 37 | filters.Register(filterName, func() (filters.Filter, error) { 38 | filename := filterName + ".json" 39 | config := new(Config) 40 | err := storage.LookupStoreByFilterName(filterName).UnmarshallJson(filename, config) 41 | if err != nil { 42 | glog.Fatalf("storage.ReadJsonConfig(%#v) failed: %s", filename, err) 43 | } 44 | return NewFilter(config) 45 | }) 46 | } 47 | 48 | func NewFilter(config *Config) (filters.Filter, error) { 49 | f := &Filter{ 50 | Config: *config, 51 | UserAgentEnabled: config.UserAgent.Enabled, 52 | UserAgentValue: config.UserAgent.Value, 53 | HostEnabled: config.Host.Enabled, 54 | HostRewriteBy: config.Host.RewriteBy, 55 | } 56 | 57 | return f, nil 58 | } 59 | 60 | func (f *Filter) FilterName() string { 61 | return filterName 62 | } 63 | 64 | func (f *Filter) Request(ctx context.Context, req *http.Request) (context.Context, *http.Request, error) { 65 | if f.UserAgentEnabled { 66 | glog.V(3).Infof("REWRITE %#v User-Agent=%#v", req.URL.String(), f.UserAgentValue) 67 | req.Header.Set("User-Agent", f.UserAgentValue) 68 | } 69 | 70 | if f.HostEnabled { 71 | if host := req.Header.Get(f.HostRewriteBy); host != "" { 72 | glog.V(3).Infof("REWRITE %#v Host=%#v", req.URL.String(), host) 73 | req.Host = host 74 | req.Header.Set("Host", req.Host) 75 | req.Header.Del(f.HostRewriteBy) 76 | } 77 | } 78 | 79 | return ctx, req, nil 80 | } 81 | 82 | func (f *Filter) Response(ctx context.Context, resp *http.Response) (context.Context, *http.Response, error) { 83 | return ctx, resp, nil 84 | } 85 | -------------------------------------------------------------------------------- /httpproxy/filters/rewrite/rewrite.json: -------------------------------------------------------------------------------- 1 | { 2 | "UserAgent": { 3 | "Enabled": false, 4 | "Value": "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36", 5 | }, 6 | "Host": { 7 | "Enabled": false, 8 | "RewriteBy": "X-Online-Host", 9 | } 10 | } -------------------------------------------------------------------------------- /httpproxy/filters/ssh2/ssh2.go: -------------------------------------------------------------------------------- 1 | package ssh2 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "fmt" 7 | "net/http" 8 | "time" 9 | 10 | "github.com/cloudflare/golibs/lrucache" 11 | "github.com/phuslu/glog" 12 | "golang.org/x/crypto/ssh" 13 | 14 | "../../filters" 15 | "../../helpers" 16 | "../../storage" 17 | ) 18 | 19 | const ( 20 | filterName string = "ssh2" 21 | ) 22 | 23 | type Config struct { 24 | Servers []struct { 25 | Addr string 26 | Username string 27 | Password string 28 | } 29 | Transport struct { 30 | DisableKeepAlives bool 31 | DisableCompression bool 32 | TLSHandshakeTimeout int 33 | MaxIdleConnsPerHost int 34 | } 35 | } 36 | 37 | type Filter struct { 38 | Config 39 | Transport *http.Transport 40 | SSHClientCache lrucache.Cache 41 | } 42 | 43 | func init() { 44 | filters.Register(filterName, func() (filters.Filter, error) { 45 | filename := filterName + ".json" 46 | config := new(Config) 47 | err := storage.LookupStoreByFilterName(filterName).UnmarshallJson(filename, config) 48 | if err != nil { 49 | glog.Fatalf("storage.ReadJsonConfig(%#v) failed: %s", filename, err) 50 | } 51 | return NewFilter(config) 52 | }) 53 | } 54 | 55 | func NewFilter(config *Config) (filters.Filter, error) { 56 | ss := &Servers{ 57 | servers: make([]Server, 0), 58 | sshClients: lrucache.NewLRUCache(uint(len(config.Servers))), 59 | } 60 | 61 | for _, s := range config.Servers { 62 | server := Server{ 63 | Address: s.Addr, 64 | ClientConfig: &ssh.ClientConfig{ 65 | User: s.Username, 66 | Auth: []ssh.AuthMethod{ 67 | ssh.Password(s.Password), 68 | }, 69 | }, 70 | } 71 | 72 | ss.servers = append(ss.servers, server) 73 | } 74 | 75 | tr := &http.Transport{ 76 | Dial: ss.Dial, 77 | TLSClientConfig: &tls.Config{ 78 | InsecureSkipVerify: false, 79 | ClientSessionCache: tls.NewLRUClientSessionCache(1000), 80 | }, 81 | TLSHandshakeTimeout: time.Duration(config.Transport.TLSHandshakeTimeout) * time.Second, 82 | MaxIdleConnsPerHost: config.Transport.MaxIdleConnsPerHost, 83 | } 84 | 85 | return &Filter{ 86 | Config: *config, 87 | Transport: tr, 88 | }, nil 89 | } 90 | 91 | func (p *Filter) FilterName() string { 92 | return filterName 93 | } 94 | 95 | func (f *Filter) RoundTrip(ctx context.Context, req *http.Request) (context.Context, *http.Response, error) { 96 | switch req.Method { 97 | case "CONNECT": 98 | glog.V(2).Infof("%s \"SSH2 %s %s %s\" - -", req.RemoteAddr, req.Method, req.Host, req.Proto) 99 | rconn, err := f.Transport.Dial("tcp", req.Host) 100 | if err != nil { 101 | return ctx, nil, err 102 | } 103 | 104 | rw := filters.GetResponseWriter(ctx) 105 | 106 | hijacker, ok := rw.(http.Hijacker) 107 | if !ok { 108 | return ctx, nil, fmt.Errorf("http.ResponseWriter(%#v) does not implments http.Hijacker", rw) 109 | } 110 | 111 | flusher, ok := rw.(http.Flusher) 112 | if !ok { 113 | return ctx, nil, fmt.Errorf("http.ResponseWriter(%#v) does not implments http.Flusher", rw) 114 | } 115 | 116 | rw.WriteHeader(http.StatusOK) 117 | flusher.Flush() 118 | 119 | lconn, _, err := hijacker.Hijack() 120 | if err != nil { 121 | return ctx, nil, fmt.Errorf("%#v.Hijack() error: %v", hijacker, err) 122 | } 123 | defer lconn.Close() 124 | 125 | go helpers.IOCopy(rconn, lconn) 126 | helpers.IOCopy(lconn, rconn) 127 | 128 | return ctx, filters.DummyResponse, nil 129 | default: 130 | helpers.FixRequestURL(req) 131 | resp, err := f.Transport.RoundTrip(req) 132 | 133 | if err != nil { 134 | return ctx, nil, err 135 | } 136 | 137 | if req.RemoteAddr != "" { 138 | glog.V(2).Infof("%s \"SSH2 %s %s %s\" %d %s", req.RemoteAddr, req.Method, req.URL.String(), req.Proto, resp.StatusCode, resp.Header.Get("Content-Length")) 139 | } 140 | 141 | return ctx, resp, err 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /httpproxy/filters/ssh2/ssh2.json: -------------------------------------------------------------------------------- 1 | { 2 | "Servers": [ 3 | { 4 | "Addr": "example.com:22", 5 | "Username": "user", 6 | "Password": "123456", 7 | } 8 | ], 9 | "Transport": { 10 | "DisableKeepAlives": false, 11 | "DisableCompression": false, 12 | "TLSHandshakeTimeout": 4, 13 | "MaxIdleConnsPerHost": 16 14 | } 15 | } -------------------------------------------------------------------------------- /httpproxy/filters/ssh2/ssh2server.go: -------------------------------------------------------------------------------- 1 | package ssh2 2 | 3 | import ( 4 | "net" 5 | "runtime" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/cloudflare/golibs/lrucache" 10 | "golang.org/x/crypto/ssh" 11 | ) 12 | 13 | type Server struct { 14 | Address string 15 | ClientConfig *ssh.ClientConfig 16 | } 17 | 18 | type Servers struct { 19 | servers []Server 20 | sshClients lrucache.Cache 21 | } 22 | 23 | func (ss *Servers) Dial(network, address string) (net.Conn, error) { 24 | var err error 25 | 26 | i := 0 27 | c, ok := ss.sshClients.Get(strconv.Itoa(i)) 28 | if !ok { 29 | c, err = ssh.Dial(network, ss.servers[i].Address, ss.servers[i].ClientConfig) 30 | if err != nil { 31 | return nil, err 32 | } 33 | runtime.SetFinalizer(c, func(c *ssh.Client) { c.Close() }) 34 | ss.sshClients.Set(strconv.Itoa(i), c, time.Time{}) 35 | } 36 | 37 | conn := c.(*ssh.Client) 38 | 39 | host, portStr, err := net.SplitHostPort(address) 40 | if err != nil { 41 | return nil, err 42 | } 43 | port, err := strconv.Atoi(portStr) 44 | if err != nil { 45 | return nil, err 46 | } 47 | ips, err := net.LookupIP(host) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | raddr := &net.TCPAddr{IP: ips[0], Port: port} 53 | 54 | return conn.DialTCP(network, nil, raddr) 55 | } 56 | -------------------------------------------------------------------------------- /httpproxy/filters/stripssl/stripssl.go: -------------------------------------------------------------------------------- 1 | package stripssl 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "fmt" 7 | "io" 8 | "net" 9 | "net/http" 10 | "strconv" 11 | "sync" 12 | "time" 13 | 14 | "github.com/cloudflare/golibs/lrucache" 15 | "github.com/phuslu/glog" 16 | 17 | "../../filters" 18 | "../../helpers" 19 | "../../storage" 20 | ) 21 | 22 | const ( 23 | filterName string = "stripssl" 24 | ) 25 | 26 | type Config struct { 27 | TLSVersion string 28 | RootCA struct { 29 | Filename string 30 | Dirname string 31 | Name string 32 | Duration int 33 | Portable bool 34 | } 35 | Ports []int 36 | Ignores []string 37 | Sites []string 38 | } 39 | 40 | type Filter struct { 41 | Config 42 | CA *RootCA 43 | CAExpiry time.Duration 44 | TLSMaxVersion uint16 45 | TLSConfigCache lrucache.Cache 46 | Ports map[string]struct{} 47 | Ignores map[string]struct{} 48 | Sites *helpers.HostMatcher 49 | } 50 | 51 | func init() { 52 | filters.Register(filterName, func() (filters.Filter, error) { 53 | filename := filterName + ".json" 54 | config := new(Config) 55 | err := storage.LookupStoreByFilterName(filterName).UnmarshallJson(filename, config) 56 | if err != nil { 57 | glog.Fatalf("storage.ReadJsonConfig(%#v) failed: %s", filename, err) 58 | } 59 | return NewFilter(config) 60 | }) 61 | } 62 | 63 | var ( 64 | defaultCA *RootCA 65 | onceCA sync.Once 66 | ) 67 | 68 | func NewFilter(config *Config) (_ filters.Filter, err error) { 69 | onceCA.Do(func() { 70 | defaultCA, err = NewRootCA(config.RootCA.Name, 71 | time.Duration(config.RootCA.Duration)*time.Second, 72 | config.RootCA.Dirname, 73 | config.RootCA.Portable) 74 | if err != nil { 75 | glog.Fatalf("NewRootCA(%#v) error: %v", config.RootCA.Name, err) 76 | } 77 | }) 78 | 79 | f := &Filter{ 80 | Config: *config, 81 | TLSMaxVersion: tls.VersionTLS12, 82 | CA: defaultCA, 83 | CAExpiry: time.Duration(config.RootCA.Duration) * time.Second, 84 | TLSConfigCache: lrucache.NewMultiLRUCache(4, 4096), 85 | Ports: make(map[string]struct{}), 86 | Ignores: make(map[string]struct{}), 87 | Sites: helpers.NewHostMatcher(config.Sites), 88 | } 89 | 90 | if v := helpers.TLSVersion(config.TLSVersion); v != 0 { 91 | f.TLSMaxVersion = v 92 | } 93 | 94 | for _, port := range config.Ports { 95 | f.Ports[strconv.Itoa(port)] = struct{}{} 96 | } 97 | 98 | for _, ignore := range config.Ignores { 99 | f.Ignores[ignore] = struct{}{} 100 | } 101 | 102 | return f, nil 103 | } 104 | 105 | func (f *Filter) FilterName() string { 106 | return filterName 107 | } 108 | 109 | func (f *Filter) Request(ctx context.Context, req *http.Request) (context.Context, *http.Request, error) { 110 | if req.Method != http.MethodConnect { 111 | return ctx, req, nil 112 | } 113 | 114 | if f1 := filters.GetRoundTripFilter(ctx); f1 != nil { 115 | if _, ok := f.Ignores[f1.FilterName()]; ok { 116 | return ctx, req, nil 117 | } 118 | } 119 | 120 | host, port, err := net.SplitHostPort(req.RequestURI) 121 | if err != nil { 122 | return ctx, req, nil 123 | } 124 | 125 | if !f.Sites.Match(host) { 126 | return ctx, req, nil 127 | } 128 | 129 | needStripSSL := true 130 | if _, ok := f.Ports[port]; !ok { 131 | needStripSSL = false 132 | } 133 | 134 | rw := filters.GetResponseWriter(ctx) 135 | hijacker, ok := rw.(http.Hijacker) 136 | if !ok { 137 | return ctx, nil, fmt.Errorf("%#v does not implments Hijacker", rw) 138 | } 139 | 140 | conn, _, err := hijacker.Hijack() 141 | if err != nil { 142 | return ctx, nil, fmt.Errorf("http.ResponseWriter Hijack failed: %s", err) 143 | } 144 | 145 | _, err = io.WriteString(conn, "HTTP/1.1 200 OK\r\n\r\n") 146 | if err != nil { 147 | conn.Close() 148 | return ctx, nil, err 149 | } 150 | 151 | glog.V(2).Infof("%s \"STRIP %s %s %s\" - -", req.RemoteAddr, req.Method, req.Host, req.Proto) 152 | 153 | var c net.Conn = conn 154 | if needStripSSL { 155 | GetConfigForClient := func(hello *tls.ClientHelloInfo) (*tls.Config, error) { 156 | host := req.Host 157 | 158 | if h, _, err := net.SplitHostPort(host); err == nil { 159 | host = h 160 | } 161 | 162 | name := GetCommonName(host) 163 | ecc := helpers.HasECCCiphers(hello.CipherSuites) 164 | 165 | var cacheKey string 166 | if ecc { 167 | cacheKey = name 168 | } else { 169 | cacheKey = name + ",rsa" 170 | } 171 | 172 | var config interface{} 173 | var ok bool 174 | if config, ok = f.TLSConfigCache.Get(cacheKey); !ok { 175 | cert, err := f.CA.Issue(name, f.CAExpiry, ecc) 176 | if err != nil { 177 | return nil, err 178 | } 179 | config = &tls.Config{ 180 | Certificates: []tls.Certificate{*cert}, 181 | MaxVersion: f.TLSMaxVersion, 182 | MinVersion: tls.VersionTLS10, 183 | PreferServerCipherSuites: true, 184 | } 185 | f.TLSConfigCache.Set(cacheKey, config, time.Now().Add(7*24*time.Hour)) 186 | } 187 | return config.(*tls.Config), nil 188 | } 189 | 190 | config := &tls.Config{ 191 | GetConfigForClient: GetConfigForClient, 192 | } 193 | 194 | tlsConn := tls.Server(conn, config) 195 | 196 | if err := tlsConn.Handshake(); err != nil { 197 | glog.V(2).Infof("%s %T.Handshake() error: %#v", req.RemoteAddr, tlsConn, err) 198 | conn.Close() 199 | return ctx, nil, err 200 | } 201 | 202 | c = tlsConn 203 | } 204 | 205 | if ln1, ok := filters.GetListener(ctx).(helpers.Listener); ok { 206 | ln1.Add(c) 207 | return ctx, filters.DummyRequest, nil 208 | } 209 | 210 | loConn, err := net.Dial("tcp", filters.GetListener(ctx).Addr().String()) 211 | if err != nil { 212 | return ctx, nil, err 213 | } 214 | 215 | go helpers.IOCopy(loConn, c) 216 | go helpers.IOCopy(c, loConn) 217 | 218 | return ctx, filters.DummyRequest, nil 219 | } 220 | -------------------------------------------------------------------------------- /httpproxy/filters/stripssl/stripssl.json: -------------------------------------------------------------------------------- 1 | { 2 | "TLSVersion": "TLSv1.2", 3 | "RootCA": { 4 | "Name": "GoProxy", 5 | "Dirname": "cache", 6 | "Duration": 31536000, 7 | "Portable": true, 8 | }, 9 | "Ports": [ 10 | 443, 11 | 7443, 12 | 8443, 13 | 9443, 14 | 10443, 15 | 20443, 16 | ], 17 | "Ignores": [ 18 | "direct", 19 | "vps", 20 | ], 21 | "Sites": [ 22 | "*" 23 | ] 24 | } -------------------------------------------------------------------------------- /httpproxy/filters/vps/vps.go: -------------------------------------------------------------------------------- 1 | package vps 2 | 3 | import ( 4 | "context" 5 | // "fmt" 6 | "math/rand" 7 | "net/http" 8 | "net/url" 9 | 10 | "github.com/phuslu/glog" 11 | "github.com/phuslu/net/http2" 12 | 13 | "../../filters" 14 | "../../helpers" 15 | "../../storage" 16 | ) 17 | 18 | const ( 19 | filterName string = "vps" 20 | ) 21 | 22 | type Config struct { 23 | Servers []struct { 24 | URL string 25 | Username string 26 | Password string 27 | SSLVerify bool 28 | } 29 | } 30 | 31 | type Filter struct { 32 | Servers []*Server 33 | Sites *helpers.HostMatcher 34 | } 35 | 36 | func init() { 37 | filters.Register(filterName, func() (filters.Filter, error) { 38 | filename := filterName + ".json" 39 | config := new(Config) 40 | err := storage.LookupStoreByFilterName(filterName).UnmarshallJson(filename, config) 41 | if err != nil { 42 | glog.Fatalf("storage.ReadJsonConfig(%#v) failed: %s", filename, err) 43 | } 44 | return NewFilter(config) 45 | }) 46 | } 47 | 48 | func NewFilter(config *Config) (filters.Filter, error) { 49 | servers := make([]*Server, 0) 50 | for _, fs := range config.Servers { 51 | u, err := url.Parse(fs.URL) 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | transport := &http2.Transport{} 57 | 58 | fs := &Server{ 59 | URL: u, 60 | Username: fs.Username, 61 | Password: fs.Password, 62 | SSLVerify: fs.SSLVerify, 63 | Transport: transport, 64 | } 65 | 66 | servers = append(servers, fs) 67 | } 68 | 69 | return &Filter{ 70 | Servers: servers, 71 | }, nil 72 | } 73 | 74 | func (p *Filter) FilterName() string { 75 | return filterName 76 | } 77 | 78 | func (f *Filter) RoundTrip(ctx context.Context, req *http.Request) (context.Context, *http.Response, error) { 79 | i := 0 80 | if helpers.IsStaticRequest(req) { 81 | i = rand.Intn(len(f.Servers)) 82 | } 83 | 84 | server := f.Servers[i] 85 | 86 | // if req.Method == "CONNECT" { 87 | // rconn, err := server.Transport.Connect(req) 88 | // if err != nil { 89 | // return ctx, nil, err 90 | // } 91 | // defer rconn.Close() 92 | 93 | // rw := ctx.GetResponseWriter() 94 | 95 | // hijacker, ok := rw.(http.Hijacker) 96 | // if !ok { 97 | // return ctx, nil, fmt.Errorf("http.ResponseWriter(%#v) does not implments http.Hijacker", rw) 98 | // } 99 | 100 | // flusher, ok := rw.(http.Flusher) 101 | // if !ok { 102 | // return ctx, nil, fmt.Errorf("http.ResponseWriter(%#v) does not implments http.Flusher", rw) 103 | // } 104 | 105 | // rw.WriteHeader(http.StatusOK) 106 | // flusher.Flush() 107 | 108 | // lconn, _, err := hijacker.Hijack() 109 | // if err != nil { 110 | // return ctx, nil, fmt.Errorf("%#v.Hijack() error: %v", hijacker, err) 111 | // } 112 | // defer lconn.Close() 113 | 114 | // go helpers.IOCopy(rconn, lconn) 115 | // helpers.IOCopy(lconn, rconn) 116 | 117 | // ctx.Hijack(true) 118 | // return ctx, nil, nil 119 | // } 120 | resp, err := server.RoundTrip(req) 121 | if err != nil { 122 | return ctx, nil, err 123 | } else { 124 | glog.V(2).Infof("%s \"VPS %s %s %s\" %d %s", req.RemoteAddr, req.Method, req.URL.String(), req.Proto, resp.StatusCode, resp.Header.Get("Content-Length")) 125 | } 126 | return ctx, resp, err 127 | } 128 | -------------------------------------------------------------------------------- /httpproxy/filters/vps/vps.json: -------------------------------------------------------------------------------- 1 | { 2 | "Servers": [ 3 | { 4 | "Url": "https://127.0.0.1:443/", 5 | "Username": "test", 6 | "Password": "123456", 7 | "SSLVerify": false 8 | } 9 | ], 10 | } -------------------------------------------------------------------------------- /httpproxy/filters/vps/vpsserver.go: -------------------------------------------------------------------------------- 1 | package vps 2 | 3 | import ( 4 | "encoding/base64" 5 | "net" 6 | "net/http" 7 | "net/url" 8 | 9 | "github.com/phuslu/net/http2" 10 | ) 11 | 12 | var ( 13 | reqWriteExcludeHeader = map[string]bool{ 14 | "Vary": true, 15 | "Via": true, 16 | "X-Forwarded-For": true, 17 | "Proxy-Authorization": true, 18 | "Proxy-Connection": true, 19 | "Upgrade": true, 20 | "X-Chrome-Variations": true, 21 | "Connection": true, 22 | "Cache-Control": true, 23 | } 24 | ) 25 | 26 | type Server struct { 27 | URL *url.URL 28 | Username string 29 | Password string 30 | SSLVerify bool 31 | Transport *http2.Transport 32 | } 33 | 34 | func (f *Server) RoundTrip(req *http.Request) (resp *http.Response, err error) { 35 | for key, shouldDelete := range reqWriteExcludeHeader { 36 | if shouldDelete && req.Header.Get(key) != "" { 37 | req.Header.Del(key) 38 | } 39 | } 40 | 41 | req.Header.Set("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(f.Username+":"+f.Password))) 42 | 43 | resp, err = f.Transport.RoundTrip(req) 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | return resp, nil 49 | } 50 | 51 | func (f *Server) Connect(req *http.Request) (conn net.Conn, err error) { 52 | return nil, nil 53 | } 54 | -------------------------------------------------------------------------------- /httpproxy/handler.go: -------------------------------------------------------------------------------- 1 | package httpproxy 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "net" 8 | "net/http" 9 | "os" 10 | "runtime" 11 | "strconv" 12 | "strings" 13 | "syscall" 14 | 15 | "github.com/phuslu/glog" 16 | 17 | "./filters" 18 | "./helpers" 19 | ) 20 | 21 | type Handler struct { 22 | Listener helpers.Listener 23 | RequestFilters []filters.RequestFilter 24 | RoundTripFilters []filters.RoundTripFilter 25 | ResponseFilters []filters.ResponseFilter 26 | Branding string 27 | } 28 | 29 | func (h Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { 30 | var err error 31 | 32 | remoteAddr := req.RemoteAddr 33 | 34 | // Prepare filter.Context 35 | ctx := filters.NewContext(req.Context(), h, h.Listener, rw, h.Branding) 36 | req = req.WithContext(ctx) 37 | 38 | // Enable transport http proxy 39 | if req.Method != "CONNECT" && !req.URL.IsAbs() { 40 | if req.URL.Scheme == "" { 41 | if req.TLS != nil && req.ProtoMajor == 1 { 42 | req.URL.Scheme = "https" 43 | } else { 44 | req.URL.Scheme = "http" 45 | } 46 | } 47 | 48 | if req.TLS != nil { 49 | if req.Host == "" { 50 | if req.URL.Host != "" { 51 | req.Host = req.URL.Host 52 | } else { 53 | req.Host = req.TLS.ServerName 54 | } 55 | } 56 | if req.URL.Host == "" { 57 | if req.Host != "" { 58 | req.URL.Host = req.Host 59 | } else { 60 | req.URL.Host = req.TLS.ServerName 61 | } 62 | } 63 | } 64 | } 65 | 66 | // Filter Request 67 | for _, f := range h.RequestFilters { 68 | ctx, req, err = f.Request(ctx, req) 69 | if req == filters.DummyRequest { 70 | return 71 | } 72 | if err != nil { 73 | if err != io.EOF { 74 | glog.Errorf("%s Filter Request %T error: %+v", remoteAddr, f, err) 75 | } 76 | return 77 | } 78 | // Update context for request 79 | req = req.WithContext(ctx) 80 | } 81 | 82 | if req.Body != nil { 83 | defer req.Body.Close() 84 | } 85 | 86 | // Filter Request -> Response 87 | var resp *http.Response 88 | for _, f := range h.RoundTripFilters { 89 | ctx, resp, err = f.RoundTrip(ctx, req) 90 | if resp == filters.DummyResponse { 91 | return 92 | } 93 | // Unexcepted errors 94 | if err != nil { 95 | filters.SetRoundTripFilter(ctx, f) 96 | glog.Errorf("%s Filter RoundTrip %T error: %+v", remoteAddr, f, err) 97 | http.Error(rw, h.FormatError(ctx, err), http.StatusBadGateway) 98 | return 99 | } 100 | // Update context for request 101 | req = req.WithContext(ctx) 102 | // A roundtrip filter give a response 103 | if resp != nil { 104 | resp.Request = req 105 | filters.SetRoundTripFilter(ctx, f) 106 | break 107 | } 108 | } 109 | 110 | // Filter Response 111 | for _, f := range h.ResponseFilters { 112 | if resp == nil || resp == filters.DummyResponse { 113 | return 114 | } 115 | ctx, resp, err = f.Response(ctx, resp) 116 | if err != nil { 117 | glog.Errorln("%s Filter %T Response error: %+v", remoteAddr, f, err) 118 | http.Error(rw, h.FormatError(ctx, err), http.StatusBadGateway) 119 | return 120 | } 121 | // Update context for request 122 | req = req.WithContext(ctx) 123 | } 124 | 125 | if resp == nil { 126 | glog.Errorln("%s Handler %#v Response empty response", remoteAddr, h) 127 | http.Error(rw, h.FormatError(ctx, fmt.Errorf("empty response")), http.StatusBadGateway) 128 | return 129 | } 130 | 131 | if resp.Header.Get("Content-Length") == "" && resp.ContentLength >= 0 { 132 | resp.Header.Set("Content-Length", strconv.FormatInt(resp.ContentLength, 10)) 133 | } 134 | for key, values := range resp.Header { 135 | for _, value := range values { 136 | rw.Header().Add(key, value) 137 | } 138 | } 139 | rw.WriteHeader(resp.StatusCode) 140 | if resp.Body != nil { 141 | defer resp.Body.Close() 142 | n, err := helpers.IOCopy(rw, resp.Body) 143 | if err != nil { 144 | if isClosedConnError(err) { 145 | glog.Infof("IOCopy %#v return %#v %T(%v)", resp.Body, n, err, err) 146 | } else { 147 | glog.Warningf("IOCopy %#v return %#v %T(%v)", resp.Body, n, err, err) 148 | } 149 | if oe, ok := resp.Body.(interface { 150 | OnError(err error) 151 | }); ok { 152 | glog.Warningf("%#v.OnError(%+v) called.", resp.Body, err) 153 | oe.OnError(err) 154 | } 155 | } 156 | } 157 | } 158 | 159 | func (h Handler) FormatError(ctx context.Context, err error) string { 160 | return fmt.Sprintf(`{ 161 | "type": "localproxy", 162 | "host": "%s", 163 | "software": "%s (go/%s %s/%s)", 164 | "filter": "%T", 165 | "error": "%s" 166 | } 167 | `, filters.GetListener(ctx).Addr().String(), 168 | h.Branding, runtime.Version(), runtime.GOOS, runtime.GOARCH, 169 | filters.GetRoundTripFilter(ctx), 170 | err.Error()) 171 | } 172 | 173 | func isClosedConnError(err error) bool { 174 | if err == nil { 175 | return false 176 | } 177 | 178 | str := err.Error() 179 | if strings.Contains(str, "use of closed network connection") { 180 | return true 181 | } 182 | 183 | if runtime.GOOS == "windows" { 184 | const WSAECONNABORTED = 10053 185 | const WSAECONNRESET = 10054 186 | if oe, ok := err.(*net.OpError); ok && (oe.Op == "read" || oe.Op == "write") { 187 | if se, ok := oe.Err.(*os.SyscallError); ok && (se.Syscall == "wsarecv" || se.Syscall == "wsasend") { 188 | if n, ok := se.Err.(syscall.Errno); ok { 189 | if n == WSAECONNRESET || n == WSAECONNABORTED { 190 | return true 191 | } 192 | } 193 | } 194 | } 195 | } 196 | return false 197 | } 198 | -------------------------------------------------------------------------------- /httpproxy/helpers/algothrim.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "math/rand" 5 | ) 6 | 7 | func ShuffleStringsN(slice []string, n int) { 8 | l := len(slice) 9 | 10 | if n <= 0 || n > l { 11 | panic("ShuffleStringsN n larger than len(slice)") 12 | } 13 | 14 | for i := 0; i < n; i++ { 15 | j := rand.Intn(l - i) 16 | slice[j], slice[n-i-1] = slice[n-i-1], slice[j] 17 | } 18 | } 19 | 20 | func ShuffleInts(slice []int) { 21 | for i := range slice { 22 | j := rand.Intn(i + 1) 23 | slice[i], slice[j] = slice[j], slice[i] 24 | } 25 | } 26 | 27 | func ShuffleUints(slice []uint) { 28 | for i := range slice { 29 | j := rand.Intn(i + 1) 30 | slice[i], slice[j] = slice[j], slice[i] 31 | } 32 | } 33 | 34 | func ShuffleUint16s(slice []uint16) { 35 | for i := range slice { 36 | j := rand.Intn(i + 1) 37 | slice[i], slice[j] = slice[j], slice[i] 38 | } 39 | } 40 | 41 | func ContainsString(slice []string, s string) bool { 42 | for _, s1 := range slice { 43 | if s == s1 { 44 | return true 45 | } 46 | } 47 | return false 48 | } 49 | 50 | func UniqueStrings(slice []string) []string { 51 | ss := make([]string, 0) 52 | m := map[string]struct{}{} 53 | for _, s := range slice { 54 | if _, ok := m[s]; ok { 55 | continue 56 | } 57 | m[s] = struct{}{} 58 | ss = append(ss, s) 59 | } 60 | return ss 61 | } 62 | -------------------------------------------------------------------------------- /httpproxy/helpers/console.go: -------------------------------------------------------------------------------- 1 | // +build !windows,!linux,!darwin,!freebsd 2 | 3 | package helpers 4 | 5 | func SetConsoleTitle(name string) { 6 | } 7 | 8 | func SetConsoleTextColorRed() error { 9 | return nil 10 | } 11 | 12 | func SetConsoleTextColorYellow() error { 13 | return nil 14 | } 15 | 16 | func SetConsoleTextColorGreen() error { 17 | return nil 18 | } 19 | 20 | func SetConsoleTextColorReset() error { 21 | return nil 22 | } 23 | -------------------------------------------------------------------------------- /httpproxy/helpers/console_posix.go: -------------------------------------------------------------------------------- 1 | // +build linux darwin freebsd 2 | 3 | package helpers 4 | 5 | import ( 6 | "os" 7 | ) 8 | 9 | var ( 10 | posixConsoleTextColorRed []byte = []byte("\033[31m") 11 | posixConsoleTextColorYellow []byte = []byte("\033[33m") 12 | posixConsoleTextColorGreen []byte = []byte("\033[32m") 13 | posixConsoleTextColorReset []byte = []byte("\033[0m") 14 | ) 15 | 16 | func SetConsoleTitle(name string) { 17 | os.Stdout.WriteString("\x1b]2;" + name + "\x07") 18 | } 19 | 20 | func SetConsoleTextColorRed() error { 21 | _, err := os.Stderr.Write(posixConsoleTextColorRed) 22 | return err 23 | } 24 | 25 | func SetConsoleTextColorYellow() error { 26 | _, err := os.Stderr.Write(posixConsoleTextColorYellow) 27 | return err 28 | } 29 | 30 | func SetConsoleTextColorGreen() error { 31 | _, err := os.Stderr.Write(posixConsoleTextColorGreen) 32 | return err 33 | } 34 | 35 | func SetConsoleTextColorReset() error { 36 | _, err := os.Stderr.Write(posixConsoleTextColorReset) 37 | return err 38 | } 39 | -------------------------------------------------------------------------------- /httpproxy/helpers/console_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package helpers 4 | 5 | import ( 6 | "os" 7 | "syscall" 8 | "unsafe" 9 | ) 10 | 11 | var ( 12 | kernel32 = syscall.NewLazyDLL("kernel32.dll") 13 | procSetConsoleTitleW = kernel32.NewProc("SetConsoleTitleW") 14 | procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute") 15 | hStderr = os.Stderr.Fd() 16 | ) 17 | 18 | func SetConsoleTitle(name string) { 19 | procSetConsoleTitleW.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(name)))) 20 | } 21 | 22 | func setConsoleTextAttribute(attr uint) error { 23 | _, _, err := procSetConsoleTextAttribute.Call(hStderr, uintptr(attr)) 24 | return err 25 | } 26 | 27 | func SetConsoleTextColorRed() error { 28 | return setConsoleTextAttribute(0x04) 29 | } 30 | 31 | func SetConsoleTextColorYellow() error { 32 | return setConsoleTextAttribute(0x06) 33 | } 34 | 35 | func SetConsoleTextColorGreen() error { 36 | return setConsoleTextAttribute(0x02) 37 | } 38 | 39 | func SetConsoleTextColorReset() error { 40 | return setConsoleTextAttribute(0x07) 41 | } 42 | -------------------------------------------------------------------------------- /httpproxy/helpers/dialer.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | "github.com/phuslu/glog" 8 | ) 9 | 10 | type Dialer struct { 11 | Dialer interface { 12 | Dial(network, addr string) (net.Conn, error) 13 | } 14 | 15 | Resolver *Resolver 16 | Level int 17 | } 18 | 19 | func (d *Dialer) Dial(network, address string) (conn net.Conn, err error) { 20 | glog.V(3).Infof("Dail(%#v, %#v)", network, address) 21 | 22 | switch network { 23 | case "tcp", "tcp4", "tcp6": 24 | if d.Resolver != nil { 25 | if host, port, err := net.SplitHostPort(address); err == nil { 26 | if ips, err := d.Resolver.LookupIP(host); err == nil { 27 | if len(ips) == 0 { 28 | return nil, net.InvalidAddrError(fmt.Sprintf("Invaid DNS Record: %s", address)) 29 | } 30 | return d.dialMulti(network, address, ips, port) 31 | } 32 | } 33 | } 34 | } 35 | 36 | return d.Dialer.Dial(network, address) 37 | } 38 | 39 | func (d *Dialer) dialMulti(network, address string, ips []net.IP, port string) (conn net.Conn, err error) { 40 | if d.Level <= 1 || len(ips) == 1 { 41 | for i, ip := range ips { 42 | addr := net.JoinHostPort(ip.String(), port) 43 | conn, err := d.Dialer.Dial(network, addr) 44 | if err != nil { 45 | if i < len(ips)-1 { 46 | continue 47 | } else { 48 | return nil, err 49 | } 50 | } 51 | return conn, nil 52 | } 53 | } else { 54 | type racer struct { 55 | c net.Conn 56 | e error 57 | } 58 | 59 | level := len(ips) 60 | if level > d.Level { 61 | level = d.Level 62 | ips = ips[:level] 63 | } 64 | 65 | lane := make(chan racer, level) 66 | for i := 0; i < level; i++ { 67 | go func(addr string, c chan<- racer) { 68 | conn, err := d.Dialer.Dial(network, addr) 69 | lane <- racer{conn, err} 70 | }(net.JoinHostPort(ips[i].String(), port), lane) 71 | } 72 | 73 | var r racer 74 | for j := 0; j < level; j++ { 75 | r = <-lane 76 | if r.e == nil { 77 | go func(count int) { 78 | var r1 racer 79 | for ; count > 0; count-- { 80 | r1 = <-lane 81 | if r1.c != nil { 82 | r1.c.Close() 83 | } 84 | } 85 | }(level - 1 - j) 86 | return r.c, nil 87 | } 88 | } 89 | } 90 | 91 | return nil, net.UnknownNetworkError("Unkown transport/direct error") 92 | } 93 | -------------------------------------------------------------------------------- /httpproxy/helpers/fragment.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "container/heap" 5 | "fmt" 6 | "io" 7 | "sync" 8 | "sync/atomic" 9 | ) 10 | 11 | type fragment struct { 12 | pos int64 13 | data []byte 14 | } 15 | 16 | type fragmentHeap []*fragment 17 | 18 | func (h fragmentHeap) Len() int { return len(h) } 19 | func (h fragmentHeap) Less(i, j int) bool { return h[i].pos < h[j].pos } 20 | func (h fragmentHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } 21 | 22 | func (h *fragmentHeap) Push(x interface{}) { 23 | *h = append(*h, x.(*fragment)) 24 | } 25 | 26 | func (h *fragmentHeap) Pop() interface{} { 27 | old := *h 28 | n := len(old) 29 | x := old[n-1] 30 | *h = old[0 : n-1] 31 | return x 32 | } 33 | 34 | type FragmentPipe interface { 35 | io.ReadCloser 36 | io.WriterTo 37 | CloseWithError(err error) error 38 | Write(data []byte, pos int64) (int, error) 39 | WriteString(data string, pos int64) (int, error) 40 | } 41 | 42 | type fragmentPipe struct { 43 | length int64 44 | pos int64 45 | err atomic.Value 46 | size int64 47 | heap fragmentHeap 48 | mu *sync.Mutex 49 | token chan struct{} 50 | } 51 | 52 | func NewFragmentPipe(size int64) FragmentPipe { 53 | return &fragmentPipe{ 54 | pos: 0, 55 | size: size, 56 | heap: []*fragment{}, 57 | mu: new(sync.Mutex), 58 | token: make(chan struct{}, 1024), 59 | } 60 | } 61 | 62 | func (p *fragmentPipe) Len() int64 { 63 | return atomic.LoadInt64(&p.length) 64 | } 65 | 66 | func (p *fragmentPipe) WriteString(data string, pos int64) (int, error) { 67 | return p.Write([]byte(data), pos) 68 | } 69 | 70 | func (p *fragmentPipe) Write(data []byte, pos int64) (int, error) { 71 | if err := p.err.Load(); err != nil { 72 | return 0, err.(error) 73 | } 74 | 75 | p.mu.Lock() 76 | defer p.mu.Unlock() 77 | 78 | heap.Push(&p.heap, &fragment{pos, data}) 79 | atomic.AddInt64(&p.length, int64(len(data))) 80 | if pos == atomic.LoadInt64(&p.pos) { 81 | p.token <- struct{}{} 82 | } 83 | return len(data), nil 84 | } 85 | 86 | func (p *fragmentPipe) Read(data []byte) (int, error) { 87 | if err := p.err.Load(); err != nil { 88 | return 0, err.(error) 89 | } 90 | 91 | if atomic.LoadInt64(&p.pos) == p.size { 92 | p.Close() 93 | return 0, nil 94 | } 95 | 96 | <-p.token 97 | 98 | if err := p.err.Load(); err != nil { 99 | return 0, err.(error) 100 | } 101 | 102 | p.mu.Lock() 103 | defer p.mu.Unlock() 104 | 105 | top := p.heap[0] 106 | if atomic.LoadInt64(&p.pos) != top.pos { 107 | err := fmt.Errorf("%T.pos=%d is not equal to %T.pos=%d", top, top.pos, p, atomic.LoadInt64(&p.pos)) 108 | defer p.CloseWithError(err) 109 | return 0, err 110 | } 111 | 112 | n := copy(data, top.data) 113 | atomic.AddInt64(&p.length, -int64(n)) 114 | atomic.AddInt64(&p.pos, int64(n)) 115 | if n < len(top.data) { 116 | top.pos += int64(n) 117 | top.data = top.data[n:] 118 | p.token <- struct{}{} 119 | } else { 120 | heap.Pop(&p.heap) 121 | if len(p.heap) > 0 && p.heap[0].pos == atomic.LoadInt64(&p.pos) { 122 | p.token <- struct{}{} 123 | } 124 | } 125 | return n, nil 126 | } 127 | 128 | func (p *fragmentPipe) Close() error { 129 | defer p.CloseWithError(io.EOF) 130 | return nil 131 | } 132 | 133 | func (p *fragmentPipe) CloseWithError(err error) error { 134 | if err == nil { 135 | err = io.ErrClosedPipe 136 | } 137 | p.err.Store(err) 138 | p.token <- struct{}{} 139 | return nil 140 | } 141 | 142 | func (p *fragmentPipe) writeTo(w io.Writer) (int64, error) { 143 | if err := p.err.Load(); err != nil { 144 | return 0, err.(error) 145 | } 146 | 147 | <-p.token 148 | 149 | if err := p.err.Load(); err != nil { 150 | return 0, err.(error) 151 | } 152 | 153 | p.mu.Lock() 154 | defer p.mu.Unlock() 155 | 156 | top := p.heap[0] 157 | if atomic.LoadInt64(&p.pos) != top.pos { 158 | err := fmt.Errorf("%T.pos=%d is not equal to %T.pos=%d", top, top.pos, p, atomic.LoadInt64(&p.pos)) 159 | defer p.CloseWithError(err) 160 | return 0, err 161 | } 162 | 163 | n, err := w.Write(top.data) 164 | atomic.AddInt64(&p.length, -int64(n)) 165 | atomic.AddInt64(&p.pos, int64(n)) 166 | if err != nil { 167 | defer p.CloseWithError(err) 168 | return int64(n), err 169 | } 170 | 171 | if n < len(top.data) { 172 | top.pos += int64(n) 173 | top.data = top.data[n:] 174 | p.token <- struct{}{} 175 | } else { 176 | heap.Pop(&p.heap) 177 | if len(p.heap) > 0 && p.heap[0].pos == atomic.LoadInt64(&p.pos) { 178 | p.token <- struct{}{} 179 | } 180 | } 181 | 182 | return int64(n), nil 183 | } 184 | 185 | func (p *fragmentPipe) WriteTo(w io.Writer) (int64, error) { 186 | var size, n int64 187 | var err error 188 | for err == nil && size < p.size { 189 | n, err = p.writeTo(w) 190 | size += n 191 | } 192 | return size, err 193 | } 194 | -------------------------------------------------------------------------------- /httpproxy/helpers/fragment_read_test.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestFragmentPipeRead1(t *testing.T) { 11 | p := NewFragmentPipe(6) 12 | p.WriteString("foo", 0) 13 | p.WriteString("bar", 3) 14 | data, err := ioutil.ReadAll(p) 15 | if err != nil { 16 | t.Errorf("ioutil.ReadAll(%T) error: %v", p, err) 17 | } 18 | t.Logf("ioutil.ReadAll(%T) return: %#v", p, string(data)) 19 | } 20 | 21 | func TestFragmentPipeRead2(t *testing.T) { 22 | p := NewFragmentPipe(6) 23 | go p.WriteString("foo", 0) 24 | go p.WriteString("bar", 3) 25 | data, err := ioutil.ReadAll(p) 26 | if err != nil { 27 | t.Errorf("ioutil.ReadAll(%T) error: %v", p, err) 28 | } 29 | t.Logf("ioutil.ReadAll(%T) return: %#v", p, string(data)) 30 | } 31 | 32 | func TestFragmentPipeRead3(t *testing.T) { 33 | p := NewFragmentPipe(6) 34 | go func() { 35 | time.Sleep(50 * time.Millisecond) 36 | p.WriteString("foo", 0) 37 | }() 38 | go func() { 39 | p.WriteString("bar", 3) 40 | }() 41 | data, err := ioutil.ReadAll(p) 42 | if err != nil { 43 | t.Errorf("ioutil.ReadAll(%T) error: %v", p, err) 44 | } 45 | t.Logf("ioutil.ReadAll(%T) return: %#v", p, string(data)) 46 | } 47 | 48 | func TestFragmentPipeRead4(t *testing.T) { 49 | p := NewFragmentPipe(6) 50 | go func() { 51 | p.WriteString("foo", 0) 52 | }() 53 | go func() { 54 | time.Sleep(50 * time.Millisecond) 55 | p.WriteString("bar", 3) 56 | }() 57 | data, err := ioutil.ReadAll(p) 58 | if err != nil { 59 | t.Errorf("ioutil.ReadAll(%T) error: %v", p, err) 60 | } 61 | t.Logf("ioutil.ReadAll(%T) return: %#v", p, string(data)) 62 | } 63 | 64 | func TestFragmentPipeRead5(t *testing.T) { 65 | p := NewFragmentPipe(6) 66 | go func() { 67 | time.Sleep(50 * time.Millisecond) 68 | p.CloseWithError(io.ErrClosedPipe) 69 | }() 70 | go func() { 71 | p.WriteString("bar", 3) 72 | }() 73 | _, err := ioutil.ReadAll(p) 74 | if err == nil { 75 | t.Errorf("ioutil.ReadAll(%#v) should not return nil err", p) 76 | } else { 77 | t.Logf("ioutil.ReadAll(%T) return:err=%v", p, err) 78 | } 79 | } 80 | 81 | func TestFragmentPipeRead6(t *testing.T) { 82 | p := NewFragmentPipe(12) 83 | go func() { 84 | time.Sleep(10 * time.Millisecond) 85 | p.WriteString("foo", 0) 86 | }() 87 | go func() { 88 | time.Sleep(0 * time.Millisecond) 89 | p.WriteString("bar", 3) 90 | }() 91 | go func() { 92 | time.Sleep(40 * time.Millisecond) 93 | p.WriteString("foo", 6) 94 | }() 95 | go func() { 96 | time.Sleep(10 * time.Millisecond) 97 | p.WriteString("bar", 9) 98 | }() 99 | data, err := ioutil.ReadAll(p) 100 | if err != nil { 101 | t.Errorf("ioutil.ReadAll(%T) error: %v", p, err) 102 | } 103 | t.Logf("ioutil.ReadAll(%T) return: %#v", p, string(data)) 104 | } 105 | -------------------------------------------------------------------------------- /httpproxy/helpers/fragment_writeto_test.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestFragmentPipeWriteTo1(t *testing.T) { 11 | p := NewFragmentPipe(6) 12 | p.WriteString("foo", 0) 13 | p.WriteString("bar", 3) 14 | n, err := io.Copy(ioutil.Discard, p) 15 | if err != nil { 16 | t.Errorf("io.Copy(%T) error: %v", p, err) 17 | } 18 | t.Logf("io.Copy(%T) return: n=%v, err=%v", p, n, err) 19 | } 20 | 21 | func TestFragmentPipeWriteTo2(t *testing.T) { 22 | p := NewFragmentPipe(6) 23 | go p.WriteString("foo", 0) 24 | go p.WriteString("bar", 3) 25 | n, err := io.Copy(ioutil.Discard, p) 26 | if err != nil { 27 | t.Errorf("io.Copy(%T) error: %v", p, err) 28 | } 29 | t.Logf("io.Copy(%T) return: n=%v, err=%v", p, n, err) 30 | } 31 | 32 | func TestFragmentPipeWriteTo3(t *testing.T) { 33 | p := NewFragmentPipe(6) 34 | go func() { 35 | time.Sleep(50 * time.Millisecond) 36 | p.WriteString("foo", 0) 37 | }() 38 | go func() { 39 | p.WriteString("bar", 3) 40 | }() 41 | n, err := io.Copy(ioutil.Discard, p) 42 | if err != nil { 43 | t.Errorf("io.Copy(%T) error: %v", p, err) 44 | } 45 | t.Logf("io.Copy(%T) return: n=%v, err=%v", p, n, err) 46 | } 47 | 48 | func TestFragmentPipeWriteTo4(t *testing.T) { 49 | p := NewFragmentPipe(6) 50 | go func() { 51 | p.WriteString("foo", 0) 52 | }() 53 | go func() { 54 | time.Sleep(50 * time.Millisecond) 55 | p.WriteString("bar", 3) 56 | }() 57 | n, err := io.Copy(ioutil.Discard, p) 58 | if err != nil { 59 | t.Errorf("io.Copy(%T) error: %v", p, err) 60 | } 61 | t.Logf("io.Copy(%T) return: n=%v, err=%v", p, n, err) 62 | } 63 | 64 | func TestFragmentPipeWriteTo5(t *testing.T) { 65 | p := NewFragmentPipe(6) 66 | go func() { 67 | time.Sleep(50 * time.Millisecond) 68 | p.CloseWithError(io.ErrClosedPipe) 69 | }() 70 | go func() { 71 | p.WriteString("bar", 3) 72 | }() 73 | _, err := ioutil.ReadAll(p) 74 | if err == nil { 75 | t.Errorf("ioutil.ReadAll(%#v) should not return nil err", p) 76 | } else { 77 | t.Logf("io.Copy(%T) return:err=%v", p, err) 78 | } 79 | } 80 | 81 | func TestFragmentPipeWriteTo6(t *testing.T) { 82 | p := NewFragmentPipe(12) 83 | go func() { 84 | time.Sleep(10 * time.Millisecond) 85 | p.WriteString("foo", 0) 86 | }() 87 | go func() { 88 | time.Sleep(0 * time.Millisecond) 89 | p.WriteString("bar", 3) 90 | }() 91 | go func() { 92 | time.Sleep(40 * time.Millisecond) 93 | p.WriteString("foo", 6) 94 | }() 95 | go func() { 96 | time.Sleep(10 * time.Millisecond) 97 | p.WriteString("bar", 9) 98 | }() 99 | n, err := io.Copy(ioutil.Discard, p) 100 | if err != nil { 101 | t.Errorf("io.Copy(%T) error: %v", p, err) 102 | } 103 | t.Logf("io.Copy(%T) return: n=%v, err=%v", p, n, err) 104 | } 105 | -------------------------------------------------------------------------------- /httpproxy/helpers/hostmatcher.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "fmt" 5 | "path" 6 | "strings" 7 | ) 8 | 9 | type HostMatcher struct { 10 | starValue interface{} 11 | strictMap map[string]interface{} 12 | prefixList []string 13 | prefixMap map[string]interface{} 14 | wildcardList []string 15 | wildcardMap map[string]interface{} 16 | } 17 | 18 | func (hm *HostMatcher) add(host string, value interface{}) { 19 | switch { 20 | case strings.Contains(host, "/"): 21 | panic(fmt.Sprintf("invalid host(%#v) for HostMatcher", host)) 22 | case host == "*": 23 | hm.starValue = value 24 | case !strings.Contains(host, "*"): 25 | if hm.strictMap == nil { 26 | hm.strictMap = make(map[string]interface{}) 27 | } 28 | hm.strictMap[host] = value 29 | case strings.HasPrefix(host, "*") && !strings.Contains(host[1:], "*"): 30 | if hm.prefixList == nil { 31 | hm.prefixList = make([]string, 0) 32 | } 33 | if hm.prefixMap == nil { 34 | hm.prefixMap = make(map[string]interface{}) 35 | } 36 | hm.prefixList = append(hm.prefixList, host[1:]) 37 | hm.prefixMap[host[1:]] = value 38 | default: 39 | if hm.wildcardList == nil { 40 | hm.wildcardList = make([]string, 0) 41 | } 42 | if hm.wildcardMap == nil { 43 | hm.wildcardMap = make(map[string]interface{}) 44 | } 45 | hm.wildcardList = append(hm.wildcardList, host) 46 | hm.wildcardMap[host] = value 47 | } 48 | } 49 | 50 | func NewHostMatcher(hosts []string) *HostMatcher { 51 | values := make(map[string]interface{}, len(hosts)) 52 | for _, host := range hosts { 53 | values[host] = struct{}{} 54 | } 55 | return NewHostMatcherWithValue(values) 56 | } 57 | 58 | func NewHostMatcherWithString(hosts map[string]string) *HostMatcher { 59 | values := make(map[string]interface{}, len(hosts)) 60 | for host, value := range hosts { 61 | values[host] = value 62 | } 63 | return NewHostMatcherWithValue(values) 64 | } 65 | 66 | func NewHostMatcherWithStrings(hosts map[string][]string) *HostMatcher { 67 | values := make(map[string]interface{}, len(hosts)) 68 | for host, value := range hosts { 69 | values[host] = value 70 | } 71 | return NewHostMatcherWithValue(values) 72 | } 73 | 74 | func NewHostMatcherWithValue(values map[string]interface{}) *HostMatcher { 75 | hm := &HostMatcher{} 76 | 77 | for host, value := range values { 78 | hm.add(host, value) 79 | } 80 | 81 | return hm 82 | } 83 | 84 | func (hm *HostMatcher) AddHost(host string) { 85 | hm.AddHostWithValue(host, struct{}{}) 86 | } 87 | 88 | func (hm *HostMatcher) AddHostWithValue(host string, value interface{}) { 89 | hm.add(host, value) 90 | } 91 | 92 | func (hm *HostMatcher) Match(host string) bool { 93 | _, ok := hm.Lookup(host) 94 | return ok 95 | } 96 | 97 | func (hm *HostMatcher) Lookup(host string) (interface{}, bool) { 98 | if hm.starValue != nil { 99 | return hm.starValue, true 100 | } 101 | 102 | if hm.strictMap != nil { 103 | if value, ok := hm.strictMap[host]; ok { 104 | return value, true 105 | } 106 | } 107 | 108 | if hm.prefixList != nil { 109 | for _, key := range hm.prefixList { 110 | if strings.HasSuffix(host, key) { 111 | return hm.prefixMap[key], true 112 | } 113 | } 114 | } 115 | 116 | if hm.wildcardList != nil { 117 | for _, key := range hm.wildcardList { 118 | if matched, _ := path.Match(key, host); matched { 119 | return hm.wildcardMap[key], true 120 | } 121 | } 122 | } 123 | 124 | return nil, false 125 | } 126 | -------------------------------------------------------------------------------- /httpproxy/helpers/hostmatcher_test.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var hosts []string = []string{ 8 | "*.ahcdn.com", 9 | "*.atm.youku.com", 10 | "*.c.docs.google.com", 11 | "*.c.youtube.com", 12 | "*.d.rncdn3.com", 13 | "*.edgecastcdn.net", 14 | "*.mms.vlog.xuite.net", 15 | "*.xvideos.com", 16 | "*av.vimeo.com", 17 | "archive.rthk.hk", 18 | "av.voanews.com", 19 | "cdn*.public.extremetube.phncdn.com", 20 | "cdn*.public.tube8.com", 21 | "cdn*.video.pornhub.phncdn.com", 22 | "s*.last.fm", 23 | "smile-*.nicovideo.jp", 24 | "video*.modimovie.com", 25 | "video.*.fbcdn.net", 26 | "videos.flv*.redtubefiles.com", 27 | "vs*.thisav.com", 28 | "x*.last.fm", 29 | } 30 | 31 | var matcher *HostMatcher = NewHostMatcher(hosts) 32 | 33 | func TestAutoRangeMatch(t *testing.T) { 34 | for _, host := range []string{ 35 | "x1.last.fm", 36 | "av.voanews.com", 37 | "test.c.docs.google.com", 38 | } { 39 | ok := matcher.Match(host) 40 | if !ok { 41 | t.Errorf("matcher.Match(%#v) return %#v", host, ok) 42 | } else { 43 | t.Logf("matcher.Match(%#v) return %#v", host, ok) 44 | } 45 | } 46 | 47 | } 48 | 49 | func BenchmarkAutoRangeMatch(b *testing.B) { 50 | for i := 0; i < 50000; i++ { 51 | for _, host := range []string{ 52 | "x1.last.fm", 53 | "av.voanews.com", 54 | "test.c.docs.google.com", 55 | } { 56 | ok := matcher.Match(host) 57 | if !ok { 58 | b.Errorf("matcher.Match(%#v) return %#v", host, ok) 59 | } 60 | } 61 | } 62 | } 63 | 64 | func BenchmarkAutoRangeLookup(b *testing.B) { 65 | for i := 0; i < 50000; i++ { 66 | for _, host := range []string{ 67 | "x1.last.fm", 68 | "av.voanews.com", 69 | "test.c.docs.google.com", 70 | } { 71 | _, ok := matcher.Lookup(host) 72 | if !ok { 73 | b.Errorf("matcher.Match(%#v) return %#v", host, ok) 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /httpproxy/helpers/importca.go: -------------------------------------------------------------------------------- 1 | //+build !windows 2 | 3 | package helpers 4 | 5 | import ( 6 | "crypto/x509" 7 | ) 8 | 9 | func ImportCAToSystemRoot(cert *x509.Certificate) error { 10 | return nil 11 | } 12 | 13 | func RemoveCAFromSystemRoot(name string) error { 14 | return nil 15 | } 16 | -------------------------------------------------------------------------------- /httpproxy/helpers/importca_windows.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "crypto/x509" 5 | "syscall" 6 | "unsafe" 7 | ) 8 | 9 | var ( 10 | crypt32 = syscall.NewLazyDLL("crypt32.dll") 11 | procCertAddEncodedCertificateToStore = crypt32.NewProc("CertAddEncodedCertificateToStore") 12 | procCertDeleteCertificateFromStore = crypt32.NewProc("CertDeleteCertificateFromStore") 13 | ) 14 | 15 | func ImportCAToSystemRoot(cert *x509.Certificate) error { 16 | data := cert.Raw 17 | 18 | store, err := syscall.CertOpenStore(10, 0, 0, 0x4000|0x20000|0x00000004, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("root")))) 19 | if err != nil { 20 | return err 21 | } 22 | defer syscall.CertCloseStore(store, 0) 23 | 24 | _, _, err = procCertAddEncodedCertificateToStore.Call(uintptr(store), 1, uintptr(unsafe.Pointer(&data[0])), uintptr(uint(len(data))), 4, 0) 25 | if err.(syscall.Errno) != 0 { 26 | return err 27 | } 28 | 29 | return nil 30 | } 31 | 32 | func RemoveCAFromSystemRoot(name string) error { 33 | store, err := syscall.CertOpenStore(10, 0, 0, 0x4000|0x20000|0x00000004, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("root")))) 34 | if err != nil { 35 | return nil 36 | } 37 | defer syscall.CertCloseStore(store, 0) 38 | 39 | certs := make([]*syscall.CertContext, 0) 40 | var cert *syscall.CertContext 41 | for { 42 | cert, err = syscall.CertEnumCertificatesInStore(store, cert) 43 | if err != nil { 44 | break 45 | } 46 | 47 | buf := (*[1 << 20]byte)(unsafe.Pointer(cert.EncodedCert))[:] 48 | buf2 := make([]byte, cert.Length) 49 | copy(buf2, buf) 50 | 51 | c, err := x509.ParseCertificate(buf2) 52 | if err != nil { 53 | return err 54 | } 55 | 56 | if c.Subject.CommonName == name || 57 | (len(c.Subject.Names) > 0 && c.Subject.Names[0].Value == name) || 58 | (len(c.Subject.Organization) > 0 && c.Subject.Organization[0] == name) { 59 | certs = append(certs, cert) 60 | } 61 | } 62 | 63 | for _, cert := range certs { 64 | _, _, err = procCertDeleteCertificateFromStore.Call(uintptr(unsafe.Pointer(cert))) 65 | } 66 | 67 | if se, ok := err.(syscall.Errno); ok && se != 0 { 68 | return err 69 | } 70 | 71 | return nil 72 | } 73 | -------------------------------------------------------------------------------- /httpproxy/helpers/io.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "io" 5 | "sync" 6 | // "github.com/cloudflare/golibs/bytepool" 7 | ) 8 | 9 | const ( 10 | BUFSZ = 32 * 1024 11 | ) 12 | 13 | var ( 14 | bufpool = sync.Pool{ 15 | New: func() interface{} { 16 | return make([]byte, BUFSZ) 17 | }, 18 | } 19 | ) 20 | 21 | func IOCopy(dst io.Writer, src io.Reader) (written int64, err error) { 22 | buf := bufpool.Get().([]byte) 23 | written, err = io.CopyBuffer(dst, src, buf) 24 | bufpool.Put(buf) 25 | return written, err 26 | } 27 | -------------------------------------------------------------------------------- /httpproxy/helpers/listener.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "crypto/tls" 5 | "fmt" 6 | "net" 7 | "sync" 8 | "time" 9 | 10 | "github.com/phuslu/glog" 11 | ) 12 | 13 | const ( 14 | backlog = 1024 15 | ) 16 | 17 | type Listener interface { 18 | net.Listener 19 | 20 | Add(net.Conn) error 21 | } 22 | 23 | type connRacer struct { 24 | conn net.Conn 25 | err error 26 | } 27 | 28 | type listener struct { 29 | ln net.Listener 30 | lane chan connRacer 31 | keepAlivePeriod time.Duration 32 | readBufferSize int 33 | writeBufferSize int 34 | stopped bool 35 | once sync.Once 36 | mu sync.Mutex 37 | } 38 | 39 | type ListenOptions struct { 40 | TLSConfig *tls.Config 41 | KeepAlivePeriod time.Duration 42 | ReadBufferSize int 43 | WriteBufferSize int 44 | } 45 | 46 | func ListenTCP(network, addr string, opts *ListenOptions) (Listener, error) { 47 | laddr, err := net.ResolveTCPAddr(network, addr) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | ln0, err := net.ListenTCP(network, laddr) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | var ln net.Listener 58 | if opts != nil && opts.TLSConfig != nil { 59 | ln = tls.NewListener(ln0, opts.TLSConfig) 60 | } else { 61 | ln = ln0 62 | } 63 | 64 | var keepAlivePeriod time.Duration 65 | var readBufferSize, writeBufferSize int 66 | if opts != nil { 67 | if opts.KeepAlivePeriod > 0 { 68 | keepAlivePeriod = opts.KeepAlivePeriod 69 | } 70 | if opts.ReadBufferSize > 0 { 71 | readBufferSize = opts.ReadBufferSize 72 | } 73 | if opts.WriteBufferSize > 0 { 74 | writeBufferSize = opts.WriteBufferSize 75 | } 76 | } 77 | 78 | l := &listener{ 79 | ln: ln, 80 | lane: make(chan connRacer, backlog), 81 | stopped: false, 82 | keepAlivePeriod: keepAlivePeriod, 83 | readBufferSize: readBufferSize, 84 | writeBufferSize: writeBufferSize, 85 | } 86 | 87 | return l, nil 88 | 89 | } 90 | 91 | func (l *listener) Accept() (c net.Conn, err error) { 92 | l.once.Do(func() { 93 | go func() { 94 | var tempDelay time.Duration 95 | for { 96 | conn, err := l.ln.Accept() 97 | l.lane <- connRacer{conn, err} 98 | if err != nil { 99 | if ne, ok := err.(net.Error); ok && ne.Temporary() { 100 | if tempDelay == 0 { 101 | tempDelay = 5 * time.Millisecond 102 | } else { 103 | tempDelay *= 2 104 | } 105 | if max := 1 * time.Second; tempDelay > max { 106 | tempDelay = max 107 | } 108 | glog.Warningf("httpproxy.Listener: Accept error: %v; retrying in %v", err, tempDelay) 109 | time.Sleep(tempDelay) 110 | continue 111 | } 112 | return 113 | } 114 | } 115 | }() 116 | }) 117 | 118 | r := <-l.lane 119 | if r.err != nil { 120 | return r.conn, r.err 121 | } 122 | 123 | if l.keepAlivePeriod > 0 || l.readBufferSize > 0 || l.writeBufferSize > 0 { 124 | if tc, ok := r.conn.(*net.TCPConn); ok { 125 | if l.keepAlivePeriod > 0 { 126 | tc.SetKeepAlive(true) 127 | tc.SetKeepAlivePeriod(l.keepAlivePeriod) 128 | } 129 | if l.readBufferSize > 0 { 130 | tc.SetReadBuffer(l.readBufferSize) 131 | } 132 | if l.writeBufferSize > 0 { 133 | tc.SetWriteBuffer(l.writeBufferSize) 134 | } 135 | } 136 | } 137 | 138 | return r.conn, nil 139 | } 140 | 141 | func (l *listener) Close() error { 142 | l.mu.Lock() 143 | defer l.mu.Unlock() 144 | if l.stopped { 145 | return nil 146 | } 147 | l.stopped = true 148 | close(l.lane) 149 | return l.ln.Close() 150 | } 151 | 152 | func (l *listener) Addr() net.Addr { 153 | return l.ln.Addr() 154 | } 155 | 156 | func (l *listener) Add(conn net.Conn) error { 157 | l.mu.Lock() 158 | defer l.mu.Unlock() 159 | 160 | if l.stopped { 161 | return fmt.Errorf("%#v already closed", l) 162 | } 163 | 164 | l.lane <- connRacer{conn, nil} 165 | 166 | return nil 167 | } 168 | -------------------------------------------------------------------------------- /httpproxy/helpers/lookup.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package helpers 4 | 5 | import ( 6 | "io/ioutil" 7 | "net" 8 | "regexp" 9 | ) 10 | 11 | var ( 12 | nsRegex = regexp.MustCompile(`(?m)^nameserver\s+([0-9a-fA-F\.:]+)`) 13 | ) 14 | 15 | func LookupIP(host string) (ips []net.IP, err error) { 16 | return net.LookupIP(host) 17 | } 18 | 19 | func GetLocalNameServers() ([]string, error) { 20 | b, err := ioutil.ReadFile("/etc/resolv.conf") 21 | if err != nil { 22 | return nil, err 23 | } 24 | nameservers := make([]string, 0, 4) 25 | for _, m := range nsRegex.FindAllStringSubmatch(string(b), -1) { 26 | nameservers = append(nameservers, m[1]) 27 | } 28 | return nameservers, nil 29 | } 30 | -------------------------------------------------------------------------------- /httpproxy/helpers/lookup_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package helpers 4 | 5 | import ( 6 | "net" 7 | "os" 8 | "strings" 9 | "syscall" 10 | "unsafe" 11 | 12 | "golang.org/x/sys/windows/registry" 13 | ) 14 | 15 | func lookup(name string, family int32) ([]net.IPAddr, error) { 16 | hints := syscall.AddrinfoW{ 17 | Family: family, 18 | Socktype: syscall.SOCK_STREAM, 19 | Protocol: syscall.IPPROTO_IP, 20 | } 21 | var result *syscall.AddrinfoW 22 | e := syscall.GetAddrInfoW(syscall.StringToUTF16Ptr(name), nil, &hints, &result) 23 | if e != nil { 24 | return nil, &net.DNSError{Err: os.NewSyscallError("getaddrinfow", e).Error(), Name: name} 25 | } 26 | defer syscall.FreeAddrInfoW(result) 27 | addrs := make([]net.IPAddr, 0, 5) 28 | for ; result != nil; result = result.Next { 29 | addr := unsafe.Pointer(result.Addr) 30 | switch result.Family { 31 | case syscall.AF_INET: 32 | a := (*syscall.RawSockaddrInet4)(addr).Addr 33 | addrs = append(addrs, net.IPAddr{IP: net.IPv4(a[0], a[1], a[2], a[3])}) 34 | case syscall.AF_INET6: 35 | a := (*syscall.RawSockaddrInet6)(addr).Addr 36 | // FIXME: expose zoneToString ? 37 | // zone := zoneToString(int((*syscall.RawSockaddrInet6)(addr).Scope_id)) 38 | zone := "" 39 | addrs = append(addrs, net.IPAddr{IP: net.IP{a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15]}, Zone: zone}) 40 | default: 41 | return nil, &net.DNSError{Err: syscall.EWINDOWS.Error(), Name: name} 42 | } 43 | } 44 | return addrs, nil 45 | } 46 | 47 | func LookupIP(host string) ([]net.IP, error) { 48 | addrs1 := make([]net.IP, 0, 8) 49 | 50 | var addrs []net.IPAddr 51 | var err error 52 | 53 | for _, family := range []int{syscall.AF_INET, syscall.AF_INET6} { 54 | if addrs, err = lookup(host, int32(family)); err == nil { 55 | for _, addr := range addrs { 56 | addrs1 = append(addrs1, addr.IP) 57 | } 58 | } 59 | } 60 | 61 | if len(addrs1) > 0 { 62 | err = nil 63 | } 64 | 65 | return addrs1, err 66 | } 67 | 68 | func GetLocalNameServers() ([]string, error) { 69 | key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters`, registry.QUERY_VALUE) 70 | if err != nil { 71 | return nil, err 72 | } 73 | defer key.Close() 74 | 75 | nameservers := make([]string, 0, 4) 76 | for _, name := range []string{`NameServer`, `DhcpNameServer`} { 77 | s, _, err := key.GetStringValue(name) 78 | if err != nil { 79 | return nil, err 80 | } 81 | for _, server := range strings.Split(s, " ") { 82 | if server != "" { 83 | nameservers = append(nameservers, server) 84 | } 85 | } 86 | } 87 | 88 | return nameservers, nil 89 | } 90 | -------------------------------------------------------------------------------- /httpproxy/helpers/media.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | func IsBinary(b []byte) bool { 4 | if len(b) > 3 && b[0] == 0xef && b[1] == 0xbb && b[2] == 0xbf { 5 | // utf-8 text 6 | return false 7 | } 8 | for i, c := range b { 9 | if c > 0x7f { 10 | return true 11 | } 12 | if c == '\n' && i > 4 { 13 | break 14 | } 15 | if i > 32 { 16 | break 17 | } 18 | } 19 | return false 20 | } 21 | 22 | func IsGzip(b []byte) bool { 23 | // return *(*uint32)(unsafe.Pointer(&b[0])) == 0x00088b1f 24 | return len(b) > 4 && 25 | b[0] == 0x1f && 26 | b[1] == 0x8b && 27 | b[2] == 0x08 && 28 | b[3] == 0x00 29 | } 30 | -------------------------------------------------------------------------------- /httpproxy/helpers/media_test.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestIsBinary1(t *testing.T) { 8 | data := []byte("\xed\xbdyW\x1b") 9 | if !IsBinary(data) { 10 | t.Errorf("data=%s must be binary", data) 11 | } 12 | } 13 | 14 | func TestIsBinary2(t *testing.T) { 15 | data := []byte("hello world!") 16 | if IsBinary(data) { 17 | t.Errorf("data=%s must not be binary", data) 18 | } 19 | } 20 | 21 | func TestIsBinary3(t *testing.T) { 22 | data := []byte("\xef\xbb\xbfhello world!") 23 | if IsBinary(data) { 24 | t.Errorf("data=%s must not be binary", data) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /httpproxy/helpers/net.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "net" 5 | "strings" 6 | ) 7 | 8 | func LocalIPv4s() ([]net.IP, error) { 9 | addrs, err := net.InterfaceAddrs() 10 | if err != nil { 11 | return nil, err 12 | } 13 | 14 | ips := make([]net.IP, 0) 15 | for _, addr := range addrs { 16 | addr1 := addr.String() 17 | switch addr.Network() { 18 | case "ip+net": 19 | addr1 = strings.Split(addr1, "/")[0] 20 | } 21 | if ip := net.ParseIP(addr1); ip != nil && ip.To4() != nil { 22 | s := ip.String() 23 | if s == "::1" || strings.HasPrefix(s, "127.") || strings.HasPrefix(s, "169.254.") { 24 | continue 25 | } 26 | ips = append(ips, ip) 27 | } 28 | } 29 | 30 | return ips, nil 31 | } 32 | 33 | func LocalPerferIPv4() (net.IP, error) { 34 | addr, err := net.ResolveUDPAddr("udp4", "8.8.8.8:53") 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | conn, err := net.DialUDP("udp4", nil, addr) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | return conn.LocalAddr().(*net.UDPAddr).IP, nil 45 | } 46 | -------------------------------------------------------------------------------- /httpproxy/helpers/net_linux.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "net" 5 | 6 | "golang.org/x/sys/unix" 7 | ) 8 | 9 | type TCPInfo struct { 10 | unix.TCPInfo 11 | } 12 | 13 | func GetTCPInfo(c net.Conn) (*TCPInfo, error) { 14 | cc, err := c.(*net.TCPConn).SyscallConn() 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | var ti *unix.TCPInfo 20 | 21 | fn := func(s uintptr) { 22 | ti, err = unix.GetsockoptTCPInfo(int(s), unix.SOL_TCP, unix.TCP_INFO) 23 | } 24 | 25 | if err := cc.Control(fn); err != nil { 26 | return nil, err 27 | } 28 | 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | return &TCPInfo{*ti}, nil 34 | } 35 | -------------------------------------------------------------------------------- /httpproxy/helpers/os.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | func SetFlagsIfAbsent(m map[string]string) { 11 | seen := map[string]struct{}{} 12 | 13 | for i := 1; i < len(os.Args); i++ { 14 | for key := range m { 15 | if strings.HasPrefix(os.Args[i], "-"+key+"=") { 16 | seen[key] = struct{}{} 17 | } 18 | } 19 | } 20 | 21 | for key, value := range m { 22 | if _, ok := seen[key]; !ok { 23 | flag.Set(key, value) 24 | } 25 | } 26 | } 27 | 28 | // copy/paste from https://github.com/jamiealquiza/envy/ 29 | // Parse takes a string p that is used 30 | // as the environment variable prefix 31 | // for each flag configured. 32 | func SetFlagsFromEnv(prefix string) { 33 | // Build a map of explicitly set flags. 34 | set := map[string]bool{} 35 | flag.CommandLine.Visit(func(f *flag.Flag) { 36 | set[f.Name] = true 37 | }) 38 | 39 | flag.CommandLine.VisitAll(func(f *flag.Flag) { 40 | // Create an env var name 41 | // based on the supplied prefix. 42 | envVar := fmt.Sprintf("%s_%s", prefix, strings.ToUpper(f.Name)) 43 | envVar = strings.Replace(envVar, "-", "_", -1) 44 | 45 | // Update the Flag.Value if the 46 | // env var is non "". 47 | if val := os.Getenv(envVar); val != "" { 48 | // Update the value if it hasn't 49 | // already been set. 50 | if defined := set[f.Name]; !defined { 51 | flag.CommandLine.Set(f.Name, val) 52 | } 53 | } 54 | 55 | // Append the env var to the 56 | // Flag.Usage field. 57 | f.Usage = fmt.Sprintf("%s [%s]", f.Usage, envVar) 58 | }) 59 | } 60 | -------------------------------------------------------------------------------- /httpproxy/helpers/reader.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/juju/ratelimit" 7 | ) 8 | 9 | type multiReadCloser struct { 10 | readers []io.Reader 11 | multiReader io.Reader 12 | } 13 | 14 | func NewMultiReadCloser(readers ...io.Reader) io.ReadCloser { 15 | return &multiReadCloser{ 16 | readers: readers, 17 | multiReader: io.MultiReader(readers...), 18 | } 19 | } 20 | 21 | func (r *multiReadCloser) Read(p []byte) (n int, err error) { 22 | return r.multiReader.Read(p) 23 | } 24 | 25 | func (r *multiReadCloser) Close() (err error) { 26 | for _, r := range r.readers { 27 | if c, ok := r.(io.Closer); ok { 28 | if e := c.Close(); e != nil { 29 | err = e 30 | } 31 | } 32 | } 33 | 34 | return err 35 | } 36 | 37 | type xorReadCloser struct { 38 | rc io.ReadCloser 39 | key []byte 40 | } 41 | 42 | func NewXorReadCloser(rc io.ReadCloser, key []byte) io.ReadCloser { 43 | return &xorReadCloser{ 44 | rc: rc, 45 | key: key, 46 | } 47 | } 48 | 49 | func (x *xorReadCloser) Read(p []byte) (n int, err error) { 50 | n, err = x.rc.Read(p) 51 | c := x.key[0] 52 | for i := 0; i < n; i++ { 53 | p[i] ^= c 54 | } 55 | 56 | return n, err 57 | } 58 | 59 | func (x *xorReadCloser) Close() error { 60 | return x.rc.Close() 61 | } 62 | 63 | type rateLimitReader struct { 64 | rc io.ReadCloser // underlying reader 65 | rlr io.Reader // ratelimit.Reader 66 | } 67 | 68 | func NewRateLimitReader(rc io.ReadCloser, rate float64, capacity int64) io.ReadCloser { 69 | var rlr rateLimitReader 70 | 71 | rlr.rc = rc 72 | rlr.rlr = ratelimit.Reader(rc, ratelimit.NewBucketWithRate(rate, capacity)) 73 | 74 | return &rlr 75 | } 76 | 77 | func (rlr *rateLimitReader) Read(p []byte) (n int, err error) { 78 | return rlr.rlr.Read(p) 79 | } 80 | 81 | func (rlr *rateLimitReader) Close() error { 82 | return rlr.rc.Close() 83 | } 84 | 85 | type ReaderCloser struct { 86 | Reader io.Reader 87 | Closer io.ReadCloser 88 | } 89 | 90 | func (bc ReaderCloser) Read(buf []byte) (int, error) { 91 | return bc.Reader.Read(buf) 92 | } 93 | 94 | func (bc ReaderCloser) Close() error { 95 | return bc.Closer.Close() 96 | } 97 | -------------------------------------------------------------------------------- /httpproxy/helpers/reflect.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/http" 7 | "reflect" 8 | ) 9 | 10 | func ReflectRemoteIPFromResponse(resp *http.Response) (net.IP, error) { 11 | addr, err := ReflectRemoteAddrFromResponse(resp) 12 | if err != nil { 13 | return nil, err 14 | } 15 | 16 | host, _, err := net.SplitHostPort(addr) 17 | if err != nil { 18 | return nil, err 19 | } 20 | 21 | ip := net.ParseIP(host) 22 | if ip == nil { 23 | return nil, fmt.Errorf("ReflectRemoteIPFromResponse: cannot parse %+v to ip format", host) 24 | } 25 | 26 | return ip, nil 27 | } 28 | 29 | func ReflectRemoteAddrFromResponse(resp *http.Response) (string, error) { 30 | if resp.Body == nil { 31 | return "", fmt.Errorf("ReflectRemoteAddrFromResponse: cannot reflect %#v for %v", resp, resp.Request.URL.String()) 32 | } 33 | 34 | v := reflect.ValueOf(resp.Body) 35 | 36 | switch v.Type().String() { 37 | case "*http.gzipReader": 38 | v = v.Elem().FieldByName("body") 39 | fallthrough 40 | case "*http.bodyEOFSignal": 41 | v = v.Elem().FieldByName("body").Elem() 42 | v = reflect.Indirect(v).FieldByName("src").Elem() 43 | switch v.Type().String() { 44 | case "*internal.chunkedReader": 45 | v = reflect.Indirect(v).FieldByName("r").Elem() 46 | v = reflect.Indirect(v).FieldByName("rd").Elem() 47 | v = reflect.Indirect(v).FieldByName("conn").Elem() 48 | case "*io.LimitedReader": 49 | v = reflect.Indirect(v).FieldByName("R").Elem() 50 | v = reflect.Indirect(v).FieldByName("rd").Elem() 51 | v = reflect.Indirect(v).FieldByName("conn").Elem() 52 | default: 53 | return "", fmt.Errorf("ReflectRemoteAddrFromResponse: unsupport %#v Type=%s", v, v.Type().String()) 54 | } 55 | case "http.http2transportResponseBody", "http2.transportResponseBody": 56 | v = v.FieldByName("cs").Elem() 57 | v = v.FieldByName("cc").Elem() 58 | v = v.FieldByName("tconn").Elem() 59 | case "*quic.stream": 60 | return resp.Body.(net.Conn).RemoteAddr().String(), nil 61 | default: 62 | return "", fmt.Errorf("ReflectRemoteAddrFromResponse: unsupport %#v Type=%s", v, v.Type().String()) 63 | } 64 | 65 | switch v.Type().String() { 66 | case "*tls.Conn": 67 | v = reflect.Indirect(v).FieldByName("conn").Elem() 68 | fallthrough 69 | case "*net.TCPConn": 70 | v = reflect.Indirect(v).FieldByName("fd").Elem() 71 | v = reflect.Indirect(v).FieldByName("raddr").Elem() 72 | v1 := reflect.Indirect(reflect.Indirect(v).FieldByName("IP")) 73 | v2 := reflect.Indirect(reflect.Indirect(v).FieldByName("Port")) 74 | v3 := reflect.Indirect(reflect.Indirect(v).FieldByName("Zone")) 75 | raddr := &net.TCPAddr{ 76 | IP: v1.Slice(0, v1.Len()).Bytes(), 77 | Port: int(v2.Int()), 78 | Zone: v3.String(), 79 | } 80 | return raddr.String(), nil 81 | } 82 | 83 | return "", fmt.Errorf("ReflectRemoteAddrFromResponse: unsupport %#v Type=%s", v, v.Type().String()) 84 | } 85 | -------------------------------------------------------------------------------- /httpproxy/helpers/reflect_test.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "net/http" 5 | "testing" 6 | 7 | "github.com/phuslu/quic-go/h2quic" 8 | ) 9 | 10 | func TestReflectRemoteAddrFromResponseHTTP(t *testing.T) { 11 | u := "http://www.google.cn" 12 | resp, err := http.Get(u) 13 | if err != nil { 14 | t.Errorf("http.Get(%#v) error: %v", u, err) 15 | } 16 | 17 | addr, err := ReflectRemoteAddrFromResponse(resp) 18 | if err != nil { 19 | t.Errorf("reflectRemoteAddrFromResponse(%T) error: %v", resp, err) 20 | } 21 | 22 | t.Logf("u=%#v, addr=%#v", u, addr) 23 | } 24 | 25 | func TestReflectRemoteAddrFromResponseHTTPS(t *testing.T) { 26 | u := "https://www.bing.com" 27 | req, _ := http.NewRequest(http.MethodGet, u, nil) 28 | tr := &http.Transport{ 29 | DisableCompression: true, 30 | } 31 | resp, err := tr.RoundTrip(req) 32 | if err != nil { 33 | t.Errorf("http.Get(%#v) error: %v", u, err) 34 | } 35 | 36 | addr, err := ReflectRemoteAddrFromResponse(resp) 37 | if err != nil { 38 | t.Errorf("reflectRemoteAddrFromResponse(%T) error: %v", resp, err) 39 | } 40 | 41 | t.Logf("u=%#v, addr=%#v", u, addr) 42 | } 43 | 44 | func TestReflectRemoteAddrFromResponseQuic(t *testing.T) { 45 | u := "https://www.google.cn" 46 | req, _ := http.NewRequest(http.MethodGet, u, nil) 47 | resp, err := (&h2quic.RoundTripper{ 48 | DisableCompression: true, 49 | }).RoundTrip(req) 50 | if err != nil { 51 | t.Errorf("http.Get(%#v) error: %v", u, err) 52 | } 53 | 54 | addr, err := ReflectRemoteAddrFromResponse(resp) 55 | if err != nil { 56 | t.Errorf("reflectRemoteAddrFromResponse(%T) error: %v", resp, err) 57 | } 58 | 59 | t.Logf("u=%#v, addr=%#v", u, addr) 60 | } 61 | -------------------------------------------------------------------------------- /httpproxy/helpers/resolver.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "strings" 7 | "time" 8 | 9 | "github.com/cloudflare/golibs/lrucache" 10 | "github.com/miekg/dns" 11 | "github.com/phuslu/glog" 12 | ) 13 | 14 | const ( 15 | DefaultDNSCacheExpiry time.Duration = 600 * time.Second 16 | ) 17 | 18 | type Resolver struct { 19 | LRUCache lrucache.Cache 20 | BlackList lrucache.Cache 21 | DNSServer net.IP 22 | DNSExpiry time.Duration 23 | DisableIPv6 bool 24 | ForceIPv6 bool 25 | } 26 | 27 | func (r *Resolver) LookupHost(name string) ([]string, error) { 28 | ips, err := r.LookupIP(name) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | addrs := make([]string, len(ips)) 34 | for i, ip := range ips { 35 | addrs[i] = ip.String() 36 | } 37 | 38 | return addrs, nil 39 | } 40 | 41 | func (r *Resolver) LookupIP(name string) ([]net.IP, error) { 42 | if r.LRUCache != nil { 43 | if v, ok := r.LRUCache.GetNotStale(name); ok { 44 | switch v.(type) { 45 | case []net.IP: 46 | return v.([]net.IP), nil 47 | case string: 48 | return r.LookupIP(v.(string)) 49 | default: 50 | return nil, fmt.Errorf("LookupIP: cannot convert %T(%+v) to []net.IP", v, v) 51 | } 52 | } 53 | } 54 | 55 | if ip := net.ParseIP(name); ip != nil { 56 | return []net.IP{ip}, nil 57 | } 58 | 59 | lookupIP := r.lookupIP1 60 | if r.DNSServer != nil { 61 | lookupIP = r.lookupIP2 62 | } 63 | 64 | ips, err := lookupIP(name) 65 | if err == nil { 66 | if r.BlackList != nil { 67 | ips1 := ips[:0] 68 | for _, ip := range ips { 69 | if _, ok := r.BlackList.GetQuiet(ip.String()); !ok { 70 | ips1 = append(ips1, ip) 71 | } 72 | } 73 | ips = ips1 74 | } 75 | 76 | if r.LRUCache != nil && len(ips) > 0 { 77 | if r.DNSExpiry == 0 { 78 | r.LRUCache.Set(name, ips, time.Now().Add(DefaultDNSCacheExpiry)) 79 | } else { 80 | r.LRUCache.Set(name, ips, time.Now().Add(r.DNSExpiry)) 81 | } 82 | } 83 | } 84 | 85 | glog.V(2).Infof("LookupIP(%#v) return %+v, err=%+v", name, ips, err) 86 | return ips, err 87 | } 88 | 89 | func (r *Resolver) lookupIP1(name string) ([]net.IP, error) { 90 | ips, err := LookupIP(name) 91 | if err != nil { 92 | return nil, err 93 | } 94 | 95 | ips1 := ips[:0] 96 | for _, ip := range ips { 97 | if strings.Contains(ip.String(), ":") { 98 | if r.ForceIPv6 || !r.DisableIPv6 { 99 | ips1 = append(ips1, ip) 100 | } 101 | } else { 102 | if !r.ForceIPv6 { 103 | ips1 = append(ips1, ip) 104 | } 105 | } 106 | } 107 | 108 | return ips1, nil 109 | } 110 | 111 | func (r *Resolver) lookupIP2(name string) ([]net.IP, error) { 112 | m := &dns.Msg{} 113 | 114 | switch { 115 | case r.ForceIPv6: 116 | m.SetQuestion(dns.Fqdn(name), dns.TypeAAAA) 117 | case r.DisableIPv6: 118 | m.SetQuestion(dns.Fqdn(name), dns.TypeA) 119 | default: 120 | m.SetQuestion(dns.Fqdn(name), dns.TypeA) 121 | } 122 | 123 | reply, err := dns.Exchange(m, net.JoinHostPort(r.DNSServer.String(), "53")) 124 | if err != nil { 125 | return nil, err 126 | } 127 | 128 | if len(reply.Answer) < 1 { 129 | return nil, fmt.Errorf("no Answer from dns server %+v", r.DNSServer.String()) 130 | } 131 | 132 | ips := make([]net.IP, 0, 4) 133 | 134 | for _, rr := range reply.Answer { 135 | var ip net.IP 136 | 137 | switch rr.(type) { 138 | case *dns.AAAA: 139 | ip = rr.(*dns.AAAA).AAAA 140 | case *dns.A: 141 | ip = rr.(*dns.A).A 142 | } 143 | if ip != nil { 144 | ips = append(ips, ip) 145 | } 146 | } 147 | 148 | return ips, nil 149 | } 150 | -------------------------------------------------------------------------------- /httpproxy/helpers/tlsciphers.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "crypto/tls" 5 | ) 6 | 7 | func TLSCipher(name string) uint16 { 8 | switch name { 9 | case "TLS_AES_128_GCM_SHA256": 10 | return tls.TLS_AES_128_GCM_SHA256 11 | case "TLS_AES_256_GCM_SHA384": 12 | return tls.TLS_AES_256_GCM_SHA384 13 | case "TLS_CHACHA20_POLY1305_SHA256": 14 | return tls.TLS_CHACHA20_POLY1305_SHA256 15 | case "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": 16 | return tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA 17 | case "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": 18 | return tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 19 | case "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": 20 | return tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 21 | case "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": 22 | return tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA 23 | case "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": 24 | return tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 25 | case "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305": 26 | return tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 27 | case "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA": 28 | return tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA 29 | case "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA": 30 | return tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA 31 | case "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": 32 | return tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA 33 | case "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": 34 | return tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 35 | case "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": 36 | return tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 37 | case "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": 38 | return tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA 39 | case "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": 40 | return tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 41 | case "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305": 42 | return tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 43 | case "TLS_ECDHE_RSA_WITH_RC4_128_SHA": 44 | return tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA 45 | case "TLS_RSA_WITH_3DES_EDE_CBC_SHA": 46 | return tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA 47 | case "TLS_RSA_WITH_AES_128_CBC_SHA": 48 | return tls.TLS_RSA_WITH_AES_128_CBC_SHA 49 | case "TLS_RSA_WITH_AES_128_CBC_SHA256": 50 | return tls.TLS_RSA_WITH_AES_128_CBC_SHA256 51 | case "TLS_RSA_WITH_AES_128_GCM_SHA256": 52 | return tls.TLS_RSA_WITH_AES_128_GCM_SHA256 53 | case "TLS_RSA_WITH_AES_256_CBC_SHA": 54 | return tls.TLS_RSA_WITH_AES_256_CBC_SHA 55 | case "TLS_RSA_WITH_AES_256_GCM_SHA384": 56 | return tls.TLS_RSA_WITH_AES_256_GCM_SHA384 57 | case "TLS_RSA_WITH_RC4_128_SHA": 58 | return tls.TLS_RSA_WITH_RC4_128_SHA 59 | //cipher suite added by phuslu 60 | case "TLS_RSA_WITH_AES_256_CBC_SHA256": 61 | return tls.TLS_RSA_WITH_AES_256_CBC_SHA256 62 | } 63 | return 0 64 | } 65 | 66 | func TLSCipherName(value uint16) string { 67 | switch value { 68 | case tls.TLS_AES_128_GCM_SHA256: 69 | return "TLS_AES_128_GCM_SHA256" 70 | case tls.TLS_AES_256_GCM_SHA384: 71 | return "TLS_AES_256_GCM_SHA384" 72 | case tls.TLS_CHACHA20_POLY1305_SHA256: 73 | return "TLS_CHACHA20_POLY1305_SHA256" 74 | case tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: 75 | return "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA" 76 | case tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: 77 | return "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256" 78 | case tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: 79 | return "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" 80 | case tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: 81 | return "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA" 82 | case tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: 83 | return "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384" 84 | case tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305: 85 | return "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305" 86 | case tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: 87 | return "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA" 88 | case tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: 89 | return "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA" 90 | case tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: 91 | return "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA" 92 | case tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: 93 | return "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256" 94 | case tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: 95 | return "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" 96 | case tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: 97 | return "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA" 98 | case tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: 99 | return "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384" 100 | case tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305: 101 | return "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305" 102 | case tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA: 103 | return "TLS_ECDHE_RSA_WITH_RC4_128_SHA" 104 | case tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA: 105 | return "TLS_RSA_WITH_3DES_EDE_CBC_SHA" 106 | case tls.TLS_RSA_WITH_AES_128_CBC_SHA: 107 | return "TLS_RSA_WITH_AES_128_CBC_SHA" 108 | case tls.TLS_RSA_WITH_AES_128_CBC_SHA256: 109 | return "TLS_RSA_WITH_AES_128_CBC_SHA256" 110 | case tls.TLS_RSA_WITH_AES_128_GCM_SHA256: 111 | return "TLS_RSA_WITH_AES_128_GCM_SHA256" 112 | case tls.TLS_RSA_WITH_AES_256_CBC_SHA: 113 | return "TLS_RSA_WITH_AES_256_CBC_SHA" 114 | case tls.TLS_RSA_WITH_AES_256_GCM_SHA384: 115 | return "TLS_RSA_WITH_AES_256_GCM_SHA384" 116 | case tls.TLS_RSA_WITH_RC4_128_SHA: 117 | return "TLS_RSA_WITH_RC4_128_SHA" 118 | //cipher suite added by phuslu 119 | case tls.TLS_RSA_WITH_AES_256_CBC_SHA256: 120 | return "TLS_RSA_WITH_AES_256_CBC_SHA256" 121 | } 122 | return "" 123 | } 124 | 125 | func HasECCCiphers(cipherSuites []uint16) bool { 126 | for _, cipher := range cipherSuites { 127 | switch cipher { 128 | case tls.TLS_AES_128_GCM_SHA256, 129 | tls.TLS_AES_256_GCM_SHA384, 130 | tls.TLS_CHACHA20_POLY1305_SHA256, 131 | tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 132 | tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, 133 | tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, 134 | tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: 135 | return true 136 | } 137 | } 138 | return false 139 | } 140 | -------------------------------------------------------------------------------- /httpproxy/helpers/tlsversion.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "crypto/tls" 5 | "fmt" 6 | ) 7 | 8 | func TLSVersion(name string) uint16 { 9 | switch name { 10 | case "TLS13", "TLSv1.3", "TLSv13": 11 | return tls.VersionTLS13 12 | case "TLS12", "TLSv1.2", "TLSv12": 13 | return tls.VersionTLS12 14 | case "TLS11", "TLSv1.1", "TLSv11": 15 | return tls.VersionTLS11 16 | case "TLS1", "TLSv1.0", "TLSv10": 17 | return tls.VersionTLS10 18 | case "SSL3", "SSLv3.0", "SSLv30": 19 | return tls.VersionSSL30 20 | case "Q039": 21 | return 39 22 | case "Q038": 23 | return 38 24 | case "Q037": 25 | return 37 26 | case "Q036": 27 | return 36 28 | case "Q035": 29 | return 35 30 | } 31 | return 0 32 | } 33 | 34 | func TLSVersionName(value uint16) string { 35 | switch value { 36 | case tls.VersionTLS13, tls.VersionTLS13Draft18: 37 | return "TLSv13" 38 | case tls.VersionTLS12: 39 | return "TLSv12" 40 | case tls.VersionTLS11: 41 | return "TLSv11" 42 | case tls.VersionTLS10: 43 | return "TLSv1" 44 | case 39: 45 | return "Q039" 46 | case 38: 47 | return "Q038" 48 | case 37: 49 | return "Q037" 50 | case 36: 51 | return "Q036" 52 | case 35: 53 | return "Q035" 54 | } 55 | return fmt.Sprintf("0x%x", value) 56 | } 57 | -------------------------------------------------------------------------------- /httpproxy/helpers/transport.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | "path" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/phuslu/glog" 11 | "github.com/phuslu/net/http2" 12 | "github.com/phuslu/quic-go/h2quic" 13 | ) 14 | 15 | var ( 16 | ReqWriteExcludeHeader = map[string]bool{ 17 | "Vary": true, 18 | "Via": true, 19 | "X-Forwarded-For": true, 20 | "Proxy-Authorization": true, 21 | "Proxy-Connection": true, 22 | "Upgrade": true, 23 | "X-Chrome-Variations": true, 24 | "Connection": true, 25 | "Cache-Control": true, 26 | } 27 | ) 28 | 29 | func CloseConnections(tr http.RoundTripper) { 30 | f := func(_ net.Addr) bool { return true } 31 | 32 | switch tr.(type) { 33 | case *http.Transport: 34 | tr.(*http.Transport).CloseConnection(f) 35 | case *http2.Transport: 36 | tr.(*http2.Transport).CloseConnection(f) 37 | case *h2quic.RoundTripper: 38 | tr.(*h2quic.RoundTripper).CloseConnection(f) 39 | default: 40 | glog.Errorf("%T(%v) has not implement CloseConnection method", tr, tr) 41 | } 42 | } 43 | 44 | func CloseConnectionByRemoteHost(tr http.RoundTripper, host string) { 45 | if host1, _, err := net.SplitHostPort(host); err == nil { 46 | host = host1 47 | } 48 | 49 | f := func(raddr net.Addr) bool { 50 | if host1, _, err := net.SplitHostPort(raddr.String()); err == nil { 51 | return host == host1 52 | } 53 | return false 54 | } 55 | 56 | switch tr.(type) { 57 | case *http.Transport: 58 | tr.(*http.Transport).CloseConnection(f) 59 | case *http2.Transport: 60 | tr.(*http2.Transport).CloseConnection(f) 61 | case *h2quic.RoundTripper: 62 | tr.(*h2quic.RoundTripper).CloseConnection(f) 63 | default: 64 | glog.Errorf("%T(%v) has not implement CloseConnection method", tr, tr) 65 | } 66 | } 67 | 68 | func FixRequestURL(req *http.Request) { 69 | if req.URL.Host == "" { 70 | switch { 71 | case req.Host != "": 72 | req.URL.Host = req.Host 73 | case req.TLS != nil: 74 | req.URL.Host = req.TLS.ServerName 75 | } 76 | } 77 | } 78 | 79 | func FixRequestHeader(req *http.Request) { 80 | if req.ContentLength > 0 { 81 | if req.Header.Get("Content-Length") == "" { 82 | req.Header.Set("Content-Length", strconv.FormatInt(req.ContentLength, 10)) 83 | } 84 | } 85 | } 86 | 87 | // CloneRequest returns a clone of the provided *http.Request. 88 | // The clone is a shallow copy of the struct and its Header map. 89 | func CloneRequest(r *http.Request) *http.Request { 90 | // shallow copy of the struct 91 | r2 := new(http.Request) 92 | *r2 = *r 93 | // deep copy of the Header 94 | r2.Header = make(http.Header, len(r.Header)) 95 | for k, s := range r.Header { 96 | r2.Header[k] = append([]string(nil), s...) 97 | } 98 | return r2 99 | } 100 | 101 | func GetHostName(req *http.Request) string { 102 | if host, _, err := net.SplitHostPort(req.Host); err == nil { 103 | return host 104 | } else { 105 | return req.Host 106 | } 107 | } 108 | 109 | func IsStaticRequest(req *http.Request) bool { 110 | switch path.Ext(req.URL.Path) { 111 | case "bmp", "gif", "ico", "jpeg", "jpg", "png", "tif", "tiff", 112 | "3gp", "3gpp", "avi", "f4v", "flv", "m4p", "mkv", "mp4", 113 | "mp4v", "mpv4", "rmvb", ".webp", ".js", ".css": 114 | return true 115 | case "": 116 | name := path.Base(req.URL.Path) 117 | if strings.Contains(name, "play") || 118 | strings.Contains(name, "video") { 119 | return true 120 | } 121 | default: 122 | if req.Header.Get("Range") != "" || 123 | strings.Contains(req.Host, "img.") || 124 | strings.Contains(req.Host, "cache.") || 125 | strings.Contains(req.Host, "video.") || 126 | strings.Contains(req.Host, "static.") || 127 | strings.HasPrefix(req.Host, "img") || 128 | strings.HasPrefix(req.URL.Path, "/static") || 129 | strings.HasPrefix(req.URL.Path, "/asset") || 130 | strings.Contains(req.URL.Path, "static") || 131 | strings.Contains(req.URL.Path, "asset") || 132 | strings.Contains(req.URL.Path, "/cache/") { 133 | return true 134 | } 135 | } 136 | return false 137 | } 138 | -------------------------------------------------------------------------------- /httpproxy/helpers/transport_test.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | "testing" 7 | 8 | "github.com/phuslu/net/http2" 9 | "github.com/phuslu/quic-go/h2quic" 10 | ) 11 | 12 | func TestCloseConnections(t *testing.T) { 13 | tansports := []http.RoundTripper{ 14 | http.DefaultTransport, 15 | &http2.Transport{}, 16 | &h2quic.RoundTripper{}, 17 | } 18 | 19 | type RoundTripperCloser interface { 20 | CloseConnection(f func(raddr net.Addr) bool) 21 | } 22 | 23 | for _, tr := range tansports { 24 | _, ok := tr.(RoundTripperCloser) 25 | if !ok { 26 | t.Errorf("%T(%v) CloseConnection()", tr, tr) 27 | } 28 | CloseConnections(tr) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /httpproxy/httpproxy.go: -------------------------------------------------------------------------------- 1 | package httpproxy 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | 7 | "github.com/phuslu/glog" 8 | 9 | "./filters" 10 | "./helpers" 11 | 12 | _ "./filters/auth" 13 | _ "./filters/autoproxy" 14 | _ "./filters/autorange" 15 | _ "./filters/direct" 16 | _ "./filters/gae" 17 | _ "./filters/php" 18 | _ "./filters/rewrite" 19 | _ "./filters/ssh2" 20 | _ "./filters/stripssl" 21 | _ "./filters/vps" 22 | ) 23 | 24 | type Config struct { 25 | Enabled bool 26 | Address string 27 | KeepAlivePeriod int 28 | ReadTimeout int 29 | WriteTimeout int 30 | RequestFilters []string 31 | RoundTripFilters []string 32 | ResponseFilters []string 33 | } 34 | 35 | func ServeProfile(config Config, branding string) error { 36 | 37 | listenOpts := &helpers.ListenOptions{TLSConfig: nil} 38 | 39 | ln, err := helpers.ListenTCP("tcp", config.Address, listenOpts) 40 | if err != nil { 41 | glog.Fatalf("ListenTCP(%s, %#v) error: %s", config.Address, listenOpts, err) 42 | } 43 | 44 | h := Handler{ 45 | Listener: ln, 46 | RequestFilters: []filters.RequestFilter{}, 47 | RoundTripFilters: []filters.RoundTripFilter{}, 48 | ResponseFilters: []filters.ResponseFilter{}, 49 | Branding: branding, 50 | } 51 | 52 | for _, name := range config.RequestFilters { 53 | f, err := filters.GetFilter(name) 54 | f1, ok := f.(filters.RequestFilter) 55 | if !ok { 56 | glog.Fatalf("%#v is not a RequestFilter, err=%+v", f, err) 57 | } 58 | h.RequestFilters = append(h.RequestFilters, f1) 59 | } 60 | 61 | for _, name := range config.RoundTripFilters { 62 | f, err := filters.GetFilter(name) 63 | f1, ok := f.(filters.RoundTripFilter) 64 | if !ok { 65 | glog.Fatalf("%#v is not a RoundTripFilter, err=%+v", f, err) 66 | } 67 | h.RoundTripFilters = append(h.RoundTripFilters, f1) 68 | } 69 | 70 | for _, name := range config.ResponseFilters { 71 | f, err := filters.GetFilter(name) 72 | f1, ok := f.(filters.ResponseFilter) 73 | if !ok { 74 | glog.Fatalf("%#v is not a ResponseFilter, err=%+v", f, err) 75 | } 76 | h.ResponseFilters = append(h.ResponseFilters, f1) 77 | } 78 | 79 | s := &http.Server{ 80 | Handler: h, 81 | ReadTimeout: time.Duration(config.ReadTimeout) * time.Second, 82 | WriteTimeout: time.Duration(config.WriteTimeout) * time.Second, 83 | MaxHeaderBytes: 1 << 20, 84 | } 85 | 86 | return s.Serve(h.Listener) 87 | } 88 | -------------------------------------------------------------------------------- /httpproxy/httpproxy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Default": { 3 | "Enabled": true, 4 | "Address": "127.0.0.1:8087", 5 | "KeepAlivePeriod": 0, 6 | "ReadTimeout": 600, 7 | "WriteTimeout": 3600, 8 | "RequestFilters": [ 9 | // "auth", 10 | // "rewrite", 11 | "autoproxy", 12 | "stripssl", 13 | "autorange", 14 | ], 15 | "RoundTripFilters": [ 16 | "autoproxy", 17 | // "auth", 18 | // "vps", 19 | // "php", 20 | "gae", 21 | "direct", 22 | ], 23 | "ResponseFilters": [ 24 | "autorange", 25 | // "rewrite", 26 | ] 27 | }, 28 | "PHP": { 29 | "Enabled": false, 30 | "Address": "127.0.0.1:8088", 31 | "KeepAlivePeriod": 0, 32 | "ReadTimeout": 600, 33 | "WriteTimeout": 3600, 34 | "RequestFilters": [ 35 | "stripssl", 36 | ], 37 | "RoundTripFilters": [ 38 | "autoproxy", 39 | "php", 40 | ], 41 | "ResponseFilters": [ 42 | ] 43 | }, 44 | } 45 | -------------------------------------------------------------------------------- /httpproxy/proxy/direct.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package proxy 6 | 7 | import ( 8 | "net" 9 | ) 10 | 11 | type direct struct{} 12 | 13 | // Direct is a direct proxy: one that makes network connections directly. 14 | var Direct = direct{} 15 | 16 | func (direct) Dial(network, addr string) (net.Conn, error) { 17 | return net.Dial(network, addr) 18 | } 19 | -------------------------------------------------------------------------------- /httpproxy/proxy/dummy_resolver.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package proxy 6 | 7 | type dummyResolver struct{} 8 | 9 | // Direct is a direct proxy: one that makes network connections directly. 10 | var DummyResolver = dummyResolver{} 11 | 12 | func (dummyResolver) LookupHost(host string) (addrs []string, err error) { 13 | return []string{host}, nil 14 | } 15 | -------------------------------------------------------------------------------- /httpproxy/proxy/http1.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package proxy 6 | 7 | import ( 8 | "bufio" 9 | "bytes" 10 | "encoding/base64" 11 | "errors" 12 | "fmt" 13 | "io" 14 | "net" 15 | "net/http" 16 | "strconv" 17 | "sync" 18 | ) 19 | 20 | var ( 21 | CRLF = []byte{0x0d, 0x0a} 22 | CRLFCRLF = []byte{0x0d, 0x0a, 0x0d, 0x0a} 23 | ) 24 | 25 | var bufPool = sync.Pool{ 26 | New: func() interface{} { return new(bytes.Buffer) }, 27 | } 28 | 29 | func HTTP1(network, addr string, auth *Auth, forward Dialer, resolver Resolver) (Dialer, error) { 30 | var hostname string 31 | 32 | if host, _, err := net.SplitHostPort(addr); err == nil { 33 | hostname = host 34 | } else { 35 | hostname = addr 36 | addr = net.JoinHostPort(addr, "443") 37 | } 38 | 39 | s := &http1{ 40 | network: network, 41 | addr: addr, 42 | hostname: hostname, 43 | forward: forward, 44 | resolver: resolver, 45 | } 46 | if auth != nil { 47 | s.user = auth.User 48 | s.password = auth.Password 49 | } 50 | 51 | return s, nil 52 | } 53 | 54 | type http1 struct { 55 | user, password string 56 | network, addr string 57 | hostname string 58 | forward Dialer 59 | resolver Resolver 60 | } 61 | 62 | type preReaderConn struct { 63 | net.Conn 64 | data []byte 65 | } 66 | 67 | func (r *preReaderConn) Read(b []byte) (int, error) { 68 | if r.data == nil { 69 | return r.Conn.Read(b) 70 | } else { 71 | n := copy(b, r.data) 72 | if n < len(r.data) { 73 | r.data = r.data[n:] 74 | } else { 75 | r.data = nil 76 | } 77 | return n, nil 78 | } 79 | } 80 | 81 | // Dial connects to the address addr on the network net via the HTTP1 proxy. 82 | func (h *http1) Dial(network, addr string) (net.Conn, error) { 83 | switch network { 84 | case "tcp", "tcp6", "tcp4": 85 | default: 86 | return nil, errors.New("proxy: no support for HTTP proxy connections of type " + network) 87 | } 88 | 89 | conn, err := h.forward.Dial(h.network, h.addr) 90 | if err != nil { 91 | return nil, err 92 | } 93 | closeConn := &conn 94 | defer func() { 95 | if closeConn != nil { 96 | (*closeConn).Close() 97 | } 98 | }() 99 | 100 | host, portStr, err := net.SplitHostPort(addr) 101 | if err != nil { 102 | return nil, err 103 | } 104 | 105 | port, err := strconv.Atoi(portStr) 106 | if err != nil { 107 | return nil, errors.New("proxy: failed to parse port number: " + portStr) 108 | } 109 | if port < 1 || port > 0xffff { 110 | return nil, errors.New("proxy: port number out of range: " + portStr) 111 | } 112 | 113 | if h.resolver != nil { 114 | hosts, err := h.resolver.LookupHost(host) 115 | if err == nil && len(hosts) > 0 { 116 | host = hosts[0] 117 | } 118 | } 119 | 120 | b := bufPool.Get().(*bytes.Buffer) 121 | b.Reset() 122 | 123 | fmt.Fprintf(b, "CONNECT %s:%s HTTP/1.1\r\nHost: %s:%s\r\n", host, portStr, host, portStr) 124 | if h.user != "" { 125 | fmt.Fprintf(b, "Proxy-Authorization: Basic %s\r\n", base64.StdEncoding.EncodeToString([]byte(h.user+":"+h.password))) 126 | } 127 | io.WriteString(b, "\r\n") 128 | 129 | bb := b.Bytes() 130 | bufPool.Put(b) 131 | 132 | if _, err := conn.Write(bb); err != nil { 133 | return nil, errors.New("proxy: failed to write greeting to HTTP proxy at " + h.addr + ": " + err.Error()) 134 | } 135 | 136 | buf := make([]byte, 2048) 137 | b0 := buf 138 | total := 0 139 | 140 | for { 141 | n, err := conn.Read(buf) 142 | if err != nil { 143 | return nil, err 144 | } 145 | total += n 146 | buf = buf[n:] 147 | 148 | if i := bytes.Index(b0, CRLFCRLF); i > 0 { 149 | conn = &preReaderConn{conn, b0[i+4 : total]} 150 | b0 = b0[:i+4] 151 | break 152 | } 153 | } 154 | 155 | resp, err := http.ReadResponse(bufio.NewReader(bytes.NewReader(b0)), nil) 156 | if err != nil { 157 | return nil, err 158 | } 159 | 160 | if resp.StatusCode != http.StatusOK { 161 | return nil, errors.New("proxy: failed to read greeting from HTTP proxy at " + h.addr + ": " + resp.Status) 162 | } 163 | 164 | closeConn = nil 165 | return conn, nil 166 | } 167 | -------------------------------------------------------------------------------- /httpproxy/proxy/http2.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package proxy 6 | 7 | import ( 8 | "bufio" 9 | "bytes" 10 | "crypto/tls" 11 | "encoding/base64" 12 | "errors" 13 | "fmt" 14 | "io" 15 | "net" 16 | "net/http" 17 | "strconv" 18 | "time" 19 | 20 | "github.com/cloudflare/golibs/lrucache" 21 | ) 22 | 23 | func HTTP2(network, addr string, auth *Auth, forward Dialer, resolver Resolver) (Dialer, error) { 24 | var hostname string 25 | 26 | if host, _, err := net.SplitHostPort(addr); err == nil { 27 | hostname = host 28 | } else { 29 | hostname = addr 30 | addr = net.JoinHostPort(addr, "443") 31 | } 32 | 33 | s := &http2{ 34 | network: network, 35 | addr: addr, 36 | hostname: hostname, 37 | forward: forward, 38 | resolver: resolver, 39 | cache: lrucache.NewLRUCache(128), 40 | } 41 | if auth != nil { 42 | s.user = auth.User 43 | s.password = auth.Password 44 | } 45 | 46 | return s, nil 47 | } 48 | 49 | type http2 struct { 50 | user, password string 51 | network, addr string 52 | hostname string 53 | forward Dialer 54 | resolver Resolver 55 | cache lrucache.Cache 56 | } 57 | 58 | // Dial connects to the address addr on the network net via the HTTPS proxy. 59 | func (h *http2) Dial(network, addr string) (net.Conn, error) { 60 | switch network { 61 | case "tcp", "tcp6", "tcp4": 62 | default: 63 | return nil, errors.New("proxy: no support for HTTP proxy connections of type " + network) 64 | } 65 | 66 | conn0, err := h.forward.Dial(h.network, h.addr) 67 | if err != nil { 68 | return nil, err 69 | } 70 | closeConn0 := &conn0 71 | defer func() { 72 | if closeConn0 != nil { 73 | (*closeConn0).Close() 74 | } 75 | }() 76 | 77 | var config *tls.Config 78 | if v, ok := h.cache.GetNotStale(h.addr); ok { 79 | config = v.(*tls.Config) 80 | } else { 81 | config = &tls.Config{ 82 | MinVersion: tls.VersionTLS10, 83 | MaxVersion: tls.VersionTLS13, 84 | Max0RTTDataSize: 100 * 1024, 85 | // InsecureSkipVerify: true, 86 | ServerName: h.hostname, 87 | ClientSessionCache: tls.NewLRUClientSessionCache(1024), 88 | } 89 | h.cache.Set(h.addr, config, time.Now().Add(2*time.Hour)) 90 | } 91 | 92 | conn1 := tls.Client(conn0, config) 93 | 94 | err = conn1.Handshake() 95 | if err != nil { 96 | return nil, err 97 | } 98 | 99 | var conn net.Conn = conn1 100 | 101 | host, portStr, err := net.SplitHostPort(addr) 102 | if err != nil { 103 | return nil, err 104 | } 105 | 106 | port, err := strconv.Atoi(portStr) 107 | if err != nil { 108 | return nil, errors.New("proxy: failed to parse port number: " + portStr) 109 | } 110 | if port < 1 || port > 0xffff { 111 | return nil, errors.New("proxy: port number out of range: " + portStr) 112 | } 113 | 114 | if h.resolver != nil { 115 | hosts, err := h.resolver.LookupHost(host) 116 | if err == nil && len(hosts) > 0 { 117 | host = hosts[0] 118 | } 119 | } 120 | 121 | b := bufPool.Get().(*bytes.Buffer) 122 | b.Reset() 123 | 124 | fmt.Fprintf(b, "CONNECT %s:%s HTTP/1.1\r\nHost: %s:%s\r\n", host, portStr, host, portStr) 125 | if h.user != "" { 126 | fmt.Fprintf(b, "Proxy-Authorization: Basic %s\r\n", base64.StdEncoding.EncodeToString([]byte(h.user+":"+h.password))) 127 | } 128 | io.WriteString(b, "\r\n") 129 | 130 | bb := b.Bytes() 131 | bufPool.Put(b) 132 | 133 | if _, err := conn.Write(bb); err != nil { 134 | return nil, errors.New("proxy: failed to write greeting to HTTP proxy at " + h.addr + ": " + err.Error()) 135 | } 136 | 137 | buf := make([]byte, 2048) 138 | b0 := buf 139 | total := 0 140 | 141 | for { 142 | n, err := conn.Read(buf) 143 | if err != nil { 144 | return nil, err 145 | } 146 | total += n 147 | buf = buf[n:] 148 | 149 | if i := bytes.Index(b0, CRLFCRLF); i > 0 { 150 | conn = &preReaderConn{conn, b0[i+4 : total]} 151 | b0 = b0[:i+4] 152 | break 153 | } 154 | } 155 | 156 | resp, err := http.ReadResponse(bufio.NewReader(bytes.NewReader(b0)), nil) 157 | if err != nil { 158 | return nil, err 159 | } 160 | 161 | if resp.StatusCode != http.StatusOK { 162 | return nil, errors.New("proxy: failed to read greeting from HTTP proxy at " + h.addr + ": " + resp.Status) 163 | } 164 | 165 | closeConn0 = nil 166 | return conn, nil 167 | } 168 | -------------------------------------------------------------------------------- /httpproxy/proxy/https.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package proxy 6 | 7 | import ( 8 | "bufio" 9 | "bytes" 10 | "encoding/base64" 11 | "errors" 12 | "fmt" 13 | "io" 14 | "net" 15 | "net/http" 16 | "strconv" 17 | "time" 18 | 19 | "github.com/cloudflare/golibs/lrucache" 20 | tls "github.com/google/boringssl/ssl/test/runner" 21 | ) 22 | 23 | func HTTPS(network, addr string, auth *Auth, forward Dialer, resolver Resolver) (Dialer, error) { 24 | var hostname string 25 | 26 | if host, _, err := net.SplitHostPort(addr); err == nil { 27 | hostname = host 28 | } else { 29 | hostname = addr 30 | addr = net.JoinHostPort(addr, "443") 31 | } 32 | 33 | s := &https{ 34 | network: network, 35 | addr: addr, 36 | hostname: hostname, 37 | forward: forward, 38 | resolver: resolver, 39 | cache: lrucache.NewLRUCache(128), 40 | } 41 | if auth != nil { 42 | s.user = auth.User 43 | s.password = auth.Password 44 | } 45 | 46 | return s, nil 47 | } 48 | 49 | type https struct { 50 | user, password string 51 | network, addr string 52 | hostname string 53 | forward Dialer 54 | resolver Resolver 55 | cache lrucache.Cache 56 | } 57 | 58 | // Dial connects to the address addr on the network net via the HTTPS proxy. 59 | func (h *https) Dial(network, addr string) (net.Conn, error) { 60 | switch network { 61 | case "tcp", "tcp6", "tcp4": 62 | default: 63 | return nil, errors.New("proxy: no support for HTTP proxy connections of type " + network) 64 | } 65 | 66 | conn0, err := h.forward.Dial(h.network, h.addr) 67 | if err != nil { 68 | return nil, err 69 | } 70 | closeConn0 := &conn0 71 | defer func() { 72 | if closeConn0 != nil { 73 | (*closeConn0).Close() 74 | } 75 | }() 76 | 77 | var config *tls.Config 78 | if v, ok := h.cache.GetNotStale(h.addr); ok { 79 | config = v.(*tls.Config) 80 | } else { 81 | config = &tls.Config{ 82 | MinVersion: tls.VersionTLS10, 83 | MaxVersion: tls.VersionTLS13, 84 | // InsecureSkipVerify: true, 85 | ServerName: h.hostname, 86 | ClientSessionCache: tls.NewLRUClientSessionCache(1024), 87 | MaxEarlyDataSize: 100 * 1024, 88 | // Max0RTTDataSize: 100 * 1024, 89 | } 90 | h.cache.Set(h.addr, config, time.Now().Add(2*time.Hour)) 91 | } 92 | 93 | conn1 := tls.Client(conn0, config) 94 | 95 | err = conn1.Handshake() 96 | if err != nil { 97 | return nil, err 98 | } 99 | 100 | var conn net.Conn = conn1 101 | 102 | host, portStr, err := net.SplitHostPort(addr) 103 | if err != nil { 104 | return nil, err 105 | } 106 | 107 | port, err := strconv.Atoi(portStr) 108 | if err != nil { 109 | return nil, errors.New("proxy: failed to parse port number: " + portStr) 110 | } 111 | if port < 1 || port > 0xffff { 112 | return nil, errors.New("proxy: port number out of range: " + portStr) 113 | } 114 | 115 | if h.resolver != nil { 116 | hosts, err := h.resolver.LookupHost(host) 117 | if err == nil && len(hosts) > 0 { 118 | host = hosts[0] 119 | } 120 | } 121 | 122 | b := bufPool.Get().(*bytes.Buffer) 123 | b.Reset() 124 | 125 | fmt.Fprintf(b, "CONNECT %s:%s HTTP/1.1\r\nHost: %s:%s\r\n", host, portStr, host, portStr) 126 | if h.user != "" { 127 | fmt.Fprintf(b, "Proxy-Authorization: Basic %s\r\n", base64.StdEncoding.EncodeToString([]byte(h.user+":"+h.password))) 128 | } 129 | io.WriteString(b, "\r\n") 130 | 131 | bb := b.Bytes() 132 | bufPool.Put(b) 133 | 134 | if _, err := conn.Write(bb); err != nil { 135 | return nil, errors.New("proxy: failed to write greeting to HTTP proxy at " + h.addr + ": " + err.Error()) 136 | } 137 | 138 | buf := make([]byte, 2048) 139 | b0 := buf 140 | total := 0 141 | 142 | for { 143 | n, err := conn.Read(buf) 144 | if err != nil { 145 | return nil, err 146 | } 147 | total += n 148 | buf = buf[n:] 149 | 150 | if i := bytes.Index(b0, CRLFCRLF); i > 0 { 151 | conn = &preReaderConn{conn, b0[i+4 : total]} 152 | b0 = b0[:i+4] 153 | break 154 | } 155 | } 156 | 157 | resp, err := http.ReadResponse(bufio.NewReader(bytes.NewReader(b0)), nil) 158 | if err != nil { 159 | return nil, err 160 | } 161 | 162 | if resp.StatusCode != http.StatusOK { 163 | return nil, errors.New("proxy: failed to read greeting from HTTP proxy at " + h.addr + ": " + resp.Status) 164 | } 165 | 166 | closeConn0 = nil 167 | return conn, nil 168 | } 169 | -------------------------------------------------------------------------------- /httpproxy/proxy/per_host.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package proxy 6 | 7 | import ( 8 | "net" 9 | "strings" 10 | ) 11 | 12 | // A PerHost directs connections to a default Dialer unless the hostname 13 | // requested matches one of a number of exceptions. 14 | type PerHost struct { 15 | def, bypass Dialer 16 | 17 | bypassNetworks []*net.IPNet 18 | bypassIPs []net.IP 19 | bypassZones []string 20 | bypassHosts []string 21 | } 22 | 23 | // NewPerHost returns a PerHost Dialer that directs connections to either 24 | // defaultDialer or bypass, depending on whether the connection matches one of 25 | // the configured rules. 26 | func NewPerHost(defaultDialer, bypass Dialer) *PerHost { 27 | return &PerHost{ 28 | def: defaultDialer, 29 | bypass: bypass, 30 | } 31 | } 32 | 33 | // Dial connects to the address addr on the given network through either 34 | // defaultDialer or bypass. 35 | func (p *PerHost) Dial(network, addr string) (c net.Conn, err error) { 36 | host, _, err := net.SplitHostPort(addr) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | return p.dialerForRequest(host).Dial(network, addr) 42 | } 43 | 44 | func (p *PerHost) dialerForRequest(host string) Dialer { 45 | if ip := net.ParseIP(host); ip != nil { 46 | for _, net := range p.bypassNetworks { 47 | if net.Contains(ip) { 48 | return p.bypass 49 | } 50 | } 51 | for _, bypassIP := range p.bypassIPs { 52 | if bypassIP.Equal(ip) { 53 | return p.bypass 54 | } 55 | } 56 | return p.def 57 | } 58 | 59 | for _, zone := range p.bypassZones { 60 | if strings.HasSuffix(host, zone) { 61 | return p.bypass 62 | } 63 | if host == zone[1:] { 64 | // For a zone "example.com", we match "example.com" 65 | // too. 66 | return p.bypass 67 | } 68 | } 69 | for _, bypassHost := range p.bypassHosts { 70 | if bypassHost == host { 71 | return p.bypass 72 | } 73 | } 74 | return p.def 75 | } 76 | 77 | // AddFromString parses a string that contains comma-separated values 78 | // specifying hosts that should use the bypass proxy. Each value is either an 79 | // IP address, a CIDR range, a zone (*.example.com) or a hostname 80 | // (localhost). A best effort is made to parse the string and errors are 81 | // ignored. 82 | func (p *PerHost) AddFromString(s string) { 83 | hosts := strings.Split(s, ",") 84 | for _, host := range hosts { 85 | host = strings.TrimSpace(host) 86 | if len(host) == 0 { 87 | continue 88 | } 89 | if strings.Contains(host, "/") { 90 | // We assume that it's a CIDR address like 127.0.0.0/8 91 | if _, net, err := net.ParseCIDR(host); err == nil { 92 | p.AddNetwork(net) 93 | } 94 | continue 95 | } 96 | if ip := net.ParseIP(host); ip != nil { 97 | p.AddIP(ip) 98 | continue 99 | } 100 | if strings.HasPrefix(host, "*.") { 101 | p.AddZone(host[1:]) 102 | continue 103 | } 104 | p.AddHost(host) 105 | } 106 | } 107 | 108 | // AddIP specifies an IP address that will use the bypass proxy. Note that 109 | // this will only take effect if a literal IP address is dialed. A connection 110 | // to a named host will never match an IP. 111 | func (p *PerHost) AddIP(ip net.IP) { 112 | p.bypassIPs = append(p.bypassIPs, ip) 113 | } 114 | 115 | // AddNetwork specifies an IP range that will use the bypass proxy. Note that 116 | // this will only take effect if a literal IP address is dialed. A connection 117 | // to a named host will never match. 118 | func (p *PerHost) AddNetwork(net *net.IPNet) { 119 | p.bypassNetworks = append(p.bypassNetworks, net) 120 | } 121 | 122 | // AddZone specifies a DNS suffix that will use the bypass proxy. A zone of 123 | // "example.com" matches "example.com" and all of its subdomains. 124 | func (p *PerHost) AddZone(zone string) { 125 | if strings.HasSuffix(zone, ".") { 126 | zone = zone[:len(zone)-1] 127 | } 128 | if !strings.HasPrefix(zone, ".") { 129 | zone = "." + zone 130 | } 131 | p.bypassZones = append(p.bypassZones, zone) 132 | } 133 | 134 | // AddHost specifies a hostname that will use the bypass proxy. 135 | func (p *PerHost) AddHost(host string) { 136 | if strings.HasSuffix(host, ".") { 137 | host = host[:len(host)-1] 138 | } 139 | p.bypassHosts = append(p.bypassHosts, host) 140 | } 141 | -------------------------------------------------------------------------------- /httpproxy/proxy/per_host_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package proxy 6 | 7 | import ( 8 | "errors" 9 | "net" 10 | "reflect" 11 | "testing" 12 | ) 13 | 14 | type recordingProxy struct { 15 | addrs []string 16 | } 17 | 18 | func (r *recordingProxy) Dial(network, addr string) (net.Conn, error) { 19 | r.addrs = append(r.addrs, addr) 20 | return nil, errors.New("recordingProxy") 21 | } 22 | 23 | func TestPerHost(t *testing.T) { 24 | var def, bypass recordingProxy 25 | perHost := NewPerHost(&def, &bypass) 26 | perHost.AddFromString("localhost,*.zone,127.0.0.1,10.0.0.1/8,1000::/16") 27 | 28 | expectedDef := []string{ 29 | "example.com:123", 30 | "1.2.3.4:123", 31 | "[1001::]:123", 32 | } 33 | expectedBypass := []string{ 34 | "localhost:123", 35 | "zone:123", 36 | "foo.zone:123", 37 | "127.0.0.1:123", 38 | "10.1.2.3:123", 39 | "[1000::]:123", 40 | } 41 | 42 | for _, addr := range expectedDef { 43 | perHost.Dial("tcp", addr) 44 | } 45 | for _, addr := range expectedBypass { 46 | perHost.Dial("tcp", addr) 47 | } 48 | 49 | if !reflect.DeepEqual(expectedDef, def.addrs) { 50 | t.Errorf("Hosts which went to the default proxy didn't match. Got %v, want %v", def.addrs, expectedDef) 51 | } 52 | if !reflect.DeepEqual(expectedBypass, bypass.addrs) { 53 | t.Errorf("Hosts which went to the bypass proxy didn't match. Got %v, want %v", bypass.addrs, expectedBypass) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /httpproxy/proxy/proxy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package proxy provides support for a variety of protocols to proxy network 6 | // data. 7 | package proxy 8 | 9 | import ( 10 | "errors" 11 | "net" 12 | "net/url" 13 | "os" 14 | ) 15 | 16 | // A Dialer is a means to establish a connection. 17 | type Dialer interface { 18 | // Dial connects to the given address via the proxy. 19 | Dial(network, addr string) (c net.Conn, err error) 20 | } 21 | 22 | // A Resolver is a means to transform hostname. 23 | type Resolver interface { 24 | LookupHost(host string) (addrs []string, err error) 25 | } 26 | 27 | // Auth contains authentication parameters that specific Dialers may require. 28 | type Auth struct { 29 | User, Password string 30 | } 31 | 32 | // FromEnvironment returns the dialer specified by the proxy related variables in 33 | // the environment. 34 | func FromEnvironment() Dialer { 35 | allProxy := os.Getenv("all_proxy") 36 | if len(allProxy) == 0 { 37 | return Direct 38 | } 39 | 40 | proxyURL, err := url.Parse(allProxy) 41 | if err != nil { 42 | return Direct 43 | } 44 | proxy, err := FromURL(proxyURL, Direct, DummyResolver) 45 | if err != nil { 46 | return Direct 47 | } 48 | 49 | noProxy := os.Getenv("no_proxy") 50 | if len(noProxy) == 0 { 51 | return proxy 52 | } 53 | 54 | perHost := NewPerHost(proxy, Direct) 55 | perHost.AddFromString(noProxy) 56 | return perHost 57 | } 58 | 59 | // proxySchemes is a map from URL schemes to a function that creates a Dialer 60 | // from a URL with such a scheme. 61 | var proxySchemes map[string]func(*url.URL, Dialer) (Dialer, error) 62 | 63 | // RegisterDialerType takes a URL scheme and a function to generate Dialers from 64 | // a URL with that scheme and a forwarding Dialer. Registered schemes are used 65 | // by FromURL. 66 | func RegisterDialerType(scheme string, f func(*url.URL, Dialer) (Dialer, error)) { 67 | if proxySchemes == nil { 68 | proxySchemes = make(map[string]func(*url.URL, Dialer) (Dialer, error)) 69 | } 70 | proxySchemes[scheme] = f 71 | } 72 | 73 | // FromURL returns a Dialer given a URL specification and an underlying 74 | // Dialer for it to make network requests. 75 | func FromURL(u *url.URL, forward Dialer, resolver Resolver) (Dialer, error) { 76 | var auth *Auth 77 | if u.User != nil { 78 | auth = new(Auth) 79 | auth.User = u.User.Username() 80 | if p, ok := u.User.Password(); ok { 81 | auth.Password = p 82 | } 83 | } 84 | 85 | switch u.Scheme { 86 | case "socks5", "socks": 87 | return SOCKS5("tcp", u.Host, auth, forward, resolver) 88 | case "socks4": 89 | return SOCKS4("tcp", u.Host, false, forward, resolver) 90 | case "socks4a": 91 | return SOCKS4("tcp", u.Host, true, forward, resolver) 92 | case "http": 93 | return HTTP1("tcp", u.Host, auth, forward, resolver) 94 | case "https": 95 | return HTTPS("tcp", u.Host, auth, forward, resolver) 96 | case "https+h2": 97 | return HTTP2("tcp", u.Host, auth, forward, resolver) 98 | case "ssh", "ssh2": 99 | return SSH2("tcp", u.Host, auth, forward, resolver) 100 | case "quic": 101 | return QUIC("udp", u.Host, auth, forward, resolver) 102 | } 103 | 104 | // If the scheme doesn't match any of the built-in schemes, see if it 105 | // was registered by another package. 106 | if proxySchemes != nil { 107 | if f, ok := proxySchemes[u.Scheme]; ok { 108 | return f(u, forward) 109 | } 110 | } 111 | 112 | return nil, errors.New("proxy: unknown scheme: " + u.Scheme) 113 | } 114 | -------------------------------------------------------------------------------- /httpproxy/proxy/proxy_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package proxy 6 | 7 | import ( 8 | "io" 9 | "net" 10 | "net/url" 11 | "strconv" 12 | "sync" 13 | "testing" 14 | ) 15 | 16 | func TestFromURL(t *testing.T) { 17 | endSystem, err := net.Listen("tcp", "127.0.0.1:0") 18 | if err != nil { 19 | t.Fatalf("net.Listen failed: %v", err) 20 | } 21 | defer endSystem.Close() 22 | gateway, err := net.Listen("tcp", "127.0.0.1:0") 23 | if err != nil { 24 | t.Fatalf("net.Listen failed: %v", err) 25 | } 26 | defer gateway.Close() 27 | 28 | var wg sync.WaitGroup 29 | wg.Add(1) 30 | go socks5Gateway(t, gateway, endSystem, socks5Domain, &wg) 31 | 32 | url, err := url.Parse("socks5://user:password@" + gateway.Addr().String()) 33 | if err != nil { 34 | t.Fatalf("url.Parse failed: %v", err) 35 | } 36 | proxy, err := FromURL(url, Direct) 37 | if err != nil { 38 | t.Fatalf("FromURL failed: %v", err) 39 | } 40 | _, port, err := net.SplitHostPort(endSystem.Addr().String()) 41 | if err != nil { 42 | t.Fatalf("net.SplitHostPort failed: %v", err) 43 | } 44 | if c, err := proxy.Dial("tcp", "localhost:"+port); err != nil { 45 | t.Fatalf("FromURL.Dial failed: %v", err) 46 | } else { 47 | c.Close() 48 | } 49 | 50 | wg.Wait() 51 | } 52 | 53 | func TestSOCKS5(t *testing.T) { 54 | endSystem, err := net.Listen("tcp", "127.0.0.1:0") 55 | if err != nil { 56 | t.Fatalf("net.Listen failed: %v", err) 57 | } 58 | defer endSystem.Close() 59 | gateway, err := net.Listen("tcp", "127.0.0.1:0") 60 | if err != nil { 61 | t.Fatalf("net.Listen failed: %v", err) 62 | } 63 | defer gateway.Close() 64 | 65 | var wg sync.WaitGroup 66 | wg.Add(1) 67 | go socks5Gateway(t, gateway, endSystem, socks5IP4, &wg) 68 | 69 | proxy, err := SOCKS5("tcp", gateway.Addr().String(), nil, Direct) 70 | if err != nil { 71 | t.Fatalf("SOCKS5 failed: %v", err) 72 | } 73 | if c, err := proxy.Dial("tcp", endSystem.Addr().String()); err != nil { 74 | t.Fatalf("SOCKS5.Dial failed: %v", err) 75 | } else { 76 | c.Close() 77 | } 78 | 79 | wg.Wait() 80 | } 81 | 82 | func socks5Gateway(t *testing.T, gateway, endSystem net.Listener, typ byte, wg *sync.WaitGroup) { 83 | defer wg.Done() 84 | 85 | c, err := gateway.Accept() 86 | if err != nil { 87 | t.Errorf("net.Listener.Accept failed: %v", err) 88 | return 89 | } 90 | defer c.Close() 91 | 92 | b := make([]byte, 32) 93 | var n int 94 | if typ == socks5Domain { 95 | n = 4 96 | } else { 97 | n = 3 98 | } 99 | if _, err := io.ReadFull(c, b[:n]); err != nil { 100 | t.Errorf("io.ReadFull failed: %v", err) 101 | return 102 | } 103 | if _, err := c.Write([]byte{socks5Version, socks5AuthNone}); err != nil { 104 | t.Errorf("net.Conn.Write failed: %v", err) 105 | return 106 | } 107 | if typ == socks5Domain { 108 | n = 16 109 | } else { 110 | n = 10 111 | } 112 | if _, err := io.ReadFull(c, b[:n]); err != nil { 113 | t.Errorf("io.ReadFull failed: %v", err) 114 | return 115 | } 116 | if b[0] != socks5Version || b[1] != socks5Connect || b[2] != 0x00 || b[3] != typ { 117 | t.Errorf("got an unexpected packet: %#02x %#02x %#02x %#02x", b[0], b[1], b[2], b[3]) 118 | return 119 | } 120 | if typ == socks5Domain { 121 | copy(b[:5], []byte{socks5Version, 0x00, 0x00, socks5Domain, 9}) 122 | b = append(b, []byte("localhost")...) 123 | } else { 124 | copy(b[:4], []byte{socks5Version, 0x00, 0x00, socks5IP4}) 125 | } 126 | host, port, err := net.SplitHostPort(endSystem.Addr().String()) 127 | if err != nil { 128 | t.Errorf("net.SplitHostPort failed: %v", err) 129 | return 130 | } 131 | b = append(b, []byte(net.ParseIP(host).To4())...) 132 | p, err := strconv.Atoi(port) 133 | if err != nil { 134 | t.Errorf("strconv.Atoi failed: %v", err) 135 | return 136 | } 137 | b = append(b, []byte{byte(p >> 8), byte(p)}...) 138 | if _, err := c.Write(b); err != nil { 139 | t.Errorf("net.Conn.Write failed: %v", err) 140 | return 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /httpproxy/proxy/quic.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package proxy 6 | 7 | import ( 8 | "crypto/tls" 9 | "errors" 10 | "net" 11 | "net/http" 12 | "net/url" 13 | "time" 14 | 15 | "github.com/phuslu/glog" 16 | quic "github.com/phuslu/quic-go" 17 | "github.com/phuslu/quic-go/h2quic" 18 | ) 19 | 20 | func QUIC(network, addr string, auth *Auth, forward Dialer, resolver Resolver) (Dialer, error) { 21 | var hostname string 22 | 23 | if host, _, err := net.SplitHostPort(addr); err == nil { 24 | hostname = host 25 | } else { 26 | hostname = addr 27 | addr = net.JoinHostPort(addr, "443") 28 | } 29 | 30 | s := &Quic{ 31 | network: network, 32 | addr: addr, 33 | hostname: hostname, 34 | forward: forward, 35 | resolver: resolver, 36 | transport: &h2quic.RoundTripper{ 37 | DisableCompression: true, 38 | QuicConfig: &quic.Config{ 39 | HandshakeTimeout: 5 * time.Second, 40 | IdleTimeout: 10 * time.Second, 41 | RequestConnectionIDTruncation: true, 42 | KeepAlive: true, 43 | }, 44 | KeepAliveTimeout: 30 * time.Minute, 45 | IdleConnTimeout: 5 * time.Minute, 46 | ResponseHeaderTimeout: 5 * time.Second, 47 | DialAddr: func(address string, tlsConfig *tls.Config, cfg *quic.Config) (quic.Session, error) { 48 | return quic.DialAddr(addr, tlsConfig, cfg) 49 | }, 50 | GetClientKey: func(_ string) string { 51 | return addr 52 | }, 53 | }, 54 | } 55 | if auth != nil { 56 | s.user = auth.User 57 | s.password = auth.Password 58 | } 59 | 60 | return s, nil 61 | } 62 | 63 | type Quic struct { 64 | user, password string 65 | network, addr string 66 | hostname string 67 | forward Dialer 68 | resolver Resolver 69 | transport *h2quic.RoundTripper 70 | } 71 | 72 | // Dial connects to the address addr on the network net via the HTTPS proxy. 73 | func (h *Quic) Dial(network, addr string) (net.Conn, error) { 74 | switch network { 75 | case "tcp", "tcp6", "tcp4": 76 | default: 77 | return nil, errors.New("proxy: no support for QUIC proxy connections of type " + network) 78 | } 79 | 80 | req := &http.Request{ 81 | Method: http.MethodConnect, 82 | Host: addr, 83 | Header: http.Header{}, 84 | URL: &url.URL{ 85 | Scheme: "https", 86 | Host: addr, 87 | }, 88 | } 89 | 90 | resp, err := h.transport.RoundTripOpt(req, h2quic.RoundTripOpt{OnlyCachedConn: true}) 91 | if err != nil { 92 | glog.Warningf("%T.RoundTripOpt(%#v) error: %+v", h.transport, req.URL.String(), err) 93 | h.transport.Close() 94 | resp, err = h.transport.RoundTripOpt(req, h2quic.RoundTripOpt{OnlyCachedConn: false}) 95 | } 96 | 97 | if err != nil { 98 | return nil, err 99 | } 100 | 101 | if resp.StatusCode != http.StatusOK { 102 | return nil, errors.New("proxy: failed to read greeting from HTTP proxy at " + h.addr + ": " + resp.Status) 103 | } 104 | 105 | stream, ok := resp.Body.(quic.Stream) 106 | if !ok || stream == nil { 107 | return nil, errors.New("proxy: failed to convert resp.Body to a quic.Stream") 108 | } 109 | 110 | return stream, nil 111 | } 112 | -------------------------------------------------------------------------------- /httpproxy/proxy/socks4.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package proxy 6 | 7 | import ( 8 | "errors" 9 | "io" 10 | "net" 11 | "strconv" 12 | ) 13 | 14 | // SOCKS4 returns a Dialer that makes SOCKSv4 connections to the given address 15 | func SOCKS4(network, addr string, is4a bool, forward Dialer, resolver Resolver) (Dialer, error) { 16 | s := &socks4{ 17 | network: network, 18 | addr: addr, 19 | is4a: is4a, 20 | forward: forward, 21 | resolver: resolver, 22 | } 23 | 24 | return s, nil 25 | } 26 | 27 | type socks4 struct { 28 | network, addr string 29 | is4a bool 30 | forward Dialer 31 | resolver Resolver 32 | } 33 | 34 | const ( 35 | socks4Version = 4 36 | socks4Connect = 1 37 | socks4Granted = 0x5a 38 | socks4Rejected = 0x5b 39 | socks4IdentdRequired = 0x5c 40 | socks4IdentdFailed = 0x5d 41 | ) 42 | 43 | var socks4Errors = []string{ 44 | "", 45 | "connection forbidden", 46 | "identd required", 47 | "identd failed", 48 | } 49 | 50 | // Dial connects to the address addr on the network net via the SOCKS4 proxy. 51 | func (s *socks4) Dial(network, addr string) (net.Conn, error) { 52 | switch network { 53 | case "tcp", "tcp4": 54 | default: 55 | return nil, errors.New("proxy: no support for SOCKS4 proxy connections of type " + network) 56 | } 57 | 58 | conn, err := s.forward.Dial(s.network, s.addr) 59 | if err != nil { 60 | return nil, err 61 | } 62 | closeConn := &conn 63 | defer func() { 64 | if closeConn != nil { 65 | (*closeConn).Close() 66 | } 67 | }() 68 | 69 | host, portStr, err := net.SplitHostPort(addr) 70 | if err != nil { 71 | return nil, err 72 | } 73 | 74 | port, err := strconv.Atoi(portStr) 75 | if err != nil { 76 | return nil, errors.New("proxy: failed to parse port number: " + portStr) 77 | } 78 | if port < 1 || port > 0xffff { 79 | return nil, errors.New("proxy: port number out of range: " + portStr) 80 | } 81 | 82 | if s.resolver != nil { 83 | if hosts, err := s.resolver.LookupHost(host); err == nil && len(hosts) > 0 { 84 | host = hosts[0] 85 | } 86 | } 87 | 88 | buf := make([]byte, 0, 1024) 89 | 90 | buf = append(buf, socks4Version, socks4Connect) 91 | buf = append(buf, byte(port>>8), byte(port)) 92 | if s.is4a { 93 | buf = append(buf, 0, 0, 0, 1, 0) 94 | buf = append(buf, []byte(host+"\x00")...) 95 | } else { 96 | ip, err := net.ResolveIPAddr("ip4", host) 97 | if err != nil { 98 | return nil, err 99 | } 100 | ip4 := ip.IP.To4() 101 | if len(ip4) < 4 { 102 | return nil, errors.New("proxy: resolve ip address out of range: " + ip.String()) 103 | } 104 | buf = append(buf, ip4[0], ip4[1], ip4[2], ip4[3], 0) 105 | } 106 | 107 | _, err = conn.Write(buf) 108 | if err != nil { 109 | return nil, err 110 | } 111 | 112 | var resp [8]byte 113 | _, err = conn.Read(resp[:]) 114 | if err != nil && err != io.EOF { 115 | return nil, err 116 | } 117 | 118 | switch code := resp[1]; code { 119 | case socks4Granted: 120 | break 121 | case socks4Rejected, socks4IdentdRequired, socks4IdentdFailed: 122 | return nil, errors.New("proxy: SOCKS4 proxy at " + s.addr + " failed to connect: " + socks4Errors[code-socks4Granted]) 123 | default: 124 | return nil, errors.New("proxy: SOCKS4 proxy at " + s.addr + " failed to connect: errno 0x" + strconv.FormatInt(int64(code), 16)) 125 | } 126 | 127 | closeConn = nil 128 | return conn, nil 129 | } 130 | -------------------------------------------------------------------------------- /httpproxy/proxy/ssh2.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package proxy 6 | 7 | import ( 8 | "errors" 9 | "net" 10 | "strconv" 11 | 12 | "golang.org/x/crypto/ssh" 13 | ) 14 | 15 | func SSH2(network, addr string, auth *Auth, forward Dialer, resolver Resolver) (Dialer, error) { 16 | s := &ssh2{ 17 | network: network, 18 | addr: addr, 19 | forward: forward, 20 | resolver: resolver, 21 | user: auth.User, 22 | password: auth.Password, 23 | } 24 | 25 | return s, nil 26 | } 27 | 28 | type ssh2 struct { 29 | user, password string 30 | network, addr string 31 | forward Dialer 32 | resolver Resolver 33 | } 34 | 35 | type sshConn struct { 36 | net.Conn 37 | sshClient *ssh.Client 38 | } 39 | 40 | func (c *sshConn) Close() error { 41 | defer c.sshClient.Close() 42 | return c.Conn.Close() 43 | } 44 | 45 | // Dial connects to the address addr on the network net via the HTTP1 proxy. 46 | func (s *ssh2) Dial(network, addr string) (net.Conn, error) { 47 | switch network { 48 | case "tcp", "tcp6", "tcp4": 49 | default: 50 | return nil, errors.New("proxy: no support for HTTP proxy connections of type " + network) 51 | } 52 | 53 | config := &ssh.ClientConfig{ 54 | User: s.user, 55 | Auth: []ssh.AuthMethod{ 56 | ssh.Password(s.password), 57 | }, 58 | } 59 | 60 | conn, err := ssh.Dial(s.network, s.addr, config) 61 | if err != nil { 62 | return nil, err 63 | } 64 | closeConn := &conn 65 | defer func() { 66 | if closeConn != nil { 67 | (*closeConn).Close() 68 | } 69 | }() 70 | 71 | host, portStr, err := net.SplitHostPort(addr) 72 | if err != nil { 73 | return nil, err 74 | } 75 | 76 | port, err := strconv.Atoi(portStr) 77 | if err != nil { 78 | return nil, errors.New("proxy: failed to parse port number: " + portStr) 79 | } 80 | if port < 1 || port > 0xffff { 81 | return nil, errors.New("proxy: port number out of range: " + portStr) 82 | } 83 | 84 | if s.resolver != nil { 85 | hosts, err := s.resolver.LookupHost(host) 86 | if err == nil && len(hosts) > 0 { 87 | host = hosts[0] 88 | } 89 | } 90 | 91 | ips, err := net.LookupIP(host) 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | raddr := &net.TCPAddr{IP: ips[0], Port: port} 97 | conn1, err := conn.DialTCP(network, nil, raddr) 98 | if err != nil { 99 | return nil, err 100 | } 101 | 102 | closeConn = nil 103 | return &sshConn{conn1, conn}, nil 104 | } 105 | -------------------------------------------------------------------------------- /httpproxy/storage/file.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "io/ioutil" 7 | "mime" 8 | "net/http" 9 | "os" 10 | "path" 11 | "path/filepath" 12 | "strings" 13 | ) 14 | 15 | type FileStore struct { 16 | Dirname string 17 | } 18 | 19 | var _ Store = &FileStore{} 20 | 21 | func (s *FileStore) Get(name string) (*http.Response, error) { 22 | req, err := http.NewRequest(http.MethodGet, "/"+strings.TrimLeft(name, "/"), nil) 23 | 24 | filename := filepath.Join(s.Dirname, name) 25 | 26 | fi, err := os.Stat(filename) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | data, err := ioutil.ReadFile(filename) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | resp := &http.Response{ 37 | StatusCode: http.StatusOK, 38 | Header: http.Header{}, 39 | Request: req, 40 | Close: true, 41 | ContentLength: fi.Size(), 42 | Body: ioutil.NopCloser(bytes.NewReader(data)), 43 | } 44 | 45 | resp.Header.Set("Last-Modified", fi.ModTime().Format(DateFormat)) 46 | resp.Header.Set("Content-Type", mime.TypeByExtension(filepath.Ext(filename))) 47 | 48 | return resp, nil 49 | } 50 | 51 | func (s *FileStore) List(name string) ([]string, error) { 52 | fis, err := ioutil.ReadDir(filepath.Join(s.Dirname, name)) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | names := make([]string, len(fis)) 58 | for i, fi := range fis { 59 | names[i] = path.Join(name, fi.Name()) 60 | } 61 | 62 | return names, nil 63 | } 64 | 65 | func (s *FileStore) Head(name string) (*http.Response, error) { 66 | filename := filepath.Join(s.Dirname, name) 67 | 68 | req, _ := http.NewRequest(http.MethodHead, "/"+strings.TrimLeft(name, "/"), nil) 69 | 70 | fi, err := os.Stat(filename) 71 | if err != nil { 72 | return nil, err 73 | } 74 | 75 | resp := &http.Response{ 76 | StatusCode: http.StatusOK, 77 | Header: http.Header{}, 78 | Request: req, 79 | Close: true, 80 | ContentLength: fi.Size(), 81 | } 82 | 83 | resp.Header.Set("Last-Modified", fi.ModTime().Format(DateFormat)) 84 | resp.Header.Set("Content-Type", mime.TypeByExtension(filepath.Ext(filename))) 85 | 86 | return resp, nil 87 | } 88 | 89 | func (s *FileStore) Put(name string, header http.Header, data io.ReadCloser) (*http.Response, error) { 90 | defer data.Close() 91 | 92 | filename := filepath.Join(s.Dirname, name) 93 | 94 | if err := os.MkdirAll(filepath.Dir(filename), 0755); err != nil { 95 | return nil, err 96 | } 97 | 98 | f, err := ioutil.TempFile(filepath.Dir(filename), filepath.Base(filename)+".") 99 | if err != nil { 100 | return nil, err 101 | } 102 | 103 | if _, err = io.Copy(f, data); err != nil { 104 | return nil, err 105 | } 106 | if err = f.Close(); err != nil { 107 | return nil, err 108 | } 109 | 110 | if err = os.Chmod(f.Name(), 0644); err != nil { 111 | return nil, err 112 | } 113 | 114 | if err = os.Rename(f.Name(), filename); err != nil { 115 | return nil, err 116 | } 117 | 118 | resp := &http.Response{ 119 | StatusCode: http.StatusOK, 120 | Header: http.Header{}, 121 | ContentLength: 0, 122 | } 123 | 124 | return resp, nil 125 | } 126 | 127 | func (s *FileStore) Copy(dest string, src string) (*http.Response, error) { 128 | data, err := ioutil.ReadFile(filepath.Join(s.Dirname, src)) 129 | if err != nil { 130 | return nil, err 131 | } 132 | 133 | err = ioutil.WriteFile(filepath.Join(s.Dirname, dest), data, 0644) 134 | if err != nil { 135 | return nil, err 136 | } 137 | 138 | resp := &http.Response{ 139 | StatusCode: http.StatusOK, 140 | Header: http.Header{}, 141 | ContentLength: 0, 142 | } 143 | 144 | return resp, nil 145 | } 146 | 147 | func (s *FileStore) Delete(object string) (*http.Response, error) { 148 | filename := filepath.Join(s.Dirname, object) 149 | 150 | if err := os.Remove(filename); err != nil { 151 | return nil, err 152 | } 153 | 154 | resp := &http.Response{ 155 | StatusCode: http.StatusOK, 156 | Header: http.Header{}, 157 | ContentLength: 0, 158 | } 159 | 160 | return resp, nil 161 | } 162 | 163 | func (s *FileStore) UnmarshallJson(name string, config interface{}) error { 164 | return readJsonConfig(s, name, config) 165 | } 166 | -------------------------------------------------------------------------------- /httpproxy/storage/json.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "path" 10 | "strings" 11 | ) 12 | 13 | func readJsonConfig(store Store, filename string, config interface{}) error { 14 | fileext := path.Ext(filename) 15 | filename1 := strings.TrimSuffix(filename, fileext) + ".user" + fileext 16 | 17 | cm := make(map[string]interface{}) 18 | for i, name := range []string{filename, filename1} { 19 | resp, err := store.Get(name) 20 | if err != nil { 21 | if i == 0 { 22 | return err 23 | } else { 24 | continue 25 | } 26 | } 27 | 28 | if resp.Body != nil { 29 | defer resp.Body.Close() 30 | 31 | data, err := readJson(resp.Body) 32 | if err != nil { 33 | return err 34 | } 35 | 36 | data = bytes.TrimPrefix(data, []byte("\xef\xbb\xbf")) 37 | 38 | cm1 := make(map[string]interface{}) 39 | 40 | d := json.NewDecoder(bytes.NewReader(data)) 41 | d.UseNumber() 42 | 43 | if err = d.Decode(&cm1); err != nil { 44 | return err 45 | } 46 | 47 | if err = mergeMap(cm, cm1); err != nil { 48 | return err 49 | } 50 | } 51 | } 52 | 53 | data, err := json.Marshal(cm) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | d := json.NewDecoder(bytes.NewReader(data)) 59 | d.UseNumber() 60 | 61 | return d.Decode(config) 62 | } 63 | 64 | func readJson(r io.Reader) ([]byte, error) { 65 | 66 | s, err := ioutil.ReadAll(r) 67 | if err != nil { 68 | return s, err 69 | } 70 | 71 | lines := make([]string, 0) 72 | for _, line := range strings.Split(strings.Replace(string(s), "\r\n", "\n", -1), "\n") { 73 | line = strings.TrimSpace(line) 74 | if line == "" || strings.HasPrefix(line, "//") { 75 | continue 76 | } 77 | lines = append(lines, line) 78 | } 79 | 80 | var b bytes.Buffer 81 | for i, line := range lines { 82 | if i < len(lines)-1 { 83 | nextLine := strings.TrimSpace(lines[i+1]) 84 | if nextLine == "]" || 85 | nextLine == "]," || 86 | nextLine == "}" || 87 | nextLine == "}," { 88 | if strings.HasSuffix(line, ",") { 89 | line = strings.TrimSuffix(line, ",") 90 | } 91 | } 92 | } 93 | b.WriteString(line) 94 | } 95 | 96 | return b.Bytes(), nil 97 | } 98 | 99 | func mergeMap(m1 map[string]interface{}, m2 map[string]interface{}) error { 100 | for key, value := range m2 { 101 | 102 | m1v, m1_has_key := m1[key] 103 | m2v, m2v_is_map := value.(map[string]interface{}) 104 | m1v1, m1v_is_map := m1v.(map[string]interface{}) 105 | 106 | switch { 107 | case !m1_has_key, !m2v_is_map: 108 | m1[key] = value 109 | case !m1v_is_map: 110 | return fmt.Errorf("m1v=%#v is not a map, but m2v=%#v is a map", m1v, m2v) 111 | default: 112 | mergeMap(m1v1, m2v) 113 | } 114 | } 115 | 116 | return nil 117 | } 118 | -------------------------------------------------------------------------------- /httpproxy/storage/storage.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "net/http" 7 | "os" 8 | "path/filepath" 9 | ) 10 | 11 | const ( 12 | DateFormat = "Mon, 02 Jan 2006 15:04:05 GMT" 13 | ) 14 | 15 | var ( 16 | ErrNotImplemented error = errors.New("Not Implemented") 17 | ) 18 | 19 | type Store interface { 20 | Get(name string) (*http.Response, error) 21 | List(name string) ([]string, error) 22 | Put(name string, header http.Header, data io.ReadCloser) (*http.Response, error) 23 | Copy(dest string, src string) (*http.Response, error) 24 | Head(name string) (*http.Response, error) 25 | Delete(name string) (*http.Response, error) 26 | UnmarshallJson(name string, config interface{}) error 27 | } 28 | 29 | // Lookup config uri by filename 30 | func LookupStoreByFilterName(name string) Store { 31 | var store Store 32 | 33 | exe, err := os.Executable() 34 | if err != nil { 35 | println("os.Executable() error: ", err) 36 | } 37 | 38 | for _, dirname := range []string{filepath.Dir(exe), ".", "httpproxy", "httpproxy/filters/" + name} { 39 | filename := dirname + "/" + name + ".json" 40 | if _, err := os.Stat(filename); err == nil { 41 | store = &FileStore{dirname} 42 | break 43 | } 44 | } 45 | if store == nil { 46 | store = &FileStore{"."} 47 | } 48 | return store 49 | } 50 | 51 | func IsNotExist(resp *http.Response, err error) bool { 52 | return os.IsNotExist(err) || (resp != nil && resp.StatusCode == http.StatusNotFound) 53 | } 54 | -------------------------------------------------------------------------------- /httpproxy/storage/zip.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "archive/zip" 5 | "io" 6 | "mime" 7 | "net/http" 8 | "os" 9 | "path/filepath" 10 | "strconv" 11 | "strings" 12 | ) 13 | 14 | type ZipStore struct { 15 | Filename string 16 | zfs map[string]*zip.File 17 | } 18 | 19 | var _ Store = &ZipStore{} 20 | 21 | func (s *ZipStore) init() error { 22 | if s.zfs != nil { 23 | return nil 24 | } 25 | 26 | var err error 27 | 28 | f, err := os.OpenFile(s.Filename, os.O_RDWR, 0644) 29 | if err != nil { 30 | return err 31 | } 32 | 33 | fi, err := f.Stat() 34 | if err != nil { 35 | return err 36 | } 37 | 38 | zr, err := zip.NewReader(f, fi.Size()) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | s.zfs = make(map[string]*zip.File) 44 | for _, zf := range zr.File { 45 | s.zfs[zf.Name] = zf 46 | } 47 | 48 | return nil 49 | } 50 | 51 | func (s *ZipStore) Get(name string) (*http.Response, error) { 52 | if err := s.init(); err != nil { 53 | return nil, err 54 | } 55 | 56 | zf, ok := s.zfs[name] 57 | if !ok { 58 | return nil, os.ErrNotExist 59 | } 60 | 61 | req, err := http.NewRequest(http.MethodGet, "/"+strings.TrimLeft(name, "/"), nil) 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | rc, err := zf.Open() 67 | if err != nil { 68 | return nil, err 69 | } 70 | 71 | resp := &http.Response{ 72 | StatusCode: http.StatusOK, 73 | Header: http.Header{}, 74 | Request: req, 75 | Close: true, 76 | ContentLength: int64(zf.UncompressedSize64), 77 | Body: rc, 78 | } 79 | 80 | resp.Header.Set("Last-Modified", zf.ModTime().Format(DateFormat)) 81 | resp.Header.Set("Content-Length", strconv.FormatUint(zf.UncompressedSize64, 10)) 82 | if ct := mime.TypeByExtension(filepath.Ext(zf.Name)); ct != "" { 83 | resp.Header.Set("Content-Type", ct) 84 | } 85 | 86 | return resp, nil 87 | } 88 | 89 | func (s *ZipStore) List(name string) ([]string, error) { 90 | if err := s.init(); err != nil { 91 | return nil, err 92 | } 93 | 94 | prefix := strings.TrimRight(name, "/") + "/" 95 | names := make([]string, 0) 96 | for key := range s.zfs { 97 | if strings.HasPrefix(key, prefix) { 98 | names = append(names, key) 99 | } 100 | } 101 | 102 | return names, nil 103 | } 104 | 105 | func (s *ZipStore) Head(name string) (*http.Response, error) { 106 | if err := s.init(); err != nil { 107 | return nil, err 108 | } 109 | 110 | zf, ok := s.zfs[name] 111 | if !ok { 112 | return nil, os.ErrNotExist 113 | } 114 | 115 | req, err := http.NewRequest(http.MethodGet, "/"+strings.TrimLeft(name, "/"), nil) 116 | if err != nil { 117 | return nil, err 118 | } 119 | 120 | resp := &http.Response{ 121 | StatusCode: http.StatusOK, 122 | Header: http.Header{}, 123 | Request: req, 124 | Close: true, 125 | ContentLength: int64(zf.UncompressedSize64), 126 | } 127 | 128 | resp.Header.Set("Last-Modified", zf.ModTime().Format(DateFormat)) 129 | resp.Header.Set("Content-Length", strconv.FormatUint(zf.UncompressedSize64, 10)) 130 | if ct := mime.TypeByExtension(filepath.Ext(zf.Name)); ct != "" { 131 | resp.Header.Set("Content-Type", ct) 132 | } 133 | 134 | return resp, nil 135 | } 136 | 137 | func (s *ZipStore) Put(name string, header http.Header, data io.ReadCloser) (*http.Response, error) { 138 | return nil, ErrNotImplemented 139 | } 140 | 141 | func (s *ZipStore) Copy(dest string, src string) (*http.Response, error) { 142 | return nil, ErrNotImplemented 143 | } 144 | 145 | func (s *ZipStore) Delete(name string) (*http.Response, error) { 146 | return nil, ErrNotImplemented 147 | } 148 | 149 | func (s *ZipStore) UnmarshallJson(name string, config interface{}) error { 150 | return readJsonConfig(s, name, config) 151 | } 152 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "math/rand" 7 | "net" 8 | "os" 9 | "runtime" 10 | "strconv" 11 | "strings" 12 | "time" 13 | 14 | "github.com/phuslu/glog" 15 | 16 | "./httpproxy" 17 | "./httpproxy/filters" 18 | "./httpproxy/helpers" 19 | "./httpproxy/storage" 20 | 21 | "./httpproxy/filters/gae" 22 | "./httpproxy/filters/php" 23 | ) 24 | 25 | var ( 26 | version = "r9999" 27 | ) 28 | 29 | func init() { 30 | rand.Seed(time.Now().UnixNano()) 31 | } 32 | 33 | func main() { 34 | 35 | if len(os.Args) > 1 { 36 | var line string 37 | switch os.Args[1] { 38 | case "-version": 39 | line = version 40 | case "-arch": 41 | line = runtime.GOARCH 42 | case "-os": 43 | line = runtime.GOOS 44 | } 45 | if line != "" { 46 | fmt.Println(line) 47 | return 48 | } 49 | } 50 | 51 | helpers.SetFlagsIfAbsent(map[string]string{ 52 | "logtostderr": "true", 53 | "v": "2", 54 | }) 55 | 56 | flag.Parse() 57 | 58 | gover := strings.Split(strings.TrimPrefix(runtime.Version(), "devel +"), " ")[0] 59 | 60 | switch runtime.GOARCH { 61 | case "386", "amd64": 62 | helpers.SetConsoleTitle(fmt.Sprintf("GoProxy %s (go/%s)", version, gover)) 63 | } 64 | 65 | config := make(map[string]httpproxy.Config) 66 | filename := "httpproxy.json" 67 | err := storage.LookupStoreByFilterName("httpproxy").UnmarshallJson(filename, &config) 68 | if err != nil { 69 | fmt.Printf("storage.LookupStoreByFilterName(%#v) failed: %s\n", filename, err) 70 | return 71 | } 72 | 73 | fmt.Fprintf(os.Stderr, `------------------------------------------------------ 74 | GoProxy Version : %s (go/%s %s/%s)`, 75 | version, gover, runtime.GOOS, runtime.GOARCH) 76 | for profile, config := range config { 77 | if !config.Enabled { 78 | continue 79 | } 80 | addr := config.Address 81 | if ip, port, err := net.SplitHostPort(addr); err == nil { 82 | switch ip { 83 | case "", "0.0.0.0", "::": 84 | if ip1, err := helpers.LocalPerferIPv4(); err == nil { 85 | ip = ip1.String() 86 | } else if ips, err := helpers.LocalIPv4s(); err == nil && len(ips) > 0 { 87 | ip = ips[0].String() 88 | } 89 | } 90 | addr = net.JoinHostPort(ip, port) 91 | } 92 | fmt.Fprintf(os.Stderr, ` 93 | GoProxy Profile : %s 94 | Listen Address : %s 95 | Enabled Filters : %v`, 96 | profile, 97 | addr, 98 | fmt.Sprintf("%s|%s|%s", strings.Join(config.RequestFilters, ","), strings.Join(config.RoundTripFilters, ","), strings.Join(config.ResponseFilters, ","))) 99 | for _, fn := range config.RoundTripFilters { 100 | f, err := filters.GetFilter(fn) 101 | if err != nil { 102 | glog.Fatalf("filters.GetFilter(%#v) error: %+v", fn, err) 103 | } 104 | 105 | switch fn { 106 | case "autoproxy": 107 | fmt.Fprintf(os.Stderr, ` 108 | Pac Server : http://%s/proxy.pac`, addr) 109 | case "gae": 110 | config := f.(*gae.Filter).Config 111 | if len(config.AppIDs) > 0 { 112 | fmt.Fprintf(os.Stderr, ` 113 | GAE AppIDs : %s`, strings.Join(config.AppIDs, "|")) 114 | } 115 | if len(config.CustomDomains) > 0 { 116 | fmt.Fprintf(os.Stderr, ` 117 | GAE Domains : %s`, strings.Join(config.CustomDomains, "|")) 118 | } 119 | switch { 120 | case config.EnableQuic: 121 | fmt.Fprintf(os.Stderr, ` 122 | GAE Mode : Quic`) 123 | default: 124 | fmt.Fprintf(os.Stderr, ` 125 | GAE Mode : TLS`) 126 | } 127 | case "php": 128 | urls := make([]string, 0) 129 | for _, s := range f.(*php.Filter).Config.Servers { 130 | urls = append(urls, s.URL) 131 | } 132 | fmt.Fprintf(os.Stderr, ` 133 | PHP Servers : %s`, strings.Join(urls, "|")) 134 | } 135 | } 136 | go httpproxy.ServeProfile(config, "goproxy "+version) 137 | } 138 | fmt.Fprintf(os.Stderr, "\n------------------------------------------------------\n") 139 | 140 | if ws, ok := os.LookupEnv("GOPROXY_WAIT_SECONDS"); ok { 141 | if ws1, err := strconv.Atoi(ws); err == nil { 142 | time.Sleep(time.Duration(ws1) * time.Second) 143 | return 144 | } 145 | } 146 | 147 | select {} 148 | } 149 | -------------------------------------------------------------------------------- /make.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | REVSION=$(git rev-list --count HEAD) 6 | LDFLAGS="-s -w -X main.version=r${REVSION}" 7 | 8 | GOOS=${GOOS:-$(go env GOOS)} 9 | GOARCH=${GOARCH:-$(go env GOARCH)} 10 | CGO_ENABLED=${CGO_ENABLED:-$(go env CGO_ENABLED)} 11 | 12 | REPO=$(git rev-parse --show-toplevel) 13 | PACKAGE=$(basename ${REPO}) 14 | if [ "${CGO_ENABLED}" = "0" ]; then 15 | BUILDROOT=${REPO}/build/${GOOS}_${GOARCH} 16 | else 17 | BUILDROOT=${REPO}/build/${GOOS}_${GOARCH}_cgo 18 | fi 19 | STAGEDIR=${BUILDROOT}/stage 20 | OBJECTDIR=${BUILDROOT}/obj 21 | DISTDIR=${BUILDROOT}/dist 22 | 23 | if [ "${GOOS}" == "windows" ]; then 24 | GOPROXY_EXE="${PACKAGE}.exe" 25 | GOPROXY_STAGEDIR="${STAGEDIR}" 26 | GOPROXY_DISTCMD="7za a -y -mx=9 -m0=lzma -mfb=128 -md=64m -ms=on" 27 | GOPROXY_DISTEXT=".7z" 28 | elif [ "${GOOS}" == "darwin" ]; then 29 | GOPROXY_EXE="${PACKAGE}" 30 | GOPROXY_STAGEDIR="${STAGEDIR}" 31 | GOPROXY_DISTCMD="env BZIP=-9 tar cvjpf" 32 | GOPROXY_DISTEXT=".tar.bz2" 33 | elif [ "${GOARCH:0:3}" == "arm" ]; then 34 | GOPROXY_EXE="${PACKAGE}" 35 | GOPROXY_STAGEDIR="${STAGEDIR}" 36 | GOPROXY_DISTCMD="env BZIP=-9 tar cvjpf" 37 | GOPROXY_DISTEXT=".tar.bz2" 38 | elif [ "${GOARCH:0:4}" == "mips" ]; then 39 | GOPROXY_EXE="${PACKAGE}" 40 | GOPROXY_STAGEDIR="${STAGEDIR}" 41 | GOPROXY_DISTCMD="env GZIP=-9 tar cvzpf" 42 | GOPROXY_DISTEXT=".tar.gz" 43 | else 44 | GOPROXY_EXE="${PACKAGE}" 45 | GOPROXY_STAGEDIR="${STAGEDIR}/${PACKAGE}" 46 | GOPROXY_DISTCMD="env XZ_OPT=-9 tar cvJpf" 47 | GOPROXY_DISTEXT=".tar.xz" 48 | fi 49 | 50 | GOPROXY_DIST=${DISTDIR}/${PACKAGE}_${GOOS}_${GOARCH}-r${REVSION}${GOPROXY_DISTEXT} 51 | if [ "${CGO_ENABLED}" = "1" ]; then 52 | GOPROXY_DIST=${DISTDIR}/${PACKAGE}_${GOOS}_${GOARCH}_cgo-r${REVSION}${GOPROXY_DISTEXT} 53 | fi 54 | 55 | GOPROXY_GUI_EXE=${REPO}/assets/taskbar/${GOARCH}/goproxy-gui.exe 56 | if [ ! -f "${GOPROXY_GUI_EXE}" ]; then 57 | GOPROXY_GUI_EXE=${REPO}/assets/packaging/goproxy-gui.exe 58 | fi 59 | 60 | OBJECTS=${OBJECTDIR}/${GOPROXY_EXE} 61 | 62 | SOURCES="${REPO}/README.md \ 63 | ${REPO}/assets/packaging/gae.user.json.example \ 64 | ${REPO}/httpproxy/filters/auth/auth.json \ 65 | ${REPO}/httpproxy/filters/autoproxy/17monipdb.dat \ 66 | ${REPO}/httpproxy/filters/autoproxy/autoproxy.json \ 67 | ${REPO}/httpproxy/filters/autoproxy/gfwlist.txt \ 68 | ${REPO}/httpproxy/filters/autoproxy/ip.html \ 69 | ${REPO}/httpproxy/filters/autorange/autorange.json \ 70 | ${REPO}/httpproxy/filters/direct/direct.json \ 71 | ${REPO}/httpproxy/filters/gae/gae.json \ 72 | ${REPO}/httpproxy/filters/php/php.json \ 73 | ${REPO}/httpproxy/filters/rewrite/rewrite.json \ 74 | ${REPO}/httpproxy/filters/stripssl/stripssl.json \ 75 | ${REPO}/httpproxy/httpproxy.json" 76 | 77 | if [ "${GOOS}" = "windows" ]; then 78 | SOURCES="${SOURCES} \ 79 | ${GOPROXY_GUI_EXE} \ 80 | ${REPO}/assets/packaging/addto-startup.vbs \ 81 | ${REPO}/assets/packaging/get-latest-goproxy.cmd" 82 | elif [ "${GOOS}_${GOARCH}_${CGO_ENABLED}" = "linux_arm_0" ]; then 83 | SOURCES="${SOURCES} \ 84 | ${REPO}/assets/packaging/goproxy.sh \ 85 | ${REPO}/assets/packaging/get-latest-goproxy.sh" 86 | GOARM=${GORAM:-5} 87 | elif [ "${GOOS}_${GOARCH}_${CGO_ENABLED}" = "linux_arm_1" ]; then 88 | SOURCES="${SOURCES} \ 89 | ${REPO}/assets/packaging/goproxy.sh \ 90 | ${REPO}/assets/packaging/get-latest-goproxy.sh" 91 | CC=${ARM_CC:-arm-linux-gnueabihf-gcc} 92 | GOARM=${GORAM:-5} 93 | elif [ "${GOOS}" = "darwin" ]; then 94 | SOURCES="${SOURCES} \ 95 | ${REPO}/assets/packaging/goproxy-macos.command \ 96 | ${REPO}/assets/packaging/get-latest-goproxy.sh" 97 | else 98 | SOURCES="${SOURCES} \ 99 | ${REPO}/assets/packaging/get-latest-goproxy.sh \ 100 | ${REPO}/assets/packaging/goproxy-gtk.desktop \ 101 | ${REPO}/assets/packaging/goproxy-gtk.png \ 102 | ${REPO}/assets/packaging/goproxy-gtk.py \ 103 | ${REPO}/assets/packaging/goproxy.sh" 104 | fi 105 | 106 | build () { 107 | mkdir -p ${OBJECTDIR} 108 | env GOOS=${GOOS} \ 109 | GOARCH=${GOARCH} \ 110 | GOARM=${GOARM} \ 111 | CGO_ENABLED=${CGO_ENABLED} \ 112 | CC=${CC} \ 113 | go build -v -ldflags="${LDFLAGS}" -o ${OBJECTDIR}/${GOPROXY_EXE} . 114 | } 115 | 116 | dist () { 117 | mkdir -p ${DISTDIR} ${STAGEDIR} ${GOPROXY_STAGEDIR} 118 | cp ${OBJECTS} ${SOURCES} ${GOPROXY_STAGEDIR} 119 | 120 | pushd ${STAGEDIR} 121 | ${GOPROXY_DISTCMD} ${GOPROXY_DIST} * 122 | popd 123 | } 124 | 125 | check () { 126 | GOPROXY_WAIT_SECONDS=0 ${GOPROXY_STAGEDIR}/${GOPROXY_EXE} 127 | } 128 | 129 | clean () { 130 | rm -rf ${BUILDROOT} 131 | } 132 | 133 | case $1 in 134 | build) 135 | build 136 | ;; 137 | dist) 138 | dist 139 | ;; 140 | check) 141 | check 142 | ;; 143 | clean) 144 | clean 145 | ;; 146 | *) 147 | build 148 | dist 149 | ;; 150 | esac 151 | --------------------------------------------------------------------------------