h2i.go 13 KB

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