mirror of
https://github.com/tailscale/tailscale.git
synced 2025-01-08 09:07:44 +00:00
net/tshttpproxy: new package, support WPAD/PAC proxies on Windows
Updates tailscale/corp#553 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
1835bb6f85
commit
c5eb57f4d6
@ -36,6 +36,7 @@
|
|||||||
"tailscale.com/log/logheap"
|
"tailscale.com/log/logheap"
|
||||||
"tailscale.com/net/netns"
|
"tailscale.com/net/netns"
|
||||||
"tailscale.com/net/tlsdial"
|
"tailscale.com/net/tlsdial"
|
||||||
|
"tailscale.com/net/tshttpproxy"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
"tailscale.com/types/opt"
|
"tailscale.com/types/opt"
|
||||||
@ -147,6 +148,7 @@ func NewDirect(opts Options) (*Direct, error) {
|
|||||||
if httpc == nil {
|
if httpc == nil {
|
||||||
dialer := netns.NewDialer()
|
dialer := netns.NewDialer()
|
||||||
tr := http.DefaultTransport.(*http.Transport).Clone()
|
tr := http.DefaultTransport.(*http.Transport).Clone()
|
||||||
|
tr.Proxy = tshttpproxy.ProxyFromEnvironment
|
||||||
tr.DialContext = dialer.DialContext
|
tr.DialContext = dialer.DialContext
|
||||||
tr.ForceAttemptHTTP2 = true
|
tr.ForceAttemptHTTP2 = true
|
||||||
tr.TLSClientConfig = tlsdial.Config(serverURL.Host, tr.TLSClientConfig)
|
tr.TLSClientConfig = tlsdial.Config(serverURL.Host, tr.TLSClientConfig)
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
"tailscale.com/net/dnscache"
|
"tailscale.com/net/dnscache"
|
||||||
"tailscale.com/net/netns"
|
"tailscale.com/net/netns"
|
||||||
"tailscale.com/net/tlsdial"
|
"tailscale.com/net/tlsdial"
|
||||||
|
"tailscale.com/net/tshttpproxy"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/types/key"
|
"tailscale.com/types/key"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
@ -420,6 +421,19 @@ func shouldDialProto(s string, pred func(netaddr.IP) bool) bool {
|
|||||||
// TODO(bradfitz): longer if no options remain perhaps? ... Or longer
|
// TODO(bradfitz): longer if no options remain perhaps? ... Or longer
|
||||||
// overall but have dialRegion start overlapping races?
|
// overall but have dialRegion start overlapping races?
|
||||||
func (c *Client) dialNode(ctx context.Context, n *tailcfg.DERPNode) (net.Conn, error) {
|
func (c *Client) dialNode(ctx context.Context, n *tailcfg.DERPNode) (net.Conn, error) {
|
||||||
|
// First see if we need to use an HTTP proxy.
|
||||||
|
proxyReq := &http.Request{
|
||||||
|
Method: "GET", // doesn't really matter
|
||||||
|
URL: &url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: c.tlsServerName(n),
|
||||||
|
Path: "/", // unused
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if proxyURL, err := tshttpproxy.ProxyFromEnvironment(proxyReq); err == nil && proxyURL != nil {
|
||||||
|
return c.dialNodeUsingProxy(ctx, n, proxyURL)
|
||||||
|
}
|
||||||
|
|
||||||
type res struct {
|
type res struct {
|
||||||
c net.Conn
|
c net.Conn
|
||||||
err error
|
err error
|
||||||
@ -480,6 +494,69 @@ type res struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func firstStr(a, b string) string {
|
||||||
|
if a != "" {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// dialNodeUsingProxy connects to n using a CONNECT to the HTTP(s) proxy in proxyURL.
|
||||||
|
func (c *Client) dialNodeUsingProxy(ctx context.Context, n *tailcfg.DERPNode, proxyURL *url.URL) (proxyConn net.Conn, err error) {
|
||||||
|
pu := proxyURL
|
||||||
|
if pu.Scheme == "https" {
|
||||||
|
var d tls.Dialer
|
||||||
|
proxyConn, err = d.DialContext(ctx, "tcp", net.JoinHostPort(pu.Hostname(), firstStr(pu.Port(), "443")))
|
||||||
|
} else {
|
||||||
|
var d net.Dialer
|
||||||
|
proxyConn, err = d.DialContext(ctx, "tcp", net.JoinHostPort(pu.Hostname(), firstStr(pu.Port(), "80")))
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err != nil && proxyConn != nil {
|
||||||
|
// In a goroutine in case it's a *tls.Conn (that can block on Close)
|
||||||
|
// TODO(bradfitz): track the underlying tcp.Conn and just close that instead.
|
||||||
|
go proxyConn.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
|
defer close(done)
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
return
|
||||||
|
case <-ctx.Done():
|
||||||
|
proxyConn.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
target := net.JoinHostPort(n.HostName, "443")
|
||||||
|
if _, err := fmt.Fprintf(proxyConn, "CONNECT %s HTTP/1.1\r\nHost: %s\r\n\r\n", target, pu.Hostname()); err != nil {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return nil, ctx.Err()
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
br := bufio.NewReader(proxyConn)
|
||||||
|
res, err := http.ReadResponse(br, nil)
|
||||||
|
if err != nil {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return nil, ctx.Err()
|
||||||
|
}
|
||||||
|
c.logf("derphttp: CONNECT dial to %s: %v", target, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.logf("derphttp: CONNECT dial to %s: %v", target, res.Status)
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
return nil, fmt.Errorf("invalid response status from HTTP proxy %s on CONNECT to %s: %v", pu, target, res.Status)
|
||||||
|
}
|
||||||
|
return proxyConn, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) Send(dstKey key.Public, b []byte) error {
|
func (c *Client) Send(dstKey key.Public, b []byte) error {
|
||||||
client, _, err := c.connect(context.TODO(), "derphttp.Client.Send")
|
client, _, err := c.connect(context.TODO(), "derphttp.Client.Send")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
2
go.mod
2
go.mod
@ -29,7 +29,7 @@ require (
|
|||||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5
|
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
|
||||||
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3
|
golang.org/x/sys v0.0.0-20200812155832-6a926be9bd1d
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0
|
||||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425
|
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425
|
||||||
honnef.co/go/tools v0.0.1-2020.1.4
|
honnef.co/go/tools v0.0.1-2020.1.4
|
||||||
|
2
go.sum
2
go.sum
@ -141,6 +141,8 @@ golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3 h1:5B6i6EAiSYyejWfvc5Rc9BbI3rzIsrrXfAQBWnYfn+w=
|
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3 h1:5B6i6EAiSYyejWfvc5Rc9BbI3rzIsrrXfAQBWnYfn+w=
|
||||||
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200812155832-6a926be9bd1d h1:QQrM/CCYEzTs91GZylDCQjGHudbPTxF/1fvXdVh5lMo=
|
||||||
|
golang.org/x/sys v0.0.0-20200812155832-6a926be9bd1d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
"tailscale.com/logtail/filch"
|
"tailscale.com/logtail/filch"
|
||||||
"tailscale.com/net/netns"
|
"tailscale.com/net/netns"
|
||||||
"tailscale.com/net/tlsdial"
|
"tailscale.com/net/tlsdial"
|
||||||
|
"tailscale.com/net/tshttpproxy"
|
||||||
"tailscale.com/paths"
|
"tailscale.com/paths"
|
||||||
"tailscale.com/smallzstd"
|
"tailscale.com/smallzstd"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
@ -431,6 +432,8 @@ func newLogtailTransport(host string) *http.Transport {
|
|||||||
// Start with a copy of http.DefaultTransport and tweak it a bit.
|
// Start with a copy of http.DefaultTransport and tweak it a bit.
|
||||||
tr := http.DefaultTransport.(*http.Transport).Clone()
|
tr := http.DefaultTransport.(*http.Transport).Clone()
|
||||||
|
|
||||||
|
tr.Proxy = tshttpproxy.ProxyFromEnvironment
|
||||||
|
|
||||||
// We do our own zstd compression on uploads, and responses never contain any payload,
|
// We do our own zstd compression on uploads, and responses never contain any payload,
|
||||||
// so don't send "Accept-Encoding: gzip" to save a few bytes on the wire, since there
|
// so don't send "Accept-Encoding: gzip" to save a few bytes on the wire, since there
|
||||||
// will never be any body to decompress:
|
// will never be any body to decompress:
|
||||||
|
@ -8,13 +8,19 @@
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/net/tsaddr"
|
"tailscale.com/net/tsaddr"
|
||||||
|
"tailscale.com/net/tshttpproxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// LoginEndpointForProxyDetermination is the URL used for testing
|
||||||
|
// which HTTP proxy the system should use.
|
||||||
|
var LoginEndpointForProxyDetermination = "https://login.tailscale.com/"
|
||||||
|
|
||||||
// Tailscale returns the current machine's Tailscale interface, if any.
|
// Tailscale returns the current machine's Tailscale interface, if any.
|
||||||
// If none is found, all zero values are returned.
|
// If none is found, all zero values are returned.
|
||||||
// A non-nil error is only returned on a problem listing the system interfaces.
|
// A non-nil error is only returned on a problem listing the system interfaces.
|
||||||
@ -168,6 +174,9 @@ type State struct {
|
|||||||
// DefaultRouteInterface is the interface name for the machine's default route.
|
// DefaultRouteInterface is the interface name for the machine's default route.
|
||||||
// It is not yet populated on all OSes.
|
// It is not yet populated on all OSes.
|
||||||
DefaultRouteInterface string
|
DefaultRouteInterface string
|
||||||
|
|
||||||
|
// HTTPProxy is the HTTP proxy to use.
|
||||||
|
HTTPProxy string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) Equal(s2 *State) bool {
|
func (s *State) Equal(s2 *State) bool {
|
||||||
@ -205,6 +214,15 @@ func GetState() (*State, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
s.DefaultRouteInterface, _ = DefaultRouteInterface()
|
s.DefaultRouteInterface, _ = DefaultRouteInterface()
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", LoginEndpointForProxyDetermination, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if u, err := tshttpproxy.ProxyFromEnvironment(req); err == nil && u != nil {
|
||||||
|
s.HTTPProxy = u.String()
|
||||||
|
}
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
33
net/tshttpproxy/tshttpproxy.go
Normal file
33
net/tshttpproxy/tshttpproxy.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// 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 contains Tailscale additions to httpproxy not available
|
||||||
|
// in golang.org/x/net/http/httpproxy. Notably, it aims to support Windows better.
|
||||||
|
package tshttpproxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// sysProxyFromEnv, if non-nil, specifies a platform-specific ProxyFromEnvironment
|
||||||
|
// func to use if http.ProxyFromEnvironment doesn't return a proxy.
|
||||||
|
// For example, WPAD PAC files on Windows.
|
||||||
|
var sysProxyFromEnv func(*http.Request) (*url.URL, error)
|
||||||
|
|
||||||
|
func ProxyFromEnvironment(req *http.Request) (*url.URL, error) {
|
||||||
|
u, err := http.ProxyFromEnvironment(req)
|
||||||
|
if u != nil && err == nil {
|
||||||
|
return u, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if sysProxyFromEnv != nil {
|
||||||
|
u, err := sysProxyFromEnv(req)
|
||||||
|
if u != nil && err == nil {
|
||||||
|
return u, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
142
net/tshttpproxy/tshttpproxy_windows.go
Normal file
142
net/tshttpproxy/tshttpproxy_windows.go
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
// 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
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user