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