diff --git a/dnstt-client/main.go b/dnstt-client/main.go index 2d46dc2..ad7e364 100644 --- a/dnstt-client/main.go +++ b/dnstt-client/main.go @@ -222,39 +222,62 @@ func run(pubkey []byte, domain dns.Name, localAddr *net.TCPAddr, remoteAddr net. func main() { var dohURL string var dotAddr string + var pubkeyFilename string + var pubkeyString string var udpAddr string flag.Usage = func() { - fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [-doh URL|-dot ADDR|-udp ADDR] PUBKEY DOMAIN LOCALADDR\n", os.Args[0]) + fmt.Fprintf(flag.CommandLine.Output(), `Usage: + %[1]s [-doh URL|-dot ADDR|-udp ADDR] -pubkey PUBKEY DOMAIN LOCALADDR + +Examples: + %[1]s -doh https://resolver.example/dns-query -pubkey 0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff t.example.com 127.0.0.1:7000 + %[1]s -dot resolver.example:853 -pubkey 0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff t.example.com 127.0.0.1:7000 + +`, os.Args[0]) flag.PrintDefaults() } flag.StringVar(&dohURL, "doh", "", "URL of DoH resolver") flag.StringVar(&dotAddr, "dot", "", "address of DoT resolver") + flag.StringVar(&pubkeyString, "pubkey", "", fmt.Sprintf("server public key (%d hex digits)", hex.EncodedLen(noise.KeyLen))) + flag.StringVar(&pubkeyString, "pubkey-file", "", "read server public key from file") flag.StringVar(&udpAddr, "udp", "", "address of UDP DNS resolver") flag.Parse() log.SetFlags(log.LstdFlags | log.LUTC) - if flag.NArg() != 3 { + if flag.NArg() != 2 { flag.Usage() os.Exit(1) } - pubkey, err := hex.DecodeString(flag.Arg(0)) + domain, err := dns.ParseName(flag.Arg(0)) if err != nil { - fmt.Fprintf(os.Stderr, "invalid pubkey %+q: %v\n", flag.Arg(0), err) + fmt.Fprintf(os.Stderr, "invalid domain %+q: %v\n", flag.Arg(0), err) os.Exit(1) } - domain, err := dns.ParseName(flag.Arg(1)) - if err != nil { - fmt.Fprintf(os.Stderr, "invalid domain %+q: %v\n", flag.Arg(1), err) - os.Exit(1) - } - localAddr, err := net.ResolveTCPAddr("tcp", flag.Arg(2)) + localAddr, err := net.ResolveTCPAddr("tcp", flag.Arg(1)) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } + var pubkey []byte + if pubkeyString != "" { + var err error + pubkey, err = hex.DecodeString(pubkeyString) + if err == nil && len(pubkey) != noise.KeyLen { + err = fmt.Errorf("length is %d, expected %d", len(pubkey), noise.KeyLen) + } + if err != nil { + fmt.Fprintf(os.Stderr, "pubkey format error: %v\n", err) + os.Exit(1) + } + } + if len(pubkey) == 0 { + fmt.Fprintf(os.Stderr, "the -pubkey option is required\n") + os.Exit(1) + } + // Iterate over the remote resolver address options and select one and // only one. var remoteAddr net.Addr diff --git a/dnstt-server/main.go b/dnstt-server/main.go index 01d2e51..4d48ff1 100644 --- a/dnstt-server/main.go +++ b/dnstt-server/main.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/base32" "encoding/binary" + "encoding/hex" "flag" "fmt" "io" @@ -490,13 +491,18 @@ func sendLoop(dnsConn net.PacketConn, ttConn *turbotunnel.QueuePacketConn, ch <- return nil } -func run(domain dns.Name, upstream net.Addr, udpAddr string) error { +func generateKeypair() error { privkey, pubkey, err := noise.GenerateKeypair() if err != nil { return err } - log.Printf("privkey %x", privkey) - log.Printf(" pubkey %x", pubkey) + fmt.Printf("privkey: %x\n", privkey) + fmt.Printf("pubkey: %x\n", pubkey) + return nil +} + +func run(privkey, pubkey []byte, domain dns.Name, upstream net.Addr, udpAddr string) error { + log.Printf("pubkey %x", pubkey) // Start up the virtual PacketConn for turbotunnel. ttConn := turbotunnel.NewQueuePacketConn(turbotunnel.DummyAddr{}, idleTimeout*2) @@ -535,34 +541,83 @@ func run(domain dns.Name, upstream net.Addr, udpAddr string) error { } func main() { + var genKey bool + var privkeyString string var udpAddr string flag.Usage = func() { - fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s -udp ADDR DOMAIN UPSTREAMADDR\n", os.Args[0]) + fmt.Fprintf(flag.CommandLine.Output(), `Usage: + %[1]s -gen-key + %[1]s -udp ADDR -privkey PRIVKEY DOMAIN UPSTREAMADDR + +Example: + %[1]s -gen-key + %[1]s -udp 127.0.0.1:5300 -privkey 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef server.key t.example.com 127.0.0.1:8000 + +`, os.Args[0]) flag.PrintDefaults() } + flag.BoolVar(&genKey, "gen-key", false, "generate a server keypair; print to stdout") + flag.StringVar(&privkeyString, "privkey", "", fmt.Sprintf("server private key (%d hex digits)", hex.EncodedLen(noise.KeyLen))) flag.StringVar(&udpAddr, "udp", "", "UDP address to listen on (required)") flag.Parse() log.SetFlags(log.LstdFlags | log.LUTC) - if flag.NArg() != 2 { - flag.Usage() - os.Exit(1) - } - domain, err := dns.ParseName(flag.Arg(0)) - if err != nil { - fmt.Fprintf(os.Stderr, "invalid domain %+q: %v\n", flag.Arg(0), err) - os.Exit(1) - } - upstream, err := net.ResolveTCPAddr("tcp", flag.Arg(1)) - if err != nil { - fmt.Fprintf(os.Stderr, "cannot resolve %+q: %v\n", flag.Arg(1), err) - os.Exit(1) - } + if genKey { + // -gen-key mode. + if flag.NArg() != 0 || privkeyString != "" || udpAddr != "" { + flag.Usage() + os.Exit(1) + } + if err := generateKeypair(); err != nil { + fmt.Fprintf(os.Stderr, "cannot generate keypair: %v\n", err) + os.Exit(1) + } + } else { + // Ordinary server mode. + if flag.NArg() != 2 { + flag.Usage() + os.Exit(1) + } + domain, err := dns.ParseName(flag.Arg(0)) + if err != nil { + fmt.Fprintf(os.Stderr, "invalid domain %+q: %v\n", flag.Arg(0), err) + os.Exit(1) + } + upstream, err := net.ResolveTCPAddr("tcp", flag.Arg(1)) + if err != nil { + fmt.Fprintf(os.Stderr, "cannot resolve %+q: %v\n", flag.Arg(1), err) + os.Exit(1) + } - err = run(domain, upstream, udpAddr) - if err != nil { - log.Fatal(err) + var privkey []byte + if privkeyString != "" { + var err error + privkey, err = hex.DecodeString(privkeyString) + if err == nil && len(privkey) != noise.KeyLen { + err = fmt.Errorf("length is %d, expected %d", len(privkey), noise.KeyLen) + } + if err != nil { + fmt.Fprintf(os.Stderr, "privkey format error: %v\n", err) + os.Exit(1) + } + } + if len(privkey) == 0 { + log.Println("generating a temporary one-time keypair") + log.Println("use the -privkey option for a persistent server keypair") + var err error + privkey, _, err = noise.GenerateKeypair() + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + } + pubkey := noise.PubkeyFromPrivkey(privkey) + + err = run(privkey, pubkey, domain, upstream, udpAddr) + if err != nil { + log.Fatal(err) + } } } diff --git a/noise/noise.go b/noise/noise.go index 9347f61..5757655 100644 --- a/noise/noise.go +++ b/noise/noise.go @@ -11,6 +11,9 @@ import ( "github.com/flynn/noise" ) +// The length of public and private keys as returned by GenerateKeypair. +const KeyLen = 32 + func readMessage(r io.Reader) ([]byte, error) { var length uint16 err := binary.Read(r, binary.BigEndian, &length)