github.com/mister-meeseeks/go-ethereum@v1.9.7/cmd/devp2p/enrcmd.go (about) 1 // Copyright 2019 The go-ethereum Authors 2 // This file is part of go-ethereum. 3 // 4 // go-ethereum is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU 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 // go-ethereum 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 General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>. 16 17 package main 18 19 import ( 20 "bytes" 21 "encoding/base64" 22 "encoding/hex" 23 "fmt" 24 "io/ioutil" 25 "net" 26 "os" 27 "strconv" 28 "strings" 29 30 "github.com/ethereum/go-ethereum/p2p/enode" 31 "github.com/ethereum/go-ethereum/p2p/enr" 32 "github.com/ethereum/go-ethereum/rlp" 33 "gopkg.in/urfave/cli.v1" 34 ) 35 36 var enrdumpCommand = cli.Command{ 37 Name: "enrdump", 38 Usage: "Pretty-prints node records", 39 Action: enrdump, 40 Flags: []cli.Flag{ 41 cli.StringFlag{Name: "file"}, 42 }, 43 } 44 45 func enrdump(ctx *cli.Context) error { 46 var source string 47 if file := ctx.String("file"); file != "" { 48 if ctx.NArg() != 0 { 49 return fmt.Errorf("can't dump record from command-line argument in -file mode") 50 } 51 var b []byte 52 var err error 53 if file == "-" { 54 b, err = ioutil.ReadAll(os.Stdin) 55 } else { 56 b, err = ioutil.ReadFile(file) 57 } 58 if err != nil { 59 return err 60 } 61 source = string(b) 62 } else if ctx.NArg() == 1 { 63 source = ctx.Args()[0] 64 } else { 65 return fmt.Errorf("need record as argument") 66 } 67 68 r, err := parseRecord(source) 69 if err != nil { 70 return fmt.Errorf("INVALID: %v", err) 71 } 72 fmt.Print(dumpRecord(r)) 73 return nil 74 } 75 76 // dumpRecord creates a human-readable description of the given node record. 77 func dumpRecord(r *enr.Record) string { 78 out := new(bytes.Buffer) 79 if n, err := enode.New(enode.ValidSchemes, r); err != nil { 80 fmt.Fprintf(out, "INVALID: %v\n", err) 81 } else { 82 fmt.Fprintf(out, "Node ID: %v\n", n.ID()) 83 } 84 kv := r.AppendElements(nil)[1:] 85 fmt.Fprintf(out, "Record has sequence number %d and %d key/value pairs.\n", r.Seq(), len(kv)/2) 86 fmt.Fprint(out, dumpRecordKV(kv, 2)) 87 return out.String() 88 } 89 90 func dumpRecordKV(kv []interface{}, indent int) string { 91 // Determine the longest key name for alignment. 92 var out string 93 var longestKey = 0 94 for i := 0; i < len(kv); i += 2 { 95 key := kv[i].(string) 96 if len(key) > longestKey { 97 longestKey = len(key) 98 } 99 } 100 // Print the keys, invoking formatters for known keys. 101 for i := 0; i < len(kv); i += 2 { 102 key := kv[i].(string) 103 val := kv[i+1].(rlp.RawValue) 104 pad := longestKey - len(key) 105 out += strings.Repeat(" ", indent) + strconv.Quote(key) + strings.Repeat(" ", pad+1) 106 formatter := attrFormatters[key] 107 if formatter == nil { 108 formatter = formatAttrRaw 109 } 110 fmtval, ok := formatter(val) 111 if ok { 112 out += fmtval + "\n" 113 } else { 114 out += hex.EncodeToString(val) + " (!)\n" 115 } 116 } 117 return out 118 } 119 120 // parseNode parses a node record and verifies its signature. 121 func parseNode(source string) (*enode.Node, error) { 122 if strings.HasPrefix(source, "enode://") { 123 return enode.ParseV4(source) 124 } 125 r, err := parseRecord(source) 126 if err != nil { 127 return nil, err 128 } 129 return enode.New(enode.ValidSchemes, r) 130 } 131 132 // parseRecord parses a node record from hex, base64, or raw binary input. 133 func parseRecord(source string) (*enr.Record, error) { 134 bin := []byte(source) 135 if d, ok := decodeRecordHex(bytes.TrimSpace(bin)); ok { 136 bin = d 137 } else if d, ok := decodeRecordBase64(bytes.TrimSpace(bin)); ok { 138 bin = d 139 } 140 var r enr.Record 141 err := rlp.DecodeBytes(bin, &r) 142 return &r, err 143 } 144 145 func decodeRecordHex(b []byte) ([]byte, bool) { 146 if bytes.HasPrefix(b, []byte("0x")) { 147 b = b[2:] 148 } 149 dec := make([]byte, hex.DecodedLen(len(b))) 150 _, err := hex.Decode(dec, b) 151 return dec, err == nil 152 } 153 154 func decodeRecordBase64(b []byte) ([]byte, bool) { 155 if bytes.HasPrefix(b, []byte("enr:")) { 156 b = b[4:] 157 } 158 dec := make([]byte, base64.RawURLEncoding.DecodedLen(len(b))) 159 n, err := base64.RawURLEncoding.Decode(dec, b) 160 return dec[:n], err == nil 161 } 162 163 // attrFormatters contains formatting functions for well-known ENR keys. 164 var attrFormatters = map[string]func(rlp.RawValue) (string, bool){ 165 "id": formatAttrString, 166 "ip": formatAttrIP, 167 "ip6": formatAttrIP, 168 "tcp": formatAttrUint, 169 "tcp6": formatAttrUint, 170 "udp": formatAttrUint, 171 "udp6": formatAttrUint, 172 } 173 174 func formatAttrRaw(v rlp.RawValue) (string, bool) { 175 s := hex.EncodeToString(v) 176 return s, true 177 } 178 179 func formatAttrString(v rlp.RawValue) (string, bool) { 180 content, _, err := rlp.SplitString(v) 181 return strconv.Quote(string(content)), err == nil 182 } 183 184 func formatAttrIP(v rlp.RawValue) (string, bool) { 185 content, _, err := rlp.SplitString(v) 186 if err != nil || len(content) != 4 && len(content) != 6 { 187 return "", false 188 } 189 return net.IP(content).String(), true 190 } 191 192 func formatAttrUint(v rlp.RawValue) (string, bool) { 193 var x uint64 194 if err := rlp.DecodeBytes(v, &x); err != nil { 195 return "", false 196 } 197 return strconv.FormatUint(x, 10), true 198 }