You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

227 lines
5.9 KiB

  1. package p2p
  2. import (
  3. "context"
  4. "encoding/hex"
  5. "errors"
  6. "fmt"
  7. "net"
  8. "net/url"
  9. "regexp"
  10. "strconv"
  11. "strings"
  12. "github.com/tendermint/tendermint/crypto"
  13. )
  14. const (
  15. // NodeIDByteLength is the length of a crypto.Address. Currently only 20.
  16. // FIXME: support other length addresses?
  17. NodeIDByteLength = crypto.AddressSize
  18. )
  19. var (
  20. // reNodeID is a regexp for valid node IDs.
  21. reNodeID = regexp.MustCompile(`^[0-9a-f]{40}$`)
  22. // reHasScheme tries to detect URLs with schemes. It looks for a : before a / (if any).
  23. reHasScheme = regexp.MustCompile(`^[^/]+:`)
  24. // reSchemeIsHost tries to detect URLs where the scheme part is instead a
  25. // hostname, i.e. of the form "host:80/path" where host: is a hostname.
  26. reSchemeIsHost = regexp.MustCompile(`^[^/:]+:\d+(/|$)`)
  27. )
  28. // NodeID is a hex-encoded crypto.Address. It must be lowercased
  29. // (for uniqueness) and of length 2*NodeIDByteLength.
  30. type NodeID string
  31. // NewNodeID returns a lowercased (normalized) NodeID, or errors if the
  32. // node ID is invalid.
  33. func NewNodeID(nodeID string) (NodeID, error) {
  34. n := NodeID(strings.ToLower(nodeID))
  35. return n, n.Validate()
  36. }
  37. // NodeIDFromPubKey creates a node ID from a given PubKey address.
  38. func NodeIDFromPubKey(pubKey crypto.PubKey) NodeID {
  39. return NodeID(hex.EncodeToString(pubKey.Address()))
  40. }
  41. // Bytes converts the node ID to its binary byte representation.
  42. func (id NodeID) Bytes() ([]byte, error) {
  43. bz, err := hex.DecodeString(string(id))
  44. if err != nil {
  45. return nil, fmt.Errorf("invalid node ID encoding: %w", err)
  46. }
  47. return bz, nil
  48. }
  49. // Validate validates the NodeID.
  50. func (id NodeID) Validate() error {
  51. switch {
  52. case len(id) == 0:
  53. return errors.New("empty node ID")
  54. case len(id) != 2*NodeIDByteLength:
  55. return fmt.Errorf("invalid node ID length %d, expected %d", len(id), 2*NodeIDByteLength)
  56. case !reNodeID.MatchString(string(id)):
  57. return fmt.Errorf("node ID can only contain lowercased hex digits")
  58. default:
  59. return nil
  60. }
  61. }
  62. // NodeAddress is a node address URL. It differs from a transport Endpoint in
  63. // that it contains the node's ID, and that the address hostname may be resolved
  64. // into multiple IP addresses (and thus multiple endpoints).
  65. //
  66. // If the URL is opaque, i.e. of the form "scheme:opaque", then the opaque part
  67. // is expected to contain a node ID.
  68. type NodeAddress struct {
  69. NodeID NodeID
  70. Protocol Protocol
  71. Hostname string
  72. Port uint16
  73. Path string
  74. }
  75. // ParseNodeAddress parses a node address URL into a NodeAddress, normalizing
  76. // and validating it.
  77. func ParseNodeAddress(urlString string) (NodeAddress, error) {
  78. // url.Parse requires a scheme, so if it fails to parse a scheme-less URL
  79. // we try to apply a default scheme.
  80. url, err := url.Parse(urlString)
  81. if (err != nil || url.Scheme == "") &&
  82. (!reHasScheme.MatchString(urlString) || reSchemeIsHost.MatchString(urlString)) {
  83. url, err = url.Parse(string(defaultProtocol) + "://" + urlString)
  84. }
  85. if err != nil {
  86. return NodeAddress{}, fmt.Errorf("invalid node address %q: %w", urlString, err)
  87. }
  88. address := NodeAddress{
  89. Protocol: Protocol(strings.ToLower(url.Scheme)),
  90. }
  91. // Opaque URLs are expected to contain only a node ID, also used as path.
  92. if url.Opaque != "" {
  93. address.NodeID = NodeID(url.Opaque)
  94. address.Path = url.Opaque
  95. return address, address.Validate()
  96. }
  97. // Otherwise, just parse a normal networked URL.
  98. if url.User != nil {
  99. address.NodeID = NodeID(strings.ToLower(url.User.Username()))
  100. }
  101. address.Hostname = strings.ToLower(url.Hostname())
  102. if portString := url.Port(); portString != "" {
  103. port64, err := strconv.ParseUint(portString, 10, 16)
  104. if err != nil {
  105. return NodeAddress{}, fmt.Errorf("invalid port %q: %w", portString, err)
  106. }
  107. address.Port = uint16(port64)
  108. }
  109. address.Path = url.Path
  110. if url.RawQuery != "" {
  111. address.Path += "?" + url.RawQuery
  112. }
  113. if url.Fragment != "" {
  114. address.Path += "#" + url.Fragment
  115. }
  116. if address.Path != "" {
  117. switch address.Path[0] {
  118. case '/', '#', '?':
  119. default:
  120. address.Path = "/" + address.Path
  121. }
  122. }
  123. return address, address.Validate()
  124. }
  125. // Resolve resolves a NodeAddress into a set of Endpoints, by expanding
  126. // out a DNS hostname to IP addresses.
  127. func (a NodeAddress) Resolve(ctx context.Context) ([]Endpoint, error) {
  128. if a.Protocol == "" {
  129. return nil, errors.New("address has no protocol")
  130. }
  131. // If there is no hostname, this is an opaque URL in the form
  132. // "scheme:opaque", and the opaque part is assumed to be node ID used as
  133. // Path.
  134. if a.Hostname == "" {
  135. if a.NodeID == "" {
  136. return nil, errors.New("local address has no node ID")
  137. }
  138. return []Endpoint{{
  139. Protocol: a.Protocol,
  140. Path: string(a.NodeID),
  141. }}, nil
  142. }
  143. ips, err := net.DefaultResolver.LookupIP(ctx, "ip", a.Hostname)
  144. if err != nil {
  145. return nil, err
  146. }
  147. endpoints := make([]Endpoint, len(ips))
  148. for i, ip := range ips {
  149. endpoints[i] = Endpoint{
  150. Protocol: a.Protocol,
  151. IP: ip,
  152. Port: a.Port,
  153. Path: a.Path,
  154. }
  155. }
  156. return endpoints, nil
  157. }
  158. // String formats the address as a URL string.
  159. func (a NodeAddress) String() string {
  160. u := url.URL{Scheme: string(a.Protocol)}
  161. if a.NodeID != "" {
  162. u.User = url.User(string(a.NodeID))
  163. }
  164. switch {
  165. case a.Hostname != "":
  166. if a.Port > 0 {
  167. u.Host = net.JoinHostPort(a.Hostname, strconv.Itoa(int(a.Port)))
  168. } else {
  169. u.Host = a.Hostname
  170. }
  171. u.Path = a.Path
  172. case a.Protocol != "" && (a.Path == "" || a.Path == string(a.NodeID)):
  173. u.User = nil
  174. u.Opaque = string(a.NodeID) // e.g. memory:id
  175. case a.Path != "" && a.Path[0] != '/':
  176. u.Path = "/" + a.Path // e.g. some/path
  177. default:
  178. u.Path = a.Path // e.g. /some/path
  179. }
  180. return strings.TrimPrefix(u.String(), "//")
  181. }
  182. // Validate validates a NodeAddress.
  183. func (a NodeAddress) Validate() error {
  184. if a.Protocol == "" {
  185. return errors.New("no protocol")
  186. }
  187. if a.NodeID == "" {
  188. return errors.New("no peer ID")
  189. } else if err := a.NodeID.Validate(); err != nil {
  190. return fmt.Errorf("invalid peer ID: %w", err)
  191. }
  192. if a.Port > 0 && a.Hostname == "" {
  193. return errors.New("cannot specify port without hostname")
  194. }
  195. return nil
  196. }