Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable the RpcClient to send requests to Bitcoin Core over a Unix socket. #2168

Merged
merged 1 commit into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ require (
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.5.0 // indirect
golang.org/x/net v0.24.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,15 @@ golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc h1:zK/HqS5bZxDptfPJNq8v7vJfXtkU7r9TLIoSr1bXaP4=
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
Expand All @@ -108,6 +111,7 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed h1:J22ig1FUekjjkmZUM7pTKixYm8DvrYsvrBZdunYeIuQ=
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
Expand Down
41 changes: 41 additions & 0 deletions rpcclient/examples/bitcoincoreunixsocket/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
Bitcoin Core HTTP POST Over Unix Socket Example
==============================

This example shows how to use the rpcclient package to connect to a Bitcoin
Core RPC server using HTTP POST mode over a Unix Socket with TLS disabled
and gets the current block count.

## Running the Example

The first step is to use `go get` to download and install the rpcclient package:

```bash
$ go get github.com/btcsuite/btcd/rpcclient
```

Next, modify the `main.go` source to specify the correct RPC username and
password for the RPC server:

```Go
User: "yourrpcuser",
Pass: "yourrpcpass",
```

As Bitcoin Core supports only TCP/IP, we'll redirect RPC requests from the
Unix Socket to Bitcoin Core. For this example, we'll use the `socat` command:

```bash
$ socat -d UNIX-LISTEN:"my-unix-socket-path",fork TCP:"host-address"
$ socat -d UNIX-LISTEN:/tmp/test.XXXX,fork TCP:localhost:8332
```

Finally, navigate to the example's directory and run it with:

```bash
$ cd $GOPATH/src/github.com/btcsuite/btcd/rpcclient/examples/bitcoincorehttp
$ go run *.go
```

## License

This example is licensed under the [copyfree](http://copyfree.org) ISC License.
39 changes: 39 additions & 0 deletions rpcclient/examples/bitcoincoreunixsocket/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) 2014-2017 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package main

import (
"log"

"github.com/btcsuite/btcd/rpcclient"
)

func main() {
// Connect to local bitcoin core RPC server using HTTP POST mode over a
// Unix Socket.
connCfg := &rpcclient.ConnConfig{
// For unix sockets, use unix:// + "your unix socket path".
Host: "unix:///tmp/test.XXXX",
User: "yourrpcuser",
Pass: "yourrpcpass",
HTTPPostMode: true, // Bitcoin core only supports HTTP POST mode.
DisableTLS: true, // Bitcoin core does not provide TLS by default.
}

// Notice the notification parameter is nil since notifications are
robertmin1 marked this conversation as resolved.
Show resolved Hide resolved
// not supported in HTTP POST mode.
client, err := rpcclient.New(connCfg, nil)
if err != nil {
log.Fatal(err)
}
defer client.Shutdown()

// Get the current block count.
blockCount, err := client.GetBlockCount()
if err != nil {
log.Fatal(err)
}
log.Printf("Block count: %d", blockCount)
}
106 changes: 105 additions & 1 deletion rpcclient/infrastructure.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package rpcclient
import (
"bytes"
"container/list"
"context"
"crypto/tls"
"crypto/x509"
"encoding/base64"
Expand All @@ -20,6 +21,8 @@ import (
"net/http"
"net/url"
"os"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
Expand Down Expand Up @@ -756,14 +759,31 @@ func (c *Client) handleSendPostMessage(jReq *jsonRequest) {
if !c.config.DisableTLS {
protocol = "https"
}
url := protocol + "://" + c.config.Host

var (
err, lastErr error
backoff time.Duration
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
Expand Down Expand Up @@ -1331,10 +1351,17 @@ 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())
},
},
}

Expand Down Expand Up @@ -1698,3 +1725,80 @@ func (c *Client) Send() error {

return nil
}

// ParseAddressString converts an address in string format to a net.Addr that is
// compatible with btcd. UDP is not supported because btcd needs reliable
// connections. We accept a custom function to resolve any TCP addresses so
// that caller is able control exactly how resolution is performed.
func ParseAddressString(strAddress 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))

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 use the local host short circuit
// or parse out an all interfaces listen.
addrWithPort := verifyPort(strAddress)

// Otherwise, we'll attempt to resolve the host.
return net.ResolveTCPAddr("tcp", addrWithPort)
}
}

// verifyPort makes sure that an address string has both a host and a port.
// If the address is just a port, then we'll assume that the user is using the
// short cut to specify a localhost:port address.
func verifyPort(address 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 leave it as is. 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
}
return net.JoinHostPort(address, "")
}

// In the case that both the host and port are empty, we'll use the
// an empty port.
if host == "" && port == "" {
return ":"
}

return address
}
Loading