OSDN Git Service

Hulk did something
[bytom/vapor.git] / vendor / golang.org / x / crypto / ssh / session_test.go
1 // Copyright 2011 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
5 package ssh
6
7 // Session tests.
8
9 import (
10         "bytes"
11         crypto_rand "crypto/rand"
12         "errors"
13         "io"
14         "io/ioutil"
15         "math/rand"
16         "net"
17         "testing"
18
19         "golang.org/x/crypto/ssh/terminal"
20 )
21
22 type serverType func(Channel, <-chan *Request, *testing.T)
23
24 // dial constructs a new test server and returns a *ClientConn.
25 func dial(handler serverType, t *testing.T) *Client {
26         c1, c2, err := netPipe()
27         if err != nil {
28                 t.Fatalf("netPipe: %v", err)
29         }
30
31         go func() {
32                 defer c1.Close()
33                 conf := ServerConfig{
34                         NoClientAuth: true,
35                 }
36                 conf.AddHostKey(testSigners["rsa"])
37
38                 _, chans, reqs, err := NewServerConn(c1, &conf)
39                 if err != nil {
40                         t.Fatalf("Unable to handshake: %v", err)
41                 }
42                 go DiscardRequests(reqs)
43
44                 for newCh := range chans {
45                         if newCh.ChannelType() != "session" {
46                                 newCh.Reject(UnknownChannelType, "unknown channel type")
47                                 continue
48                         }
49
50                         ch, inReqs, err := newCh.Accept()
51                         if err != nil {
52                                 t.Errorf("Accept: %v", err)
53                                 continue
54                         }
55                         go func() {
56                                 handler(ch, inReqs, t)
57                         }()
58                 }
59         }()
60
61         config := &ClientConfig{
62                 User:            "testuser",
63                 HostKeyCallback: InsecureIgnoreHostKey(),
64         }
65
66         conn, chans, reqs, err := NewClientConn(c2, "", config)
67         if err != nil {
68                 t.Fatalf("unable to dial remote side: %v", err)
69         }
70
71         return NewClient(conn, chans, reqs)
72 }
73
74 // Test a simple string is returned to session.Stdout.
75 func TestSessionShell(t *testing.T) {
76         conn := dial(shellHandler, t)
77         defer conn.Close()
78         session, err := conn.NewSession()
79         if err != nil {
80                 t.Fatalf("Unable to request new session: %v", err)
81         }
82         defer session.Close()
83         stdout := new(bytes.Buffer)
84         session.Stdout = stdout
85         if err := session.Shell(); err != nil {
86                 t.Fatalf("Unable to execute command: %s", err)
87         }
88         if err := session.Wait(); err != nil {
89                 t.Fatalf("Remote command did not exit cleanly: %v", err)
90         }
91         actual := stdout.String()
92         if actual != "golang" {
93                 t.Fatalf("Remote shell did not return expected string: expected=golang, actual=%s", actual)
94         }
95 }
96
97 // TODO(dfc) add support for Std{in,err}Pipe when the Server supports it.
98
99 // Test a simple string is returned via StdoutPipe.
100 func TestSessionStdoutPipe(t *testing.T) {
101         conn := dial(shellHandler, t)
102         defer conn.Close()
103         session, err := conn.NewSession()
104         if err != nil {
105                 t.Fatalf("Unable to request new session: %v", err)
106         }
107         defer session.Close()
108         stdout, err := session.StdoutPipe()
109         if err != nil {
110                 t.Fatalf("Unable to request StdoutPipe(): %v", err)
111         }
112         var buf bytes.Buffer
113         if err := session.Shell(); err != nil {
114                 t.Fatalf("Unable to execute command: %v", err)
115         }
116         done := make(chan bool, 1)
117         go func() {
118                 if _, err := io.Copy(&buf, stdout); err != nil {
119                         t.Errorf("Copy of stdout failed: %v", err)
120                 }
121                 done <- true
122         }()
123         if err := session.Wait(); err != nil {
124                 t.Fatalf("Remote command did not exit cleanly: %v", err)
125         }
126         <-done
127         actual := buf.String()
128         if actual != "golang" {
129                 t.Fatalf("Remote shell did not return expected string: expected=golang, actual=%s", actual)
130         }
131 }
132
133 // Test that a simple string is returned via the Output helper,
134 // and that stderr is discarded.
135 func TestSessionOutput(t *testing.T) {
136         conn := dial(fixedOutputHandler, t)
137         defer conn.Close()
138         session, err := conn.NewSession()
139         if err != nil {
140                 t.Fatalf("Unable to request new session: %v", err)
141         }
142         defer session.Close()
143
144         buf, err := session.Output("") // cmd is ignored by fixedOutputHandler
145         if err != nil {
146                 t.Error("Remote command did not exit cleanly:", err)
147         }
148         w := "this-is-stdout."
149         g := string(buf)
150         if g != w {
151                 t.Error("Remote command did not return expected string:")
152                 t.Logf("want %q", w)
153                 t.Logf("got  %q", g)
154         }
155 }
156
157 // Test that both stdout and stderr are returned
158 // via the CombinedOutput helper.
159 func TestSessionCombinedOutput(t *testing.T) {
160         conn := dial(fixedOutputHandler, t)
161         defer conn.Close()
162         session, err := conn.NewSession()
163         if err != nil {
164                 t.Fatalf("Unable to request new session: %v", err)
165         }
166         defer session.Close()
167
168         buf, err := session.CombinedOutput("") // cmd is ignored by fixedOutputHandler
169         if err != nil {
170                 t.Error("Remote command did not exit cleanly:", err)
171         }
172         const stdout = "this-is-stdout."
173         const stderr = "this-is-stderr."
174         g := string(buf)
175         if g != stdout+stderr && g != stderr+stdout {
176                 t.Error("Remote command did not return expected string:")
177                 t.Logf("want %q, or %q", stdout+stderr, stderr+stdout)
178                 t.Logf("got  %q", g)
179         }
180 }
181
182 // Test non-0 exit status is returned correctly.
183 func TestExitStatusNonZero(t *testing.T) {
184         conn := dial(exitStatusNonZeroHandler, t)
185         defer conn.Close()
186         session, err := conn.NewSession()
187         if err != nil {
188                 t.Fatalf("Unable to request new session: %v", err)
189         }
190         defer session.Close()
191         if err := session.Shell(); err != nil {
192                 t.Fatalf("Unable to execute command: %v", err)
193         }
194         err = session.Wait()
195         if err == nil {
196                 t.Fatalf("expected command to fail but it didn't")
197         }
198         e, ok := err.(*ExitError)
199         if !ok {
200                 t.Fatalf("expected *ExitError but got %T", err)
201         }
202         if e.ExitStatus() != 15 {
203                 t.Fatalf("expected command to exit with 15 but got %v", e.ExitStatus())
204         }
205 }
206
207 // Test 0 exit status is returned correctly.
208 func TestExitStatusZero(t *testing.T) {
209         conn := dial(exitStatusZeroHandler, t)
210         defer conn.Close()
211         session, err := conn.NewSession()
212         if err != nil {
213                 t.Fatalf("Unable to request new session: %v", err)
214         }
215         defer session.Close()
216
217         if err := session.Shell(); err != nil {
218                 t.Fatalf("Unable to execute command: %v", err)
219         }
220         err = session.Wait()
221         if err != nil {
222                 t.Fatalf("expected nil but got %v", err)
223         }
224 }
225
226 // Test exit signal and status are both returned correctly.
227 func TestExitSignalAndStatus(t *testing.T) {
228         conn := dial(exitSignalAndStatusHandler, t)
229         defer conn.Close()
230         session, err := conn.NewSession()
231         if err != nil {
232                 t.Fatalf("Unable to request new session: %v", err)
233         }
234         defer session.Close()
235         if err := session.Shell(); err != nil {
236                 t.Fatalf("Unable to execute command: %v", err)
237         }
238         err = session.Wait()
239         if err == nil {
240                 t.Fatalf("expected command to fail but it didn't")
241         }
242         e, ok := err.(*ExitError)
243         if !ok {
244                 t.Fatalf("expected *ExitError but got %T", err)
245         }
246         if e.Signal() != "TERM" || e.ExitStatus() != 15 {
247                 t.Fatalf("expected command to exit with signal TERM and status 15 but got signal %s and status %v", e.Signal(), e.ExitStatus())
248         }
249 }
250
251 // Test exit signal and status are both returned correctly.
252 func TestKnownExitSignalOnly(t *testing.T) {
253         conn := dial(exitSignalHandler, t)
254         defer conn.Close()
255         session, err := conn.NewSession()
256         if err != nil {
257                 t.Fatalf("Unable to request new session: %v", err)
258         }
259         defer session.Close()
260         if err := session.Shell(); err != nil {
261                 t.Fatalf("Unable to execute command: %v", err)
262         }
263         err = session.Wait()
264         if err == nil {
265                 t.Fatalf("expected command to fail but it didn't")
266         }
267         e, ok := err.(*ExitError)
268         if !ok {
269                 t.Fatalf("expected *ExitError but got %T", err)
270         }
271         if e.Signal() != "TERM" || e.ExitStatus() != 143 {
272                 t.Fatalf("expected command to exit with signal TERM and status 143 but got signal %s and status %v", e.Signal(), e.ExitStatus())
273         }
274 }
275
276 // Test exit signal and status are both returned correctly.
277 func TestUnknownExitSignal(t *testing.T) {
278         conn := dial(exitSignalUnknownHandler, t)
279         defer conn.Close()
280         session, err := conn.NewSession()
281         if err != nil {
282                 t.Fatalf("Unable to request new session: %v", err)
283         }
284         defer session.Close()
285         if err := session.Shell(); err != nil {
286                 t.Fatalf("Unable to execute command: %v", err)
287         }
288         err = session.Wait()
289         if err == nil {
290                 t.Fatalf("expected command to fail but it didn't")
291         }
292         e, ok := err.(*ExitError)
293         if !ok {
294                 t.Fatalf("expected *ExitError but got %T", err)
295         }
296         if e.Signal() != "SYS" || e.ExitStatus() != 128 {
297                 t.Fatalf("expected command to exit with signal SYS and status 128 but got signal %s and status %v", e.Signal(), e.ExitStatus())
298         }
299 }
300
301 func TestExitWithoutStatusOrSignal(t *testing.T) {
302         conn := dial(exitWithoutSignalOrStatus, t)
303         defer conn.Close()
304         session, err := conn.NewSession()
305         if err != nil {
306                 t.Fatalf("Unable to request new session: %v", err)
307         }
308         defer session.Close()
309         if err := session.Shell(); err != nil {
310                 t.Fatalf("Unable to execute command: %v", err)
311         }
312         err = session.Wait()
313         if err == nil {
314                 t.Fatalf("expected command to fail but it didn't")
315         }
316         if _, ok := err.(*ExitMissingError); !ok {
317                 t.Fatalf("got %T want *ExitMissingError", err)
318         }
319 }
320
321 // windowTestBytes is the number of bytes that we'll send to the SSH server.
322 const windowTestBytes = 16000 * 200
323
324 // TestServerWindow writes random data to the server. The server is expected to echo
325 // the same data back, which is compared against the original.
326 func TestServerWindow(t *testing.T) {
327         origBuf := bytes.NewBuffer(make([]byte, 0, windowTestBytes))
328         io.CopyN(origBuf, crypto_rand.Reader, windowTestBytes)
329         origBytes := origBuf.Bytes()
330
331         conn := dial(echoHandler, t)
332         defer conn.Close()
333         session, err := conn.NewSession()
334         if err != nil {
335                 t.Fatal(err)
336         }
337         defer session.Close()
338         result := make(chan []byte)
339
340         go func() {
341                 defer close(result)
342                 echoedBuf := bytes.NewBuffer(make([]byte, 0, windowTestBytes))
343                 serverStdout, err := session.StdoutPipe()
344                 if err != nil {
345                         t.Errorf("StdoutPipe failed: %v", err)
346                         return
347                 }
348                 n, err := copyNRandomly("stdout", echoedBuf, serverStdout, windowTestBytes)
349                 if err != nil && err != io.EOF {
350                         t.Errorf("Read only %d bytes from server, expected %d: %v", n, windowTestBytes, err)
351                 }
352                 result <- echoedBuf.Bytes()
353         }()
354
355         serverStdin, err := session.StdinPipe()
356         if err != nil {
357                 t.Fatalf("StdinPipe failed: %v", err)
358         }
359         written, err := copyNRandomly("stdin", serverStdin, origBuf, windowTestBytes)
360         if err != nil {
361                 t.Fatalf("failed to copy origBuf to serverStdin: %v", err)
362         }
363         if written != windowTestBytes {
364                 t.Fatalf("Wrote only %d of %d bytes to server", written, windowTestBytes)
365         }
366
367         echoedBytes := <-result
368
369         if !bytes.Equal(origBytes, echoedBytes) {
370                 t.Fatalf("Echoed buffer differed from original, orig %d, echoed %d", len(origBytes), len(echoedBytes))
371         }
372 }
373
374 // Verify the client can handle a keepalive packet from the server.
375 func TestClientHandlesKeepalives(t *testing.T) {
376         conn := dial(channelKeepaliveSender, t)
377         defer conn.Close()
378         session, err := conn.NewSession()
379         if err != nil {
380                 t.Fatal(err)
381         }
382         defer session.Close()
383         if err := session.Shell(); err != nil {
384                 t.Fatalf("Unable to execute command: %v", err)
385         }
386         err = session.Wait()
387         if err != nil {
388                 t.Fatalf("expected nil but got: %v", err)
389         }
390 }
391
392 type exitStatusMsg struct {
393         Status uint32
394 }
395
396 type exitSignalMsg struct {
397         Signal     string
398         CoreDumped bool
399         Errmsg     string
400         Lang       string
401 }
402
403 func handleTerminalRequests(in <-chan *Request) {
404         for req := range in {
405                 ok := false
406                 switch req.Type {
407                 case "shell":
408                         ok = true
409                         if len(req.Payload) > 0 {
410                                 // We don't accept any commands, only the default shell.
411                                 ok = false
412                         }
413                 case "env":
414                         ok = true
415                 }
416                 req.Reply(ok, nil)
417         }
418 }
419
420 func newServerShell(ch Channel, in <-chan *Request, prompt string) *terminal.Terminal {
421         term := terminal.NewTerminal(ch, prompt)
422         go handleTerminalRequests(in)
423         return term
424 }
425
426 func exitStatusZeroHandler(ch Channel, in <-chan *Request, t *testing.T) {
427         defer ch.Close()
428         // this string is returned to stdout
429         shell := newServerShell(ch, in, "> ")
430         readLine(shell, t)
431         sendStatus(0, ch, t)
432 }
433
434 func exitStatusNonZeroHandler(ch Channel, in <-chan *Request, t *testing.T) {
435         defer ch.Close()
436         shell := newServerShell(ch, in, "> ")
437         readLine(shell, t)
438         sendStatus(15, ch, t)
439 }
440
441 func exitSignalAndStatusHandler(ch Channel, in <-chan *Request, t *testing.T) {
442         defer ch.Close()
443         shell := newServerShell(ch, in, "> ")
444         readLine(shell, t)
445         sendStatus(15, ch, t)
446         sendSignal("TERM", ch, t)
447 }
448
449 func exitSignalHandler(ch Channel, in <-chan *Request, t *testing.T) {
450         defer ch.Close()
451         shell := newServerShell(ch, in, "> ")
452         readLine(shell, t)
453         sendSignal("TERM", ch, t)
454 }
455
456 func exitSignalUnknownHandler(ch Channel, in <-chan *Request, t *testing.T) {
457         defer ch.Close()
458         shell := newServerShell(ch, in, "> ")
459         readLine(shell, t)
460         sendSignal("SYS", ch, t)
461 }
462
463 func exitWithoutSignalOrStatus(ch Channel, in <-chan *Request, t *testing.T) {
464         defer ch.Close()
465         shell := newServerShell(ch, in, "> ")
466         readLine(shell, t)
467 }
468
469 func shellHandler(ch Channel, in <-chan *Request, t *testing.T) {
470         defer ch.Close()
471         // this string is returned to stdout
472         shell := newServerShell(ch, in, "golang")
473         readLine(shell, t)
474         sendStatus(0, ch, t)
475 }
476
477 // Ignores the command, writes fixed strings to stderr and stdout.
478 // Strings are "this-is-stdout." and "this-is-stderr.".
479 func fixedOutputHandler(ch Channel, in <-chan *Request, t *testing.T) {
480         defer ch.Close()
481         _, err := ch.Read(nil)
482
483         req, ok := <-in
484         if !ok {
485                 t.Fatalf("error: expected channel request, got: %#v", err)
486                 return
487         }
488
489         // ignore request, always send some text
490         req.Reply(true, nil)
491
492         _, err = io.WriteString(ch, "this-is-stdout.")
493         if err != nil {
494                 t.Fatalf("error writing on server: %v", err)
495         }
496         _, err = io.WriteString(ch.Stderr(), "this-is-stderr.")
497         if err != nil {
498                 t.Fatalf("error writing on server: %v", err)
499         }
500         sendStatus(0, ch, t)
501 }
502
503 func readLine(shell *terminal.Terminal, t *testing.T) {
504         if _, err := shell.ReadLine(); err != nil && err != io.EOF {
505                 t.Errorf("unable to read line: %v", err)
506         }
507 }
508
509 func sendStatus(status uint32, ch Channel, t *testing.T) {
510         msg := exitStatusMsg{
511                 Status: status,
512         }
513         if _, err := ch.SendRequest("exit-status", false, Marshal(&msg)); err != nil {
514                 t.Errorf("unable to send status: %v", err)
515         }
516 }
517
518 func sendSignal(signal string, ch Channel, t *testing.T) {
519         sig := exitSignalMsg{
520                 Signal:     signal,
521                 CoreDumped: false,
522                 Errmsg:     "Process terminated",
523                 Lang:       "en-GB-oed",
524         }
525         if _, err := ch.SendRequest("exit-signal", false, Marshal(&sig)); err != nil {
526                 t.Errorf("unable to send signal: %v", err)
527         }
528 }
529
530 func discardHandler(ch Channel, t *testing.T) {
531         defer ch.Close()
532         io.Copy(ioutil.Discard, ch)
533 }
534
535 func echoHandler(ch Channel, in <-chan *Request, t *testing.T) {
536         defer ch.Close()
537         if n, err := copyNRandomly("echohandler", ch, ch, windowTestBytes); err != nil {
538                 t.Errorf("short write, wrote %d, expected %d: %v ", n, windowTestBytes, err)
539         }
540 }
541
542 // copyNRandomly copies n bytes from src to dst. It uses a variable, and random,
543 // buffer size to exercise more code paths.
544 func copyNRandomly(title string, dst io.Writer, src io.Reader, n int) (int, error) {
545         var (
546                 buf       = make([]byte, 32*1024)
547                 written   int
548                 remaining = n
549         )
550         for remaining > 0 {
551                 l := rand.Intn(1 << 15)
552                 if remaining < l {
553                         l = remaining
554                 }
555                 nr, er := src.Read(buf[:l])
556                 nw, ew := dst.Write(buf[:nr])
557                 remaining -= nw
558                 written += nw
559                 if ew != nil {
560                         return written, ew
561                 }
562                 if nr != nw {
563                         return written, io.ErrShortWrite
564                 }
565                 if er != nil && er != io.EOF {
566                         return written, er
567                 }
568         }
569         return written, nil
570 }
571
572 func channelKeepaliveSender(ch Channel, in <-chan *Request, t *testing.T) {
573         defer ch.Close()
574         shell := newServerShell(ch, in, "> ")
575         readLine(shell, t)
576         if _, err := ch.SendRequest("keepalive@openssh.com", true, nil); err != nil {
577                 t.Errorf("unable to send channel keepalive request: %v", err)
578         }
579         sendStatus(0, ch, t)
580 }
581
582 func TestClientWriteEOF(t *testing.T) {
583         conn := dial(simpleEchoHandler, t)
584         defer conn.Close()
585
586         session, err := conn.NewSession()
587         if err != nil {
588                 t.Fatal(err)
589         }
590         defer session.Close()
591         stdin, err := session.StdinPipe()
592         if err != nil {
593                 t.Fatalf("StdinPipe failed: %v", err)
594         }
595         stdout, err := session.StdoutPipe()
596         if err != nil {
597                 t.Fatalf("StdoutPipe failed: %v", err)
598         }
599
600         data := []byte(`0000`)
601         _, err = stdin.Write(data)
602         if err != nil {
603                 t.Fatalf("Write failed: %v", err)
604         }
605         stdin.Close()
606
607         res, err := ioutil.ReadAll(stdout)
608         if err != nil {
609                 t.Fatalf("Read failed: %v", err)
610         }
611
612         if !bytes.Equal(data, res) {
613                 t.Fatalf("Read differed from write, wrote: %v, read: %v", data, res)
614         }
615 }
616
617 func simpleEchoHandler(ch Channel, in <-chan *Request, t *testing.T) {
618         defer ch.Close()
619         data, err := ioutil.ReadAll(ch)
620         if err != nil {
621                 t.Errorf("handler read error: %v", err)
622         }
623         _, err = ch.Write(data)
624         if err != nil {
625                 t.Errorf("handler write error: %v", err)
626         }
627 }
628
629 func TestSessionID(t *testing.T) {
630         c1, c2, err := netPipe()
631         if err != nil {
632                 t.Fatalf("netPipe: %v", err)
633         }
634         defer c1.Close()
635         defer c2.Close()
636
637         serverID := make(chan []byte, 1)
638         clientID := make(chan []byte, 1)
639
640         serverConf := &ServerConfig{
641                 NoClientAuth: true,
642         }
643         serverConf.AddHostKey(testSigners["ecdsa"])
644         clientConf := &ClientConfig{
645                 HostKeyCallback: InsecureIgnoreHostKey(),
646                 User:            "user",
647         }
648
649         go func() {
650                 conn, chans, reqs, err := NewServerConn(c1, serverConf)
651                 if err != nil {
652                         t.Fatalf("server handshake: %v", err)
653                 }
654                 serverID <- conn.SessionID()
655                 go DiscardRequests(reqs)
656                 for ch := range chans {
657                         ch.Reject(Prohibited, "")
658                 }
659         }()
660
661         go func() {
662                 conn, chans, reqs, err := NewClientConn(c2, "", clientConf)
663                 if err != nil {
664                         t.Fatalf("client handshake: %v", err)
665                 }
666                 clientID <- conn.SessionID()
667                 go DiscardRequests(reqs)
668                 for ch := range chans {
669                         ch.Reject(Prohibited, "")
670                 }
671         }()
672
673         s := <-serverID
674         c := <-clientID
675         if bytes.Compare(s, c) != 0 {
676                 t.Errorf("server session ID (%x) != client session ID (%x)", s, c)
677         } else if len(s) == 0 {
678                 t.Errorf("client and server SessionID were empty.")
679         }
680 }
681
682 type noReadConn struct {
683         readSeen bool
684         net.Conn
685 }
686
687 func (c *noReadConn) Close() error {
688         return nil
689 }
690
691 func (c *noReadConn) Read(b []byte) (int, error) {
692         c.readSeen = true
693         return 0, errors.New("noReadConn error")
694 }
695
696 func TestInvalidServerConfiguration(t *testing.T) {
697         c1, c2, err := netPipe()
698         if err != nil {
699                 t.Fatalf("netPipe: %v", err)
700         }
701         defer c1.Close()
702         defer c2.Close()
703
704         serveConn := noReadConn{Conn: c1}
705         serverConf := &ServerConfig{}
706
707         NewServerConn(&serveConn, serverConf)
708         if serveConn.readSeen {
709                 t.Fatalf("NewServerConn attempted to Read() from Conn while configuration is missing host key")
710         }
711
712         serverConf.AddHostKey(testSigners["ecdsa"])
713
714         NewServerConn(&serveConn, serverConf)
715         if serveConn.readSeen {
716                 t.Fatalf("NewServerConn attempted to Read() from Conn while configuration is missing authentication method")
717         }
718 }
719
720 func TestHostKeyAlgorithms(t *testing.T) {
721         serverConf := &ServerConfig{
722                 NoClientAuth: true,
723         }
724         serverConf.AddHostKey(testSigners["rsa"])
725         serverConf.AddHostKey(testSigners["ecdsa"])
726
727         connect := func(clientConf *ClientConfig, want string) {
728                 var alg string
729                 clientConf.HostKeyCallback = func(h string, a net.Addr, key PublicKey) error {
730                         alg = key.Type()
731                         return nil
732                 }
733                 c1, c2, err := netPipe()
734                 if err != nil {
735                         t.Fatalf("netPipe: %v", err)
736                 }
737                 defer c1.Close()
738                 defer c2.Close()
739
740                 go NewServerConn(c1, serverConf)
741                 _, _, _, err = NewClientConn(c2, "", clientConf)
742                 if err != nil {
743                         t.Fatalf("NewClientConn: %v", err)
744                 }
745                 if alg != want {
746                         t.Errorf("selected key algorithm %s, want %s", alg, want)
747                 }
748         }
749
750         // By default, we get the preferred algorithm, which is ECDSA 256.
751
752         clientConf := &ClientConfig{
753                 HostKeyCallback: InsecureIgnoreHostKey(),
754         }
755         connect(clientConf, KeyAlgoECDSA256)
756
757         // Client asks for RSA explicitly.
758         clientConf.HostKeyAlgorithms = []string{KeyAlgoRSA}
759         connect(clientConf, KeyAlgoRSA)
760
761         c1, c2, err := netPipe()
762         if err != nil {
763                 t.Fatalf("netPipe: %v", err)
764         }
765         defer c1.Close()
766         defer c2.Close()
767
768         go NewServerConn(c1, serverConf)
769         clientConf.HostKeyAlgorithms = []string{"nonexistent-hostkey-algo"}
770         _, _, _, err = NewClientConn(c2, "", clientConf)
771         if err == nil {
772                 t.Fatal("succeeded connecting with unknown hostkey algorithm")
773         }
774 }