// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package tshttpproxy import ( "log" "net/http" "net/url" "strings" "syscall" "unsafe" "golang.org/x/sys/windows" ) var ( winHTTP = windows.NewLazySystemDLL("winhttp.dll") httpOpenProc = winHTTP.NewProc("WinHttpOpen") closeHandleProc = winHTTP.NewProc("WinHttpCloseHandle") getProxyForUrlProc = winHTTP.NewProc("WinHttpGetProxyForUrl") ) func init() { sysProxyFromEnv = proxyFromWinHTTP } func proxyFromWinHTTP(req *http.Request) (*url.URL, error) { if req.URL == nil { return nil, nil } urlStr := req.URL.String() whi, err := winHTTPOpen() if err != nil { // Log but otherwise ignore the error. log.Printf("winhttp: Open: %v", err) return nil, nil } defer whi.Close() v, err := whi.GetProxyForURL(urlStr) if err != nil { // See https://docs.microsoft.com/en-us/windows/win32/winhttp/error-messages const ERROR_WINHTTP_AUTODETECTION_FAILED = 12180 if err == syscall.Errno(ERROR_WINHTTP_AUTODETECTION_FAILED) { return nil, nil } log.Printf("winhttp: GetProxyForURL(%q): %v (%T, %#v)", urlStr, err, err, err) return nil, nil } if v != "" { if !strings.HasPrefix(v, "https://") { v = "http://" + v } if u, err := url.Parse(v); err == nil { return u, nil } } return nil, nil } var userAgent = windows.StringToUTF16Ptr("Tailscale") const ( winHTTP_ACCESS_TYPE_AUTOMATIC_PROXY = 4 winHTTP_AUTOPROXY_ALLOW_AUTOCONFIG = 0x00000100 winHTTP_AUTOPROXY_AUTO_DETECT = 1 winHTTP_AUTO_DETECT_TYPE_DHCP = 0x00000001 winHTTP_AUTO_DETECT_TYPE_DNS_A = 0x00000002 ) func winHTTPOpen() (winHTTPInternet, error) { if err := httpOpenProc.Find(); err != nil { return 0, err } r, _, err := httpOpenProc.Call( uintptr(unsafe.Pointer(userAgent)), winHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, 0, /* WINHTTP_NO_PROXY_NAME */ 0, /* WINHTTP_NO_PROXY_BYPASS */ 0) if r == 0 { return 0, err } return winHTTPInternet(r), nil } type winHTTPInternet windows.Handle func (hi winHTTPInternet) Close() error { if err := closeHandleProc.Find(); err != nil { return err } r, _, err := closeHandleProc.Call(uintptr(hi)) if r == 1 { return nil } return err } // WINHTTP_AUTOPROXY_OPTIONS // https://docs.microsoft.com/en-us/windows/win32/api/winhttp/ns-winhttp-winhttp_autoproxy_options type autoProxyOptions struct { DwFlags uint32 DwAutoDetectFlags uint32 AutoConfigUrl *uint16 _ uintptr _ uint32 FAutoLogonIfChallenged bool } // WINHTTP_PROXY_INFO // https://docs.microsoft.com/en-us/windows/win32/api/winhttp/ns-winhttp-winhttp_proxy_info type winHTTPProxyInfo struct { AccessType uint16 Proxy *uint16 ProxyBypass *uint16 } var proxyForURLOpts = &autoProxyOptions{ DwFlags: winHTTP_AUTOPROXY_ALLOW_AUTOCONFIG | winHTTP_AUTOPROXY_AUTO_DETECT, DwAutoDetectFlags: winHTTP_AUTO_DETECT_TYPE_DHCP, // | winHTTP_AUTO_DETECT_TYPE_DNS_A, } func (hi winHTTPInternet) GetProxyForURL(urlStr string) (string, error) { if err := getProxyForUrlProc.Find(); err != nil { return "", err } var out winHTTPProxyInfo r, _, err := getProxyForUrlProc.Call( uintptr(hi), uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(urlStr))), uintptr(unsafe.Pointer(proxyForURLOpts)), uintptr(unsafe.Pointer(&out))) if r == 1 { return windows.UTF16PtrToString(out.Proxy), nil } return "", err }