├── .gitignore ├── LICENSE ├── README.md ├── go.mod ├── main.go └── main_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .DS_Store 3 | output/ 4 | 5 | # Binaries for programs and plugins 6 | *.exe 7 | *.exe~ 8 | *.dll 9 | *.so 10 | *.dylib 11 | 12 | # Test binary, built with `go test -c` 13 | *.test 14 | 15 | # Output of the go coverage tool, specifically when used with LiteIDE 16 | *.out 17 | 18 | # Dependency directories (remove the comment below to include it) 19 | # vendor/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Quan guanyu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go System Proxy Windows系统代理配置 2 | 3 | [![Documentation](https://godoc.org/github.com/Trisia/gosysproxy?status.svg)](https://pkg.go.dev/github.com/Trisia/gosysproxy) 4 | ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/Trisia/gosysproxy) 5 | 6 | 通过系统调用的方式实现Windows系统的代理配置、状态查询。 7 | 8 | ## Quick Start 9 | 10 | 获取 11 | ``` 12 | go get -u github.com/Trisia/gosysproxy 13 | ``` 14 | 15 | API 16 | ```go 17 | // SetPAC 设置PAC代理模式 18 | // scriptLoc: 脚本地址,如: "http://127.0.0.1:7777/pac" 19 | func SetPAC(scriptLoc string) 20 | 21 | // SetGlobalProxy 设置全局代理 22 | // proxyServer: 代理服务器host:port,例如: "127.0.0.1:7890" 23 | // bypass: 忽略代理列表,这些配置项开头的地址不进行代理 24 | func SetGlobalProxy(proxyServer string, bypasses ...string) error 25 | 26 | // Off 关闭代理 27 | func Off() error 28 | 29 | // Flush 更新系统配置使生效 30 | func Flush() 31 | 32 | // Status 获取当前系统代理配置 33 | func Status() (*ProxyStatus, error) 34 | ``` 35 | 36 | 详见: [《Go System Proxy 接口文档》](https://pkg.go.dev/github.com/Trisia/gosysproxy) 37 | 38 | ## Demo 39 | 40 | ```go 41 | package main 42 | 43 | import ( 44 | "github.com/Trisia/gosysproxy" 45 | "time" 46 | ) 47 | 48 | func main() { 49 | // 设置全局代理 50 | err := gosysproxy.SetGlobalProxy("127.0.0.1:7890") 51 | if err{ 52 | panic(err) 53 | } 54 | 55 | time.Sleep(time.Second * 60) 56 | 57 | err := gosysproxy.Off() 58 | if err{ 59 | panic(err) 60 | } 61 | } 62 | ``` 63 | 64 | 完整示例详见: [main_test.go](main_test.go) 65 | 66 | 67 | ## 致谢 68 | 69 | - [Noisyfox/sysproxy](https://github.com/Noisyfox/sysproxy) 70 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Trisia/gosysproxy 2 | 3 | go 1.14 4 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | // windows系统代理配置 5 | package gosysproxy 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | "math/big" 11 | "strings" 12 | "syscall" 13 | "unsafe" 14 | ) 15 | 16 | var ( 17 | wininet, _ = syscall.LoadLibrary("Wininet.dll") 18 | internetSetOption, _ = syscall.GetProcAddress(wininet, "InternetSetOptionW") 19 | // https://learn.microsoft.com/zh-cn/windows/win32/api/wininet/nf-wininet-internetqueryoptionw 20 | internetQueryOption, _ = syscall.GetProcAddress(wininet, "InternetQueryOptionA") 21 | ) 22 | 23 | const ( 24 | _INTERNET_OPTION_PER_CONNECTION_OPTION = 75 25 | _INTERNET_OPTION_PROXY_SETTINGS_CHANGED = 95 26 | _INTERNET_OPTION_REFRESH = 37 27 | _INTERNET_OPTION_PROXY = 38 28 | ) 29 | 30 | const ( 31 | _PROXY_TYPE_DIRECT = 0x00000001 // direct to net 32 | _PROXY_TYPE_PROXY = 0x00000002 // via named proxy 33 | _PROXY_TYPE_AUTO_PROXY_URL = 0x00000004 // autoproxy URL 34 | _PROXY_TYPE_AUTO_DETECT = 0x00000008 // use autoproxy detection 35 | ) 36 | 37 | const ( 38 | _INTERNET_PER_CONN_FLAGS = 1 39 | _INTERNET_PER_CONN_PROXY_SERVER = 2 40 | _INTERNET_PER_CONN_PROXY_BYPASS = 3 41 | _INTERNET_PER_CONN_AUTOCONFIG_URL = 4 42 | _INTERNET_PER_CONN_AUTODISCOVERY_FLAGS = 5 43 | _INTERNET_PER_CONN_AUTOCONFIG_SECONDARY_URL = 6 44 | _INTERNET_PER_CONN_AUTOCONFIG_RELOAD_DELAY_MINS = 7 45 | _INTERNET_PER_CONN_AUTOCONFIG_LAST_DETECT_TIME = 8 46 | _INTERNET_PER_CONN_AUTOCONFIG_LAST_DETECT_URL = 9 47 | _INTERNET_PER_CONN_FLAGS_UI = 10 48 | ) 49 | 50 | const ( 51 | INTERNET_OPEN_TYPE_PRECONFIG = 0 // use registry configuration 52 | INTERNET_OPEN_TYPE_DIRECT = 1 // 禁用代理 direct to net 53 | INTERNET_OPEN_TYPE_PROXY = 3 // 启用代理 via named proxy 54 | ) 55 | 56 | type internetPerConnOptionList struct { 57 | dwSize uint32 58 | pszConnection *uint16 59 | dwOptionCount uint32 60 | dwOptionError uint32 61 | pOptions uintptr 62 | } 63 | 64 | type internetPreConnOption struct { 65 | dwOption uint32 66 | value uint64 67 | } 68 | 69 | // internetProxyInfo https://learn.microsoft.com/zh-cn/windows/win32/api/wininet/ns-wininet-internet_proxy_info 70 | type internetProxyInfo struct { 71 | dwAccessType uint32 72 | lpszProxy *uint16 73 | lpszProxyBypass *uint16 74 | } 75 | 76 | type ProxyStatus struct { 77 | // 代理类型 78 | // - 0: INTERNET_OPEN_TYPE_PRECONFIG: use registry configuration 79 | // - 1: INTERNET_OPEN_TYPE_DIRECT: 不代理 direct to net 80 | // - 3: INTERNET_OPEN_TYPE_PROXY: 使用代理服务器 via named proxy 81 | Type uint32 82 | Proxy string // 代理IP地址与端口,IP:Port,例如:"127.0.0.1:7890" 83 | // 请勿对以下列条目开头的地址使用代理服务器 84 | // 注意: 85 | // - 这里的地址是ASCII编码 86 | // - "" 表示 本地(Intranet)地址,如果包含 "" 则 DisableProxyIntranet 为 true 87 | // 88 | // 例如:["localhost","127.*"], 89 | Bypass []string 90 | DisableProxyIntranet bool // 请勿将代理服务器用于本地(Intranet)地址 91 | } 92 | 93 | // stringPtrAddr 获取C字符串(UTF16)的数组第一个位置的地址 94 | func stringPtrAddr(str string) (uint64, error) { 95 | scriptLocPtr, err := syscall.UTF16PtrFromString(str) 96 | if err != nil { 97 | return 0, err 98 | } 99 | n := new(big.Int) 100 | n.SetString(fmt.Sprintf("%x\n", scriptLocPtr), 16) 101 | return n.Uint64(), nil 102 | } 103 | 104 | // newParam 创建参数序列容器 105 | // n: 参数数量 106 | func newParam(n int) internetPerConnOptionList { 107 | return internetPerConnOptionList{ 108 | dwSize: 4, 109 | pszConnection: nil, 110 | dwOptionCount: uint32(n), 111 | dwOptionError: 0, 112 | pOptions: 0, 113 | } 114 | } 115 | 116 | // SetPAC 设置PAC代理模式 117 | // scriptLoc: 脚本地址,如: "http://127.0.0.1:7777/pac" 118 | func SetPAC(scriptLoc string) error { 119 | if scriptLoc == "" { 120 | return errors.New("PAC脚本地址(scriptLoc)配置为空") 121 | } 122 | 123 | scriptLocAddr, err := stringPtrAddr(scriptLoc) 124 | if err != nil { 125 | return err 126 | } 127 | 128 | param := newParam(2) 129 | // 利用Golang数组地址空间连续模拟 malloc 130 | options := []internetPreConnOption{ 131 | {dwOption: _INTERNET_PER_CONN_FLAGS, value: _PROXY_TYPE_AUTO_PROXY_URL | _PROXY_TYPE_DIRECT}, 132 | {dwOption: _INTERNET_PER_CONN_AUTOCONFIG_URL, value: scriptLocAddr}, 133 | } 134 | param.pOptions = uintptr(unsafe.Pointer(&options[0])) 135 | ret, _, infoPtr := syscall.Syscall6(internetSetOption, 136 | 4, 137 | 0, 138 | _INTERNET_OPTION_PER_CONNECTION_OPTION, 139 | uintptr(unsafe.Pointer(¶m)), 140 | unsafe.Sizeof(param), 141 | 0, 0) 142 | // fmt.Printf(">> Ret [%d] Setting options: %s\n", ret, info) 143 | if ret != 1 { 144 | return errors.New(fmt.Sprintf("%s", infoPtr)) 145 | } 146 | 147 | return Flush() 148 | } 149 | 150 | // SetGlobalProxy 设置全局代理 151 | // - proxyServer: 代理服务器 host:port,例如: "127.0.0.1:7890" 152 | // - bypass: 忽略代理列表,这些配置项开头的地址不进行代理,若包含 "" 则 ”请勿将代理服务器用于本地(Intranet)地址“ 将勾选。 153 | func SetGlobalProxy(proxyServer string, bypasses ...string) error { 154 | if proxyServer == "" { 155 | return errors.New("代理服务器(proxyServer)配置为空") 156 | } 157 | 158 | proxyServerPtrAddr, err := stringPtrAddr(proxyServer) 159 | if err != nil { 160 | return err 161 | } 162 | 163 | var bypassBuilder strings.Builder 164 | // 地址过滤配置 165 | if bypasses != nil { 166 | for _, item := range bypasses { 167 | bypassBuilder.WriteString(item) 168 | bypassBuilder.WriteByte(';') 169 | } 170 | } else { 171 | bypassBuilder.WriteString("") 172 | } 173 | bypassAddr, err := stringPtrAddr(bypassBuilder.String()) 174 | if err != nil { 175 | return err 176 | } 177 | 178 | param := newParam(3) 179 | options := []internetPreConnOption{ 180 | {dwOption: _INTERNET_PER_CONN_FLAGS, value: _PROXY_TYPE_PROXY | _PROXY_TYPE_DIRECT}, 181 | {dwOption: _INTERNET_PER_CONN_PROXY_SERVER, value: proxyServerPtrAddr}, 182 | {dwOption: _INTERNET_PER_CONN_PROXY_BYPASS, value: bypassAddr}, 183 | } 184 | param.pOptions = uintptr(unsafe.Pointer(&options[0])) 185 | ret, _, infoPtr := syscall.Syscall6(internetSetOption, 186 | 4, 187 | 0, 188 | _INTERNET_OPTION_PER_CONNECTION_OPTION, 189 | uintptr(unsafe.Pointer(¶m)), 190 | unsafe.Sizeof(param), 191 | 0, 0) 192 | // fmt.Printf(">> Ret [%d] Setting options: %s\n", ret, infoPtr) 193 | if ret != 1 { 194 | return errors.New(fmt.Sprintf("%s", infoPtr)) 195 | } 196 | 197 | return Flush() 198 | } 199 | 200 | // Off 关闭代理 201 | func Off() error { 202 | param := newParam(1) 203 | option := internetPreConnOption{ 204 | dwOption: _INTERNET_PER_CONN_FLAGS, 205 | //value: _PROXY_TYPE_AUTO_DETECT | _PROXY_TYPE_DIRECT} 206 | value: _PROXY_TYPE_DIRECT} 207 | param.pOptions = uintptr(unsafe.Pointer(&option)) 208 | ret, _, infoPtr := syscall.Syscall6(internetSetOption, 209 | 4, 210 | 0, 211 | _INTERNET_OPTION_PER_CONNECTION_OPTION, 212 | uintptr(unsafe.Pointer(¶m)), 213 | unsafe.Sizeof(param), 214 | 0, 0) 215 | // fmt.Printf(">> Ret [%d] Setting options: %s\n", ret, info) 216 | if ret != 1 { 217 | return errors.New(fmt.Sprintf("%s", infoPtr)) 218 | } 219 | return Flush() 220 | } 221 | 222 | // Flush 更新系统配置使生效 223 | func Flush() error { 224 | ret, _, infoPtr := syscall.Syscall6(internetSetOption, 225 | 4, 226 | 0, 227 | _INTERNET_OPTION_PROXY_SETTINGS_CHANGED, 228 | 0, 0, 229 | 0, 0) 230 | // fmt.Println(">> Propagating changes:", fmt.Sprintf("%s", errno)) 231 | if ret != 1 { 232 | return errors.New(fmt.Sprintf("%s", infoPtr)) 233 | } 234 | 235 | ret, _, infoPtr = syscall.Syscall6(internetSetOption, 236 | 4, 237 | 0, 238 | _INTERNET_OPTION_REFRESH, 239 | 0, 0, 240 | 0, 0) 241 | // fmt.Println(">> Refreshing:", errno) 242 | if ret != 1 { 243 | return errors.New(fmt.Sprintf("%s", infoPtr)) 244 | } 245 | return nil 246 | } 247 | 248 | // Status 获取当前系统代理配置 249 | func Status() (*ProxyStatus, error) { 250 | var bufferLength uint32 = 1024 * 10 251 | buffer := make([]byte, bufferLength) 252 | ret, _, infoPtr := syscall.Syscall6(internetQueryOption, 253 | 4, 254 | 0, 255 | _INTERNET_OPTION_PROXY, 256 | uintptr(unsafe.Pointer(&buffer[0])), uintptr(unsafe.Pointer(&bufferLength)), 257 | 0, 0) 258 | if ret != 1 { 259 | return nil, errors.New(fmt.Sprintf("%s", infoPtr)) 260 | } 261 | //fmt.Println(hex.Dump(buffer[:bufferLength])) 262 | proxyInfo := (*internetProxyInfo)(unsafe.Pointer(&buffer[0])) 263 | res := &ProxyStatus{ 264 | Type: proxyInfo.dwAccessType, 265 | Proxy: asciiPtrToString(proxyInfo.lpszProxy), 266 | } 267 | bypassArr := asciiPtrToString(proxyInfo.lpszProxyBypass) 268 | res.Bypass = strings.SplitN(bypassArr, " ", -1) 269 | for _, bypass := range res.Bypass { 270 | if bypass == "" { 271 | res.DisableProxyIntranet = true 272 | break 273 | } 274 | } 275 | return res, nil 276 | } 277 | 278 | // ASCIIPtrToString 将UTF16指针转换为ASCII字符串 279 | func asciiPtrToString(p *uint16) string { 280 | if p == nil { 281 | return "" 282 | } 283 | res := []byte{} 284 | end := unsafe.Pointer(p) 285 | for { 286 | if *(*uint8)(end) == 0 { 287 | break 288 | } 289 | res = append(res, *(*uint8)(end)) 290 | end = unsafe.Pointer(uintptr(end) + 1) 291 | } 292 | return string(res) 293 | } 294 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package gosysproxy 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | ) 10 | 11 | func TestFlush(t *testing.T) { 12 | err := Flush() 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | } 17 | 18 | func TestOff(t *testing.T) { 19 | err := Off() 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | } 24 | 25 | func TestSetPAC(t *testing.T) { 26 | err := SetPAC("http://127.0.0.1:7777/pac") 27 | if err != nil { 28 | t.Fatal(err) 29 | } 30 | } 31 | 32 | func TestSetGlobalProxy(t *testing.T) { 33 | err := SetGlobalProxy("127.0.0.1:7890") 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | 38 | err = Off() 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | 43 | err = SetGlobalProxy("127.0.0.1:7890", "foo", "bar") 44 | if err != nil { 45 | t.Fatal(err) 46 | } 47 | 48 | err = Off() 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | } 53 | 54 | func TestStatus(t *testing.T) { 55 | err := SetGlobalProxy("127.0.0.1:7890") 56 | if err != nil { 57 | t.Fatal(err) 58 | } 59 | 60 | err = Off() 61 | if err != nil { 62 | t.Fatal(err) 63 | } 64 | 65 | err = SetGlobalProxy("127.0.0.1:7890", "foo", "bar", "") 66 | if err != nil { 67 | t.Fatal(err) 68 | } 69 | res, err := Status() 70 | if err != nil { 71 | t.Fatal(err) 72 | } 73 | switch res.Type { 74 | case INTERNET_OPEN_TYPE_PRECONFIG: 75 | fmt.Println(">> 代理方式:", "use registry configuration") 76 | case INTERNET_OPEN_TYPE_DIRECT: 77 | fmt.Println(">> 代理方式:", "不代理 direct to net") 78 | case INTERNET_OPEN_TYPE_PROXY: 79 | fmt.Println(">> 代理方式:", "使用代理服务器 via named proxy") 80 | } 81 | fmt.Println(">> 代理地址:", res.Proxy) 82 | fmt.Println(">> 请勿对以下列条目开头的地址使用代理服务器:", res.Bypass) 83 | fmt.Println(">> 请勿将代理服务器用于本地(Intranet)地址:", res.DisableProxyIntranet) 84 | 85 | err = Off() 86 | if err != nil { 87 | t.Fatal(err) 88 | } 89 | 90 | } 91 | --------------------------------------------------------------------------------