1
2
3
4
5
6
7
21 package main
22
23 import (
24 "bufio"
25 "bytes"
26 "crypto/tls"
27 "errors"
28 "flag"
29 "fmt"
30 "io"
31 "log"
32 "net"
33 "net/http"
34 "os"
35 "regexp"
36 "strconv"
37 "strings"
38
39 "golang.org/x/net/http2"
40 "golang.org/x/net/http2/hpack"
41 "golang.org/x/term"
42 )
43
44
45 var (
46 flagNextProto = flag.String("nextproto", "h2,h2-14", "Comma-separated list of NPN/ALPN protocol names to negotiate.")
47 flagInsecure = flag.Bool("insecure", false, "Whether to skip TLS cert validation")
48 flagSettings = flag.String("settings", "empty", "comma-separated list of KEY=value settings for the initial SETTINGS frame. The magic value 'empty' sends an empty initial settings frame, and the magic value 'omit' causes no initial settings frame to be sent.")
49 flagDial = flag.String("dial", "", "optional ip:port to dial, to connect to a host:port but use a different SNI name (including a SNI name without DNS)")
50 )
51
52 type command struct {
53 run func(*h2i, []string) error
54
55
56
57 complete func() []string
58 }
59
60 var commands = map[string]command{
61 "ping": {run: (*h2i).cmdPing},
62 "settings": {
63 run: (*h2i).cmdSettings,
64 complete: func() []string {
65 return []string{
66 "ACK",
67 http2.SettingHeaderTableSize.String(),
68 http2.SettingEnablePush.String(),
69 http2.SettingMaxConcurrentStreams.String(),
70 http2.SettingInitialWindowSize.String(),
71 http2.SettingMaxFrameSize.String(),
72 http2.SettingMaxHeaderListSize.String(),
73 }
74 },
75 },
76 "quit": {run: (*h2i).cmdQuit},
77 "headers": {run: (*h2i).cmdHeaders},
78 }
79
80 func usage() {
81 fmt.Fprintf(os.Stderr, "Usage: h2i <hostname>\n\n")
82 flag.PrintDefaults()
83 }
84
85
86 func withPort(host string) string {
87 if _, _, err := net.SplitHostPort(host); err != nil {
88 return net.JoinHostPort(host, "443")
89 }
90 return host
91 }
92
93
94 func withoutPort(addr string) string {
95 if h, _, err := net.SplitHostPort(addr); err == nil {
96 return h
97 }
98 return addr
99 }
100
101
102 type h2i struct {
103 host string
104 tc *tls.Conn
105 framer *http2.Framer
106 term *term.Terminal
107
108
109 streamID uint32
110 hbuf bytes.Buffer
111 henc *hpack.Encoder
112
113
114 peerSetting map[http2.SettingID]uint32
115 hdec *hpack.Decoder
116 }
117
118 func main() {
119 flag.Usage = usage
120 flag.Parse()
121 if flag.NArg() != 1 {
122 usage()
123 os.Exit(2)
124 }
125 log.SetFlags(0)
126
127 host := flag.Arg(0)
128 app := &h2i{
129 host: host,
130 peerSetting: make(map[http2.SettingID]uint32),
131 }
132 app.henc = hpack.NewEncoder(&app.hbuf)
133
134 if err := app.Main(); err != nil {
135 if app.term != nil {
136 app.logf("%v\n", err)
137 } else {
138 fmt.Fprintf(os.Stderr, "%v\n", err)
139 }
140 os.Exit(1)
141 }
142 fmt.Fprintf(os.Stdout, "\n")
143 }
144
145 func (app *h2i) Main() error {
146 cfg := &tls.Config{
147 ServerName: withoutPort(app.host),
148 NextProtos: strings.Split(*flagNextProto, ","),
149 InsecureSkipVerify: *flagInsecure,
150 }
151
152 hostAndPort := *flagDial
153 if hostAndPort == "" {
154 hostAndPort = withPort(app.host)
155 }
156 log.Printf("Connecting to %s ...", hostAndPort)
157 tc, err := tls.Dial("tcp", hostAndPort, cfg)
158 if err != nil {
159 return fmt.Errorf("Error dialing %s: %v", hostAndPort, err)
160 }
161 log.Printf("Connected to %v", tc.RemoteAddr())
162 defer tc.Close()
163
164 if err := tc.Handshake(); err != nil {
165 return fmt.Errorf("TLS handshake: %v", err)
166 }
167 if !*flagInsecure {
168 if err := tc.VerifyHostname(app.host); err != nil {
169 return fmt.Errorf("VerifyHostname: %v", err)
170 }
171 }
172 state := tc.ConnectionState()
173 log.Printf("Negotiated protocol %q", state.NegotiatedProtocol)
174 if !state.NegotiatedProtocolIsMutual || state.NegotiatedProtocol == "" {
175 return fmt.Errorf("Could not negotiate protocol mutually")
176 }
177
178 if _, err := io.WriteString(tc, http2.ClientPreface); err != nil {
179 return err
180 }
181
182 app.framer = http2.NewFramer(tc, tc)
183
184 oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
185 if err != nil {
186 return err
187 }
188 defer term.Restore(0, oldState)
189
190 var screen = struct {
191 io.Reader
192 io.Writer
193 }{os.Stdin, os.Stdout}
194
195 app.term = term.NewTerminal(screen, "h2i> ")
196 lastWord := regexp.MustCompile(`.+\W(\w+)$`)
197 app.term.AutoCompleteCallback = func(line string, pos int, key rune) (newLine string, newPos int, ok bool) {
198 if key != '\t' {
199 return
200 }
201 if pos != len(line) {
202
203 return
204 }
205
206 if !strings.Contains(line, " ") {
207 var name string
208 name, _, ok = lookupCommand(line)
209 if !ok {
210 return
211 }
212 return name, len(name), true
213 }
214 _, c, ok := lookupCommand(line[:strings.IndexByte(line, ' ')])
215 if !ok || c.complete == nil {
216 return
217 }
218 if strings.HasSuffix(line, " ") {
219 app.logf("%s", strings.Join(c.complete(), " "))
220 return line, pos, true
221 }
222 m := lastWord.FindStringSubmatch(line)
223 if m == nil {
224 return line, len(line), true
225 }
226 soFar := m[1]
227 var match []string
228 for _, cand := range c.complete() {
229 if len(soFar) > len(cand) || !strings.EqualFold(cand[:len(soFar)], soFar) {
230 continue
231 }
232 match = append(match, cand)
233 }
234 if len(match) == 0 {
235 return
236 }
237 if len(match) > 1 {
238
239 app.logf("%s", strings.Join(match, " "))
240 return line, pos, true
241 }
242 newLine = line[:len(line)-len(soFar)] + match[0]
243 return newLine, len(newLine), true
244
245 }
246
247 errc := make(chan error, 2)
248 go func() { errc <- app.readFrames() }()
249 go func() { errc <- app.readConsole() }()
250 return <-errc
251 }
252
253 func (app *h2i) logf(format string, args ...interface{}) {
254 fmt.Fprintf(app.term, format+"\r\n", args...)
255 }
256
257 func (app *h2i) readConsole() error {
258 if s := *flagSettings; s != "omit" {
259 var args []string
260 if s != "empty" {
261 args = strings.Split(s, ",")
262 }
263 _, c, ok := lookupCommand("settings")
264 if !ok {
265 panic("settings command not found")
266 }
267 c.run(app, args)
268 }
269
270 for {
271 line, err := app.term.ReadLine()
272 if err == io.EOF {
273 return nil
274 }
275 if err != nil {
276 return fmt.Errorf("term.ReadLine: %v", err)
277 }
278 f := strings.Fields(line)
279 if len(f) == 0 {
280 continue
281 }
282 cmd, args := f[0], f[1:]
283 if _, c, ok := lookupCommand(cmd); ok {
284 err = c.run(app, args)
285 } else {
286 app.logf("Unknown command %q", line)
287 }
288 if err == errExitApp {
289 return nil
290 }
291 if err != nil {
292 return err
293 }
294 }
295 }
296
297 func lookupCommand(prefix string) (name string, c command, ok bool) {
298 prefix = strings.ToLower(prefix)
299 if c, ok = commands[prefix]; ok {
300 return prefix, c, ok
301 }
302
303 for full, candidate := range commands {
304 if strings.HasPrefix(full, prefix) {
305 if c.run != nil {
306 return "", command{}, false
307 }
308 c = candidate
309 name = full
310 }
311 }
312 return name, c, c.run != nil
313 }
314
315 var errExitApp = errors.New("internal sentinel error value to quit the console reading loop")
316
317 func (a *h2i) cmdQuit(args []string) error {
318 if len(args) > 0 {
319 a.logf("the QUIT command takes no argument")
320 return nil
321 }
322 return errExitApp
323 }
324
325 func (a *h2i) cmdSettings(args []string) error {
326 if len(args) == 1 && strings.EqualFold(args[0], "ACK") {
327 return a.framer.WriteSettingsAck()
328 }
329 var settings []http2.Setting
330 for _, arg := range args {
331 if strings.EqualFold(arg, "ACK") {
332 a.logf("Error: ACK must be only argument with the SETTINGS command")
333 return nil
334 }
335 eq := strings.Index(arg, "=")
336 if eq == -1 {
337 a.logf("Error: invalid argument %q (expected SETTING_NAME=nnnn)", arg)
338 return nil
339 }
340 sid, ok := settingByName(arg[:eq])
341 if !ok {
342 a.logf("Error: unknown setting name %q", arg[:eq])
343 return nil
344 }
345 val, err := strconv.ParseUint(arg[eq+1:], 10, 32)
346 if err != nil {
347 a.logf("Error: invalid argument %q (expected SETTING_NAME=nnnn)", arg)
348 return nil
349 }
350 settings = append(settings, http2.Setting{
351 ID: sid,
352 Val: uint32(val),
353 })
354 }
355 a.logf("Sending: %v", settings)
356 return a.framer.WriteSettings(settings...)
357 }
358
359 func settingByName(name string) (http2.SettingID, bool) {
360 for _, sid := range [...]http2.SettingID{
361 http2.SettingHeaderTableSize,
362 http2.SettingEnablePush,
363 http2.SettingMaxConcurrentStreams,
364 http2.SettingInitialWindowSize,
365 http2.SettingMaxFrameSize,
366 http2.SettingMaxHeaderListSize,
367 } {
368 if strings.EqualFold(sid.String(), name) {
369 return sid, true
370 }
371 }
372 return 0, false
373 }
374
375 func (app *h2i) cmdPing(args []string) error {
376 if len(args) > 1 {
377 app.logf("invalid PING usage: only accepts 0 or 1 args")
378 return nil
379 }
380 var data [8]byte
381 if len(args) == 1 {
382 copy(data[:], args[0])
383 } else {
384 copy(data[:], "h2i_ping")
385 }
386 return app.framer.WritePing(false, data)
387 }
388
389 func (app *h2i) cmdHeaders(args []string) error {
390 if len(args) > 0 {
391 app.logf("Error: HEADERS doesn't yet take arguments.")
392
393
394 return nil
395 }
396 var h1req bytes.Buffer
397 app.term.SetPrompt("(as HTTP/1.1)> ")
398 defer app.term.SetPrompt("h2i> ")
399 for {
400 line, err := app.term.ReadLine()
401 if err != nil {
402 return err
403 }
404 h1req.WriteString(line)
405 h1req.WriteString("\r\n")
406 if line == "" {
407 break
408 }
409 }
410 req, err := http.ReadRequest(bufio.NewReader(&h1req))
411 if err != nil {
412 app.logf("Invalid HTTP/1.1 request: %v", err)
413 return nil
414 }
415 if app.streamID == 0 {
416 app.streamID = 1
417 } else {
418 app.streamID += 2
419 }
420 app.logf("Opening Stream-ID %d:", app.streamID)
421 hbf := app.encodeHeaders(req)
422 if len(hbf) > 16<<10 {
423 app.logf("TODO: h2i doesn't yet write CONTINUATION frames. Copy it from transport.go")
424 return nil
425 }
426 return app.framer.WriteHeaders(http2.HeadersFrameParam{
427 StreamID: app.streamID,
428 BlockFragment: hbf,
429 EndStream: req.Method == "GET" || req.Method == "HEAD",
430 EndHeaders: true,
431 })
432 }
433
434 func (app *h2i) readFrames() error {
435 for {
436 f, err := app.framer.ReadFrame()
437 if err != nil {
438 return fmt.Errorf("ReadFrame: %v", err)
439 }
440 app.logf("%v", f)
441 switch f := f.(type) {
442 case *http2.PingFrame:
443 app.logf(" Data = %q", f.Data)
444 case *http2.SettingsFrame:
445 f.ForeachSetting(func(s http2.Setting) error {
446 app.logf(" %v", s)
447 app.peerSetting[s.ID] = s.Val
448 return nil
449 })
450 case *http2.WindowUpdateFrame:
451 app.logf(" Window-Increment = %v", f.Increment)
452 case *http2.GoAwayFrame:
453 app.logf(" Last-Stream-ID = %d; Error-Code = %v (%d)", f.LastStreamID, f.ErrCode, f.ErrCode)
454 case *http2.DataFrame:
455 app.logf(" %q", f.Data())
456 case *http2.HeadersFrame:
457 if f.HasPriority() {
458 app.logf(" PRIORITY = %v", f.Priority)
459 }
460 if app.hdec == nil {
461
462
463
464 tableSize := uint32(4 << 10)
465 app.hdec = hpack.NewDecoder(tableSize, app.onNewHeaderField)
466 }
467 app.hdec.Write(f.HeaderBlockFragment())
468 case *http2.PushPromiseFrame:
469 if app.hdec == nil {
470
471
472
473 tableSize := uint32(4 << 10)
474 app.hdec = hpack.NewDecoder(tableSize, app.onNewHeaderField)
475 }
476 app.hdec.Write(f.HeaderBlockFragment())
477 }
478 }
479 }
480
481
482 func (app *h2i) onNewHeaderField(f hpack.HeaderField) {
483 if f.Sensitive {
484 app.logf(" %s = %q (SENSITIVE)", f.Name, f.Value)
485 }
486 app.logf(" %s = %q", f.Name, f.Value)
487 }
488
489 func (app *h2i) encodeHeaders(req *http.Request) []byte {
490 app.hbuf.Reset()
491
492
493 host := req.Host
494 if host == "" {
495 host = req.URL.Host
496 }
497
498 path := req.RequestURI
499 if path == "" {
500 path = "/"
501 }
502
503 app.writeHeader(":authority", host)
504 app.writeHeader(":method", req.Method)
505 app.writeHeader(":path", path)
506 app.writeHeader(":scheme", "https")
507
508 for k, vv := range req.Header {
509 lowKey := strings.ToLower(k)
510 if lowKey == "host" {
511 continue
512 }
513 for _, v := range vv {
514 app.writeHeader(lowKey, v)
515 }
516 }
517 return app.hbuf.Bytes()
518 }
519
520 func (app *h2i) writeHeader(name, value string) {
521 app.henc.WriteField(hpack.HeaderField{Name: name, Value: value})
522 app.logf(" %s = %s", name, value)
523 }
524
View as plain text