From 72ccb06fc41ca72d3a7dfda5699737bcf042dbde Mon Sep 17 00:00:00 2001 From: JeremyRand Date: Fri, 2 Aug 2019 05:18:27 +0000 Subject: [PATCH] (WIP) Switch to rpcclient from upstream Conformal TODO: Wait for Conformal to merge the symbol exporting patch TODO: Fix cookie authentication --- backend/backend.go | 11 +- namecoin/namecoin.go | 221 +++--------------- ncdt/ncdt.go | 27 ++- ncdumpzone/ncdumpzone.go | 10 +- ncdumpzone/ncdumpzone/ncdumpzone.go | 22 +- server/server.go | 32 ++- server/web.go | 4 +- .../firefoxoverridesync.go | 4 +- 8 files changed, 103 insertions(+), 228 deletions(-) diff --git a/backend/backend.go b/backend/backend.go index 075a3ee..1112396 100644 --- a/backend/backend.go +++ b/backend/backend.go @@ -19,7 +19,7 @@ import "time" // Provides an abstract zone file for the Namecoin .bit TLD. type Backend struct { //s *Server - nc namecoin.Conn + nc *namecoin.Client cache lru.Cache // items are of type *Domain cacheMutex sync.Mutex cfg Config @@ -31,7 +31,7 @@ var log, Log = xlog.New("ncdns.backend") // Backend configuration. type Config struct { - NamecoinConn namecoin.Conn + NamecoinConn *namecoin.Client // Timeout (in milliseconds) for Namecoin RPC requests NamecoinTimeout int @@ -65,9 +65,6 @@ func New(cfg *Config) (backend *Backend, err error) { b.cfg = *cfg b.nc = b.cfg.NamecoinConn - //b.nc.Username = cfg.RPCUsername - //b.nc.Password = cfg.RPCPassword - //b.nc.Server = cfg.RPCAddress b.cache.MaxEntries = cfg.CacheMaxEntries if b.cache.MaxEntries == 0 { @@ -344,13 +341,13 @@ func (b *Backend) resolveName(name string) (jsonValue string, err error) { return fv, nil } - // The btcjson package has quite a long timeout, far in excess of standard + // The rpcclient package has quite a long timeout, far in excess of standard // DNS timeouts. We need to return an error response rapidly if we can't // query the backend. Be generous with the timeout as responses from the // Namecoin JSON-RPC seem sluggish sometimes. result := make(chan struct{}, 1) go func() { - jsonValue, err = b.nc.Query(name) + jsonValue, err = b.nc.NameQuery(name) log.Errore(err, "failed to query namecoin") result <- struct{}{} }() diff --git a/namecoin/namecoin.go b/namecoin/namecoin.go index c79445a..4937c1a 100644 --- a/namecoin/namecoin.go +++ b/namecoin/namecoin.go @@ -1,215 +1,48 @@ package namecoin -// btcjson had to be modified a bit to get correct error reporting. import ( - extratypes "github.com/hlandau/ncbtcjsontypes" - "github.com/hlandauf/btcjson" + "github.com/btcsuite/btcd/btcjson" + "github.com/btcsuite/btcd/rpcclient" "gopkg.in/hlandau/madns.v1/merr" - "expvar" - "fmt" - "sync/atomic" + "github.com/namecoin/nmcrpcclient" ) -var cQueryCalls = expvar.NewInt("ncdns.namecoin.numQueryCalls") -var cSyncCalls = expvar.NewInt("ncdns.namecoin.numSyncCalls") -var cFilterCalls = expvar.NewInt("ncdns.namecoin.numFilterCalls") -var cScanCalls = expvar.NewInt("ncdns.namecoin.numScanCalls") -var cCurHeightCalls = expvar.NewInt("ncdns.namecoin.numCurHeightCalls") - -// Used for generating IDs for JSON-RPC requests. -var idCounter int32 - -func newID() int32 { - return atomic.AddInt32(&idCounter, 1) -} - -// Used to query a Namecoin JSON-RPC interface. Initialize the struct with a -// username, password, and address (hostname:port). -type Conn struct { - Username string - Password string - - // If set, this is called to obtain the username and password instead of - // using the Username and Password fields. - GetAuth func() (username, password string, err error) - - Server string -} - -func (nc *Conn) getAuth() (username string, password string, err error) { - if nc.GetAuth == nil { - return nc.Username, nc.Password, nil - } - - return nc.GetAuth() -} - -func (nc *Conn) rpcSend(cmd btcjson.Cmd) (btcjson.Reply, error) { - username, password, err := nc.getAuth() - if err != nil { - return btcjson.Reply{}, err - } - - return btcjson.RpcSend(username, password, nc.Server, cmd) -} - -// Query the Namecoin daemon for a Namecoin domain (e.g. d/example). -// If the domain exists, returns the value stored in Namecoin, which should be JSON. -// Note that this will return domain data even if the domain is expired. -func (nc *Conn) Query(name string) (v string, err error) { - cQueryCalls.Add(1) - - cmd, err := extratypes.NewNameShowCmd(newID(), name) - if err != nil { - //log.Info("NC NEWCMD ", err) - return "", err - } - - r, err := nc.rpcSend(cmd) - if err != nil { - return "", err - } - - if r.Error != nil { - //log.Info("RPC error: ", r.Error) - if r.Error.Code == -4 { - return "", merr.ErrNoSuchDomain - } - return "", r.Error - } - - if r.Result == nil { - //log.Info("NC NILRESULT") - return "", fmt.Errorf("got nil result") - } - - if nsr, ok := r.Result.(*extratypes.NameShowReply); ok { - //log.Info("NC OK") - return nsr.Value, nil - } - - //log.Info("NC BADREPLY") - return "", fmt.Errorf("bad reply") -} - -var ErrSyncNoSuchBlock = fmt.Errorf("no block exists with given hash") - -const rpcInvalidAddressOrKey = -5 - -func (nc *Conn) Sync(hash string, count int, wait bool) ([]extratypes.NameSyncEvent, error) { - cSyncCalls.Add(1) - - cmd, err := extratypes.NewNameSyncCmd(newID(), hash, count, wait) - if err != nil { - return nil, err - } - - r, err := nc.rpcSend(cmd) - if err != nil { - return nil, err - } - - if r.Error != nil { - if r.Error.Code == rpcInvalidAddressOrKey { - return nil, ErrSyncNoSuchBlock - } - return nil, r.Error - } - - if r.Result == nil { - return nil, fmt.Errorf("got nil result") - } - - if nsr, ok := r.Result.(extratypes.NameSyncReply); ok { - return []extratypes.NameSyncEvent(nsr), nil - } - - return nil, fmt.Errorf("bad reply") +// Client represents an nmcrpcclient.Client with an additional DNS-friendly +// convenience wrapper around NameShow. +type Client struct { + *nmcrpcclient.Client } -func (nc *Conn) CurHeight() (int, error) { - cCurHeightCalls.Add(1) - - cmd, err := btcjson.NewGetInfoCmd(newID()) - if err != nil { - return 0, err - } - - r, err := nc.rpcSend(cmd) - if err != nil { - return 0, err - } - - if r.Error != nil { - return 0, r.Error - } - - if r.Result == nil { - return 0, fmt.Errorf("got nil result") - } - - if rep, ok := r.Result.(*btcjson.InfoResult); ok { - return int(rep.Blocks), nil - } - - return 0, fmt.Errorf("bad reply") -} - -func (nc *Conn) Filter(regexp string, maxage, from, count int) (names []extratypes.NameFilterItem, err error) { - cFilterCalls.Add(1) - - cmd, err := extratypes.NewNameFilterCmd(newID(), regexp, maxage, from, count) +func New(config *rpcclient.ConnConfig, ntfnHandlers *rpcclient.NotificationHandlers) (*Client, error) { + nmcClient, err := nmcrpcclient.New(config, ntfnHandlers) if err != nil { return nil, err } - r, err := nc.rpcSend(cmd) - if err != nil { - return nil, err - } - - if r.Error != nil { - return nil, r.Error - } - - if r.Result == nil { - return nil, fmt.Errorf("got nil result") - } - - if nsr, ok := r.Result.(extratypes.NameFilterReply); ok { - return []extratypes.NameFilterItem(nsr), nil - } - - return nil, fmt.Errorf("bad reply") + return &Client{nmcClient}, nil } -func (nc *Conn) Scan(from string, count int) (names []extratypes.NameFilterItem, err error) { - cScanCalls.Add(1) - - cmd, err := extratypes.NewNameScanCmd(newID(), from, count) +// NameQuery returns the value of a name. If the name doesn't exist, the error +// returned will be merr.ErrNoSuchDomain. +func (c *Client) NameQuery(name string) (string, error) { + nameData, err := c.NameShow(name) if err != nil { - return nil, err - } - - r, err := nc.rpcSend(cmd) - if err != nil { - return nil, err - } - - if r.Error != nil { - return nil, r.Error - } + if jerr, ok := err.(*btcjson.RPCError); ok { + if jerr.Code == btcjson.ErrRPCWallet { + // ErrRPCWallet from name_show indicates that + // the name does not exist. + return "", merr.ErrNoSuchDomain + } + } - if r.Result == nil { - return nil, fmt.Errorf("got nil result") + // Some error besides NXDOMAIN happened; pass that error + // through unaltered. + return "", err } - if nsr, ok := r.Result.(extratypes.NameFilterReply); ok { - return []extratypes.NameFilterItem(nsr), nil - } + // TODO: check the "value_error" field for errors and report those to the caller. - return nil, fmt.Errorf("bad reply") + // We got the name data. Return the value. + return nameData.Value, nil } - -// © 2014 Hugo Landau GPLv3 or later diff --git a/ncdt/ncdt.go b/ncdt/ncdt.go index be969b2..aa242ea 100644 --- a/ncdt/ncdt.go +++ b/ncdt/ncdt.go @@ -7,12 +7,13 @@ import "fmt" import "os" import "strconv" import "io/ioutil" +import "github.com/btcsuite/btcd/rpcclient" import "github.com/namecoin/ncdns/util" var rpchost = flag.String("rpchost", "", "Namecoin RPC host:port") var rpcuser = flag.String("rpcuser", "", "Namecoin RPC username") var rpcpass = flag.String("rpcpass", "", "Namecoin RPC password") -var conn namecoin.Conn +var conn *namecoin.Client func usage() { fmt.Fprintf(os.Stderr, "Usage: ncdt [options] [ ...]\n") @@ -41,7 +42,7 @@ func translateValue(k, v string) (string, error) { f = os.NewFile(uintptr(n), "-") } else if len(v) == 1 { - return conn.Query(k) + return conn.NameQuery(k) } else { f, err = os.Open(v) } @@ -71,9 +72,25 @@ func main() { usage() } - conn.Username = *rpcuser - conn.Password = *rpcpass - conn.Server = *rpchost + // Connect to local namecoin core RPC server using HTTP POST mode. + connCfg := &rpcclient.ConnConfig{ + Host: *rpchost, + User: *rpcuser, + Pass: *rpcpass, + HTTPPostMode: true, // Namecoin core only supports HTTP POST mode + DisableTLS: true, // Namecoin core does not provide TLS by default + } + + var err error + + // Notice the notification parameter is nil since notifications are + // not supported in HTTP POST mode. + conn, err = namecoin.New(connCfg, nil) + if err != nil { + fmt.Fprintf(os.Stderr, "Error creating RPC client: %v\n", err) + os.Exit(1) + } + defer conn.Shutdown() for i := 0; i+1 < len(args); i += 2 { k := args[i] diff --git a/ncdumpzone/ncdumpzone.go b/ncdumpzone/ncdumpzone.go index d70ac26..d096156 100644 --- a/ncdumpzone/ncdumpzone.go +++ b/ncdumpzone/ncdumpzone.go @@ -8,12 +8,12 @@ import ( "github.com/hlandau/xlog" "github.com/miekg/dns" - extratypes "github.com/hlandau/ncbtcjsontypes" "github.com/namecoin/ncdns/namecoin" "github.com/namecoin/ncdns/ncdomain" "github.com/namecoin/ncdns/rrtourl" "github.com/namecoin/ncdns/tlsoverridefirefox" "github.com/namecoin/ncdns/util" + "github.com/namecoin/nmcjson" ) var log, Log = xlog.New("ncdumpzone") @@ -41,7 +41,7 @@ func dumpRR(rr dns.RR, dest io.Writer, format string) error { return nil } -func dumpName(item *extratypes.NameFilterItem, conn namecoin.Conn, +func dumpName(item *nmcjson.NameShowResult, conn *namecoin.Client, dest io.Writer, format string) error { // The order in which name_scan returns results is seemingly rather // random, so we can't stop when we see a non-d/ name, so just skip it. @@ -55,7 +55,7 @@ func dumpName(item *extratypes.NameFilterItem, conn namecoin.Conn, } getNameFunc := func(k string) (string, error) { - return conn.Query(k) + return conn.NameQuery(k) } var errors []error @@ -83,7 +83,7 @@ func dumpName(item *extratypes.NameFilterItem, conn namecoin.Conn, // Dump extracts all domain names from conn, formats them according to the // specified format, and writes the result to dest. -func Dump(conn namecoin.Conn, dest io.Writer, format string) error { +func Dump(conn *namecoin.Client, dest io.Writer, format string) error { if format != "zonefile" && format != "firefox-override" && format != "url-list" { return fmt.Errorf("Invalid \"format\" argument: %s", format) @@ -93,7 +93,7 @@ func Dump(conn namecoin.Conn, dest io.Writer, format string) error { continuing := 0 for { - results, err := conn.Scan(currentName, perCall) + results, err := conn.NameScan(currentName, perCall) if err != nil { return fmt.Errorf("scan: %s", err) } diff --git a/ncdumpzone/ncdumpzone/ncdumpzone.go b/ncdumpzone/ncdumpzone/ncdumpzone.go index 99fadee..93b07df 100644 --- a/ncdumpzone/ncdumpzone/ncdumpzone.go +++ b/ncdumpzone/ncdumpzone/ncdumpzone.go @@ -4,6 +4,7 @@ import ( "os" "github.com/hlandau/xlog" + "github.com/btcsuite/btcd/rpcclient" "gopkg.in/hlandau/easyconfig.v1" "gopkg.in/hlandau/easyconfig.v1/cflag" @@ -27,7 +28,7 @@ var ( "\"url-list\" = URL list.") ) -var conn namecoin.Conn +var conn *namecoin.Client var config = easyconfig.Configurator{ ProgramName: "ncdumpzone", @@ -39,9 +40,22 @@ func main() { log.Fatalf("Couldn't parse configuration: %s", err) } - conn.Server = rpchostFlag.Value() - conn.Username = rpcuserFlag.Value() - conn.Password = rpcpassFlag.Value() + // Connect to local namecoin core RPC server using HTTP POST mode. + connCfg := &rpcclient.ConnConfig{ + Host: rpchostFlag.Value(), + User: rpcuserFlag.Value(), + Pass: rpcpassFlag.Value(), + HTTPPostMode: true, // Namecoin core only supports HTTP POST mode + DisableTLS: true, // Namecoin core does not provide TLS by default + } + + // Notice the notification parameter is nil since notifications are + // not supported in HTTP POST mode. + conn, err = namecoin.New(connCfg, nil) + if err != nil { + log.Fatalf("Couldn't create RPC client: %s", err) + } + defer conn.Shutdown() err = ncdumpzone.Dump(conn, os.Stdout, formatFlag.Value()) if err != nil { diff --git a/server/server.go b/server/server.go index 83d2c4d..6d895e4 100644 --- a/server/server.go +++ b/server/server.go @@ -9,6 +9,7 @@ import ( "strings" "sync" + "github.com/btcsuite/btcd/rpcclient" "github.com/hlandau/buildinfo" "github.com/hlandau/xlog" "github.com/miekg/dns" @@ -25,7 +26,7 @@ type Server struct { cfg Config engine madns.Engine - namecoinConn namecoin.Conn + namecoinConn *namecoin.Client mux *dns.ServeMux udpServer *dns.Server @@ -74,18 +75,31 @@ var ncdnsVersion string func New(cfg *Config) (s *Server, err error) { ncdnsVersion = buildinfo.VersionSummary("github.com/namecoin/ncdns", "ncdns") + // Connect to local namecoin core RPC server using HTTP POST mode. + connCfg := &rpcclient.ConnConfig{ + Host: cfg.NamecoinRPCAddress, + User: cfg.NamecoinRPCUsername, + Pass: cfg.NamecoinRPCPassword, + HTTPPostMode: true, // Namecoin core only supports HTTP POST mode + DisableTLS: true, // Namecoin core does not provide TLS by default + } + + // Notice the notification parameter is nil since notifications are + // not supported in HTTP POST mode. + client, err := namecoin.New(connCfg, nil) + if err != nil { + return nil, err + } + s = &Server{ cfg: *cfg, - namecoinConn: namecoin.Conn{ - Username: cfg.NamecoinRPCUsername, - Password: cfg.NamecoinRPCPassword, - Server: cfg.NamecoinRPCAddress, - }, + namecoinConn: client, } - if s.cfg.NamecoinRPCCookiePath != "" { - s.namecoinConn.GetAuth = cookieRetriever(s.cfg.NamecoinRPCCookiePath) - } + // TODO: Conformal rpcclient doesn't yet support cookie auth; we should submit it upstream and then re-enable this. + //if s.cfg.NamecoinRPCCookiePath != "" { + // s.namecoinConn.GetAuth = cookieRetriever(s.cfg.NamecoinRPCCookiePath) + //} if s.cfg.CanonicalNameservers != "" { s.cfg.canonicalNameservers = strings.Split(s.cfg.CanonicalNameservers, ",") diff --git a/server/web.go b/server/web.go index 8ce2f3a..c79e7fe 100644 --- a/server/web.go +++ b/server/web.go @@ -140,7 +140,7 @@ func (ws *webServer) handleLookup(rw http.ResponseWriter, req *http.Request) { info.JSONValue = req.FormValue("value") info.Value = strings.Trim(info.JSONValue, " \t\r\n") if info.Value == "" { - info.Value, info.ExistenceError = ws.s.namecoinConn.Query(info.NamecoinName) + info.Value, info.ExistenceError = ws.s.namecoinConn.NameQuery(info.NamecoinName) if info.ExistenceError != nil { return } @@ -170,7 +170,7 @@ func (ws *webServer) handleLookup(rw http.ResponseWriter, req *http.Request) { } func (ws *webServer) resolveFunc(name string) (string, error) { - return ws.s.namecoinConn.Query(name) + return ws.s.namecoinConn.NameQuery(name) } func (ws *webServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { diff --git a/tlsoverridefirefox/tlsoverridefirefoxsync/firefoxoverridesync.go b/tlsoverridefirefox/tlsoverridefirefoxsync/firefoxoverridesync.go index 190a274..132b729 100644 --- a/tlsoverridefirefox/tlsoverridefirefoxsync/firefoxoverridesync.go +++ b/tlsoverridefirefox/tlsoverridefirefoxsync/firefoxoverridesync.go @@ -37,7 +37,7 @@ var zoneDataMux sync.Mutex // situation, .bit domains must stop resolving until the issue is corrected. // Forcing ncdns to exit is the least complex way to achieve this. -func watchZone(conn namecoin.Conn) { +func watchZone(conn *namecoin.Client) { for { var result bytes.Buffer @@ -125,7 +125,7 @@ func profileInUse() bool { // Start starts 2 background threads that synchronize the blockchain's TLSA // records to a Firefox profile's cert_override.txt. It accepts a connection // to access Namecoin Core, as well as a host suffix (usually "bit"). -func Start(conn namecoin.Conn, suffix string) error { +func Start(conn *namecoin.Client, suffix string) error { if syncEnableFlag.Value() { go watchZone(conn) go watchProfile(suffix)