diff --git a/rpcclient/examples/bitcoincoreunixsocket/main.go b/rpcclient/examples/bitcoincoreunixsocket/main.go index b1bcfc65b8..a859a9e12a 100644 --- a/rpcclient/examples/bitcoincoreunixsocket/main.go +++ b/rpcclient/examples/bitcoincoreunixsocket/main.go @@ -13,11 +13,8 @@ import ( func main() { // Connect to local bitcoin core RPC server using HTTP POST mode over a Unix Socket. connCfg := &rpcclient.ConnConfig{ - // The value passed doesn't matter as long as it's valid - // To avoid failures, functions relying on a host value still require - // one to be passed. If the client has a default host, it will be used as long as it's valid. - Host: "localhost:8332", - UnixSocketPath: "/tmp/test.XXXX", + // For unix sockets, unix:// + "your unix socket path" + Host: "unix:///tmp/test.XXXX", User: "yourrpcuser", Pass: "yourrpcpass", HTTPPostMode: true, // Bitcoin core only supports HTTP POST mode diff --git a/rpcclient/infrastructure.go b/rpcclient/infrastructure.go index 4b07447e36..3c34e624ae 100644 --- a/rpcclient/infrastructure.go +++ b/rpcclient/infrastructure.go @@ -21,6 +21,8 @@ import ( "net/http" "net/url" "os" + "strconv" + "strings" "sync" "sync/atomic" "time" @@ -757,7 +759,6 @@ func (c *Client) handleSendPostMessage(jReq *jsonRequest) { if !c.config.DisableTLS { protocol = "https" } - url := protocol + "://" + c.config.Host var ( err, lastErr error @@ -765,6 +766,24 @@ func (c *Client) handleSendPostMessage(jReq *jsonRequest) { httpResponse *http.Response ) + parsedAddr, err := ParseAddressString(c.config.Host, "") + if err != nil { + jReq.responseChan <- &Response{ + err: fmt.Errorf("failed to parse address %v", err), + } + return + } + + var url string + switch parsedAddr.Network(){ + case "unix", "unixpacket": + // Using a placeholder URL because a non-empty URL is required + // the Unix domain socket is specified in the DialContext. + url = protocol + "://" + "unix" + default: + url = protocol + "://" + c.config.Host + } + tries := 10 for i := 0; i < tries; i++ { var httpReq *http.Request @@ -1268,10 +1287,6 @@ type ConnConfig struct { // EnableBCInfoHacks is an option provided to enable compatibility hacks // when connecting to blockchain.info RPC server EnableBCInfoHacks bool - - // UnixSocketPath is the path to the Unix socket. If set, it overrides - // the Host field. - UnixSocketPath string } // getAuth returns the username and passphrase that will actually be used for @@ -1336,20 +1351,20 @@ func newHTTPClient(config *ConnConfig) (*http.Client, error) { } } + parsedAddr, err := ParseAddressString(config.Host, "") + if err != nil { + return nil, err + } client := http.Client{ Transport: &http.Transport{ Proxy: proxyFunc, TLSClientConfig: tlsConfig, + DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { + return net.Dial(parsedAddr.Network(), parsedAddr.String()) + }, }, } - // Configure the client to use a Unix socket if one is configured. - if config.UnixSocketPath != "" { - client.Transport.(*http.Transport).DialContext = func(_ context.Context, _, _ string) (net.Conn, error) { - return net.Dial("unix", config.UnixSocketPath) - } - } - return &client, nil } @@ -1710,3 +1725,73 @@ func (c *Client) Send() error { return nil } + +func ParseAddressString(strAddress string, defaultPort string) (net.Addr, error) { + var parsedNetwork, parsedAddr string + + // Addresses can either be in network://address:port format, + // network:address:port, address:port, or just port. We want to support + // all possible types. + if strings.Contains(strAddress, "://") { + parts := strings.Split(strAddress, "://") + parsedNetwork, parsedAddr = parts[0], parts[1] + } else if strings.Contains(strAddress, ":") { + parts := strings.Split(strAddress, ":") + parsedNetwork = parts[0] + parsedAddr = strings.Join(parts[1:], ":") + } else { + parsedAddr = strAddress + } + + // Only TCP and Unix socket addresses are valid. We can't use IP or + // UDP only connections for anything we do in lnd. + switch parsedNetwork { + case "unix", "unixpacket": + return net.ResolveUnixAddr(parsedNetwork, parsedAddr) + + case "tcp", "tcp4", "tcp6": + return net.ResolveTCPAddr(parsedNetwork, verifyPort(parsedAddr, defaultPort)) + + case "ip", "ip4", "ip6", "udp", "udp4", "udp6", "unixgram": + return nil, fmt.Errorf("only TCP or unix socket "+ + "addresses are supported: %s", parsedAddr) + + default: + // We'll now possibly apply the default port, use the local + // host short circuit, or parse out an all interfaces listen. + addrWithPort := verifyPort(strAddress, defaultPort) + + // Otherwise, we'll attempt to resolve the host. + return net.ResolveTCPAddr("tcp", addrWithPort) + } +} + +func verifyPort(address string, defaultPort string) string { + host, port, err := net.SplitHostPort(address) + if err != nil { + // If the address itself is just an integer, then we'll assume + // that we're mapping this directly to a localhost:port pair. + // This ensures we maintain the legacy behavior. + if _, err := strconv.Atoi(address); err == nil { + return net.JoinHostPort("localhost", address) + } + + // Otherwise, we'll assume that the address just failed to + // attach its own port, so we'll use the default port. In the + // case of IPv6 addresses, if the host is already surrounded by + // brackets, then we'll avoid using the JoinHostPort function, + // since it will always add a pair of brackets. + if strings.HasPrefix(address, "[") { + return address + ":" + defaultPort + } + return net.JoinHostPort(address, defaultPort) + } + + // In the case that both the host and port are empty, we'll use the + // default port. + if host == "" && port == "" { + return ":" + defaultPort + } + + return address +} \ No newline at end of file