github.com/core-coin/go-core/v2@v2.1.9/p2p/enode/urlv4.go (about) 1 // Copyright 2018 by the Authors 2 // This file is part of the go-core library. 3 // 4 // The go-core library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-core library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-core library. If not, see <http://www.gnu.org/licenses/>. 16 17 package enode 18 19 import ( 20 "encoding/hex" 21 "errors" 22 "fmt" 23 "net" 24 "net/url" 25 "regexp" 26 "strconv" 27 28 "github.com/core-coin/go-core/v2/crypto" 29 "github.com/core-coin/go-core/v2/p2p/enr" 30 ) 31 32 var ( 33 incompleteNodeURL = regexp.MustCompile("(?i)^(?:enode://)?([0-9a-f]+)$") 34 lookupIPFunc = net.LookupIP 35 ) 36 37 // MustParseV4 parses a node URL. It panics if the URL is not valid. 38 func MustParseV4(rawurl string) *Node { 39 n, err := ParseV4(rawurl) 40 if err != nil { 41 panic("invalid node URL: " + err.Error()) 42 } 43 return n 44 } 45 46 // ParseV4 parses a node URL. 47 // 48 // There are two basic forms of node URLs: 49 // 50 // - incomplete nodes, which only have the public key (node ID) 51 // - complete nodes, which contain the public key and IP/Port information 52 // 53 // For incomplete nodes, the designator must look like one of these 54 // 55 // enode://<hex node id> 56 // <hex node id> 57 // 58 // For complete nodes, the node ID is encoded in the username portion 59 // of the URL, separated from the host by an @ sign. The hostname can 60 // only be given as an IP address or using DNS domain name. 61 // The port in the host name section is the TCP listening port. If the 62 // TCP and UDP (discovery) ports differ, the UDP port is specified as 63 // query parameter "discport". 64 // 65 // In the following example, the node URL describes 66 // a node with IP address 10.3.58.6, TCP listening port 30300 67 // and UDP discovery port 30330. 68 // 69 // enode://<hex node id>@10.3.58.6:30300?discport=30330 70 func ParseV4(rawurl string) (*Node, error) { 71 if m := incompleteNodeURL.FindStringSubmatch(rawurl); m != nil { 72 id, err := parsePubkey(m[1]) 73 if err != nil { 74 return nil, fmt.Errorf("invalid public key (%v)", err) 75 } 76 return NewV4(id, nil, 0, 0), nil 77 } 78 return parseComplete(rawurl) 79 } 80 81 // NewV4 creates a node from discovery v4 node information. The record 82 // contained in the node has a zero-length signature. 83 func NewV4(pubkey *crypto.PublicKey, ip net.IP, tcp, udp int) *Node { 84 var r enr.Record 85 if len(ip) > 0 { 86 r.Set(enr.IP(ip)) 87 } 88 if udp != 0 { 89 r.Set(enr.UDP(udp)) 90 } 91 if tcp != 0 { 92 r.Set(enr.TCP(tcp)) 93 } 94 signV4Compat(&r, pubkey) 95 n, err := New(v4CompatID{}, &r) 96 if err != nil { 97 panic(err) 98 } 99 return n 100 } 101 102 // isNewV4 returns true for nodes created by NewV4. 103 func isNewV4(n *Node) bool { 104 var k ed448raw 105 return n.r.IdentityScheme() == "" && n.r.Load(&k) == nil && len(n.r.Signature()) == 0 106 } 107 108 func parseComplete(rawurl string) (*Node, error) { 109 var ( 110 id *crypto.PublicKey 111 tcpPort, udpPort uint64 112 ) 113 u, err := url.Parse(rawurl) 114 if err != nil { 115 return nil, err 116 } 117 if u.Scheme != "enode" { 118 return nil, errors.New("invalid URL scheme, want \"enode\"") 119 } 120 // Parse the Node ID from the user portion. 121 if u.User == nil { 122 return nil, errors.New("does not contain node ID") 123 } 124 if id, err = parsePubkey(u.User.String()); err != nil { 125 return nil, fmt.Errorf("invalid public key (%v)", err) 126 } 127 // Parse the IP address. 128 ip := net.ParseIP(u.Hostname()) 129 if ip == nil { 130 ips, err := lookupIPFunc(u.Hostname()) 131 if err != nil { 132 return nil, err 133 } 134 ip = ips[0] 135 } 136 // Ensure the IP is 4 bytes long for IPv4 addresses. 137 if ipv4 := ip.To4(); ipv4 != nil { 138 ip = ipv4 139 } 140 // Parse the port numbers. 141 if tcpPort, err = strconv.ParseUint(u.Port(), 10, 16); err != nil { 142 return nil, errors.New("invalid port") 143 } 144 udpPort = tcpPort 145 qv := u.Query() 146 if qv.Get("discport") != "" { 147 udpPort, err = strconv.ParseUint(qv.Get("discport"), 10, 16) 148 if err != nil { 149 return nil, errors.New("invalid discport in query") 150 } 151 } 152 return NewV4(id, ip, int(tcpPort), int(udpPort)), nil 153 } 154 155 // parsePubkey parses a hex-encoded ed448 public key. 156 func parsePubkey(in string) (*crypto.PublicKey, error) { 157 b, err := hex.DecodeString(in) 158 if err != nil { 159 return nil, err 160 } else if len(b) != crypto.PubkeyLength { 161 return nil, fmt.Errorf("wrong length, want %d hex chars", crypto.PubkeyLength*2) 162 } 163 return crypto.UnmarshalPubKey(b) 164 } 165 166 func (n *Node) URLv4() string { 167 var ( 168 scheme enr.ID 169 nodeid string 170 key crypto.PublicKey 171 ) 172 n.Load(&scheme) 173 n.Load((*Ed448)(&key)) 174 switch { 175 case scheme == "v4" || key != crypto.PublicKey{}: 176 nodeid = fmt.Sprintf("%x", key[:]) 177 default: 178 nodeid = fmt.Sprintf("%s.%x", scheme, n.id[:]) 179 } 180 u := url.URL{Scheme: "enode"} 181 if n.Incomplete() { 182 u.Host = nodeid 183 } else { 184 addr := net.TCPAddr{IP: n.IP(), Port: n.TCP()} 185 u.User = url.User(nodeid) 186 u.Host = addr.String() 187 if n.UDP() != n.TCP() { 188 u.RawQuery = "discport=" + strconv.Itoa(n.UDP()) 189 } 190 } 191 return u.String() 192 } 193 194 // PubkeyToIDV4 derives the v4 node address from the given public key. 195 func PubkeyToIDV4(key *crypto.PublicKey) ID { 196 return ID(crypto.SHA3Hash(key[:])) 197 }