Skip to content

Commit

Permalink
unix sockets
Browse files Browse the repository at this point in the history
  • Loading branch information
robertmin1 committed Apr 24, 2024
1 parent cbd17bc commit 0204a86
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 17 deletions.
7 changes: 2 additions & 5 deletions rpcclient/examples/bitcoincoreunixsocket/main.go
Expand Up @@ -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
Expand Down
109 changes: 97 additions & 12 deletions rpcclient/infrastructure.go
Expand Up @@ -21,6 +21,8 @@ import (
"net/http"
"net/url"
"os"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
Expand Down Expand Up @@ -757,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 @@ -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
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
}

0 comments on commit 0204a86

Please sign in to comment.