Browse Source

h2i: start of a working shell

w/ bmizerany
Brad Fitzpatrick 10 years ago
parent
commit
a63713941f
1 changed files with 84 additions and 17 deletions
  1. 84 17
      h2i/h2i.go

+ 84 - 17
h2i/h2i.go

@@ -9,17 +9,25 @@
 package main
 
 import (
+	"crypto/tls"
 	"flag"
 	"fmt"
 	"io"
 	"log"
 	"net"
 	"os"
-	"time"
+	"strings"
 
+	"github.com/bradfitz/http2"
 	"golang.org/x/crypto/ssh/terminal"
 )
 
+// Flags
+var (
+	flagNextProto = flag.String("nextproto", "h2,h2-14", "Comma-separated list of NPN/ALPN protocol names to negotiate.")
+	flagInsecure  = flag.Bool("insecure", false, "Whether to skip TLS cert validation")
+)
+
 func usage() {
 	fmt.Fprintf(os.Stderr, "Usage: h2i <hostname>\n\n")
 	flag.PrintDefaults()
@@ -34,24 +42,71 @@ func withPort(host string) string {
 	return host
 }
 
+// h2i is the app's state.
+type h2i struct {
+	host   string
+	tc     *tls.Conn
+	framer *http2.Framer
+	term   *terminal.Terminal
+}
+
 func main() {
 	flag.Usage = usage
 	flag.Parse()
 	if flag.NArg() != 1 {
 		usage()
 	}
+	log.SetFlags(0)
 
 	host := flag.Arg(0)
-	c, err := net.Dial("tcp", withPort(host))
+	app := &h2i{
+		host: host,
+	}
+	if err := app.Main(); err != nil {
+		fmt.Fprintf(os.Stderr, "%v\n", err)
+		os.Exit(1)
+	}
+}
+
+func (a *h2i) Main() error {
+	cfg := &tls.Config{
+		ServerName:         a.host,
+		NextProtos:         strings.Split(*flagNextProto, ","),
+		InsecureSkipVerify: *flagInsecure,
+	}
+
+	hostAndPort := withPort(a.host)
+	log.Printf("Connecting to %s ...", hostAndPort)
+	tc, err := tls.Dial("tcp", hostAndPort, cfg)
 	if err != nil {
-		log.Fatalf("Error dialing %s: %v", withPort(host), err)
+		return fmt.Errorf("Error dialing %s: %v", withPort(a.host), err)
+	}
+	log.Printf("Connected to %v", tc.RemoteAddr())
+	defer tc.Close()
+
+	if err := tc.Handshake(); err != nil {
+		return fmt.Errorf("TLS handshake: %v", err)
+	}
+	if !*flagInsecure {
+		if err := tc.VerifyHostname(a.host); err != nil {
+			return fmt.Errorf("VerifyHostname: %v", err)
+		}
+	}
+	state := tc.ConnectionState()
+	log.Printf("Negotiated protocol %q", state.NegotiatedProtocol)
+	if !state.NegotiatedProtocolIsMutual || state.NegotiatedProtocol == "" {
+		return fmt.Errorf("Could not negotiate protocol mutually")
+	}
+
+	if _, err := io.WriteString(tc, http2.ClientPreface); err != nil {
+		return err
 	}
-	defer c.Close()
-	log.Printf("Connected to %v", c.RemoteAddr())
+
+	a.framer = http2.NewFramer(tc, tc)
 
 	oldState, err := terminal.MakeRaw(0)
 	if err != nil {
-		panic(err)
+		return err
 	}
 	defer terminal.Restore(0, oldState)
 
@@ -60,25 +115,37 @@ func main() {
 		io.Writer
 	}{os.Stdin, os.Stdout}
 
-	term := terminal.NewTerminal(screen, "> ")
+	a.term = terminal.NewTerminal(screen, "> ")
 
-	go func() {
-		for t := range time.Tick(1 * time.Second) {
-			term.Write([]byte(t.String() + "\n"))
-		}
-	}()
+	errc := make(chan error, 2)
+	go func() { errc <- a.readFrames() }()
+	go func() { errc <- a.readConsole() }()
+	return <-errc
+}
 
+func (a *h2i) logf(format string, args ...interface{}) {
+	fmt.Fprintf(a.term, format+"\n", args...)
+}
+
+func (a *h2i) readConsole() error {
 	for {
-		line, err := term.ReadLine()
+		line, err := a.term.ReadLine()
 		if err != nil {
-			log.Fatal("ReadLine:", err)
+			return fmt.Errorf("terminal.ReadLine: %v", err)
 		}
 		if line == "q" || line == "quit" {
-			return
+			return nil
 		}
-		_, err = term.Write([]byte("boom - " + line + "\n"))
+		a.logf("Unknown command %q", line)
+	}
+}
+
+func (a *h2i) readFrames() error {
+	for {
+		f, err := a.framer.ReadFrame()
 		if err != nil {
-			log.Fatal("Write:", err)
+			return fmt.Errorf("ReadFrame: %v", err)
 		}
+		fmt.Fprintf(a.term, "%v\n", f)
 	}
 }