func backchannel(ctx context.Context, conn *net.Conn) error { defer trace.End(trace.Begin("establish tether backchannel")) // HACK: currently RawConn dosn't implement timeout so throttle the spinning // it does implement the Timeout methods so the intermediary code can be written // to support it, but they are stub implementation in rawconn impl. // This needs to tick *faster* than the ticker in connection.go on the // portlayer side. The PL sends the first syn and if this isn't waiting, // alignment will take a few rounds (or it may never happen). ticker := time.NewTicker(10 * time.Millisecond) for { select { case <-ticker.C: err := serial.HandshakeServer(ctx, *conn) if err == nil { return nil } case <-ctx.Done(): (*conn).Close() ticker.Stop() return ctx.Err() } } }
// Start the server, make 200 client connections, test they connect, then Stop. func TestAttachStartStop(t *testing.T) { log.SetLevel(log.InfoLevel) s := NewAttachServer("", -1) wg := &sync.WaitGroup{} dial := func() { wg.Add(1) c, err := net.Dial("tcp", s.l.Addr().String()) assert.NoError(t, err) assert.NotNil(t, c) buf := make([]byte, 1) c.Read(buf) if !assert.Error(t, serial.HandshakeServer(context.Background(), c)) { return } if !assert.NoError(t, serial.HandshakeServer(context.Background(), c)) { return } wg.Done() } assert.NoError(t, s.Start()) for i := 0; i < 200; i++ { go dial() } done := make(chan bool) go func() { wg.Wait() close(done) }() select { case <-done: case <-time.After(10 * time.Second): t.Fail() } assert.NoError(t, s.Stop()) _, err := net.Dial("tcp", s.l.Addr().String()) assert.Error(t, err) }
func backchannel(ctx context.Context, conn net.Conn) error { defer trace.End(trace.Begin("establish tether backchannel")) // HACK: currently RawConn dosn't implement timeout so throttle the spinning // it does implement the Timeout methods so the intermediary code can be written // to support it, but they are stub implementation in rawconn impl. // This needs to tick *faster* than the ticker in connection.go on the // portlayer side. The PL sends the first syn and if this isn't waiting, // alignment will take a few rounds (or it may never happen). ticker := time.NewTicker(10 * time.Millisecond) defer ticker.Stop() // We run this in a separate goroutine because HandshakeServer // calls a Read on rawconn which is a blocking call which causes // the caller to block as well so this is the only way to cancel. // Calling Close() will unblock us and on the next tick we will // return ctx.Err() go func() { select { case <-ctx.Done(): conn.Close() } }() for { select { case <-ticker.C: if ctx.Err() != nil { return ctx.Err() } deadline, ok := ctx.Deadline() if ok { conn.SetReadDeadline(deadline) } err := serial.HandshakeServer(conn) if err == nil { conn.SetReadDeadline(time.Time{}) return nil } switch et := err.(type) { case *serial.HandshakeError: log.Debugf("HandshakeServer: %v", et) default: log.Errorf("HandshakeServer: %v", err) } } } }
// Start the server, make 200 client connections, test they connect, then Stop. func TestAttachStartStop(t *testing.T) { log.SetLevel(log.InfoLevel) s := NewAttachServer("", -1) wg := &sync.WaitGroup{} dial := func() { defer wg.Done() c, err := net.Dial("tcp", s.l.Addr().String()) assert.NoError(t, err) assert.NotNil(t, c) defer c.Close() buf := make([]byte, 1) c.SetReadDeadline(time.Now().Add(time.Second)) c.Read(buf) // This will pass if the client has written a second syn packet by the time it's called. As such we set an // unbounded readdeadline on the connection. // We can assert behaviours that take a while, but cannot reliably assert behaviours that require fast scheduling // of lots of threads on all systems running the CI. c.SetReadDeadline(time.Time{}) if !assert.NoError(t, serial.HandshakeServer(c), "Expected handshake to succeed on 2nd syn packet from client") { return } } assert.NoError(t, s.Start(true)) for i := 0; i < 100; i++ { wg.Add(1) go dial() } done := make(chan bool) go func() { wg.Wait() close(done) }() select { case <-done: case <-time.After(10 * time.Second): t.Fail() } assert.NoError(t, s.Stop()) _, err := net.Dial("tcp", s.l.Addr().String()) assert.Error(t, err) }
func backchannel(ctx context.Context, conn *net.Conn) error { // HACK: currently RawConn dosn't implement timeout so throttle the spinning ticker := time.NewTicker(50 * time.Millisecond) for { select { case <-ticker.C: err := serial.HandshakeServer(ctx, *conn) if err == nil { return nil } case <-ctx.Done(): (*conn).Close() ticker.Stop() return ctx.Err() } } }
func TestAttachSshSession(t *testing.T) { log.SetLevel(log.InfoLevel) s := NewAttachServer("", -1) assert.NoError(t, s.Start()) defer s.Stop() expectedID := "foo" // This should block until the ssh server returns its container ID wg := sync.WaitGroup{} go func() { wg.Add(1) defer wg.Done() _, err := s.connServer.Get(context.Background(), expectedID, 5*time.Second) if !assert.NoError(t, err) { return } }() // Dial the attach server. This is a TCP client networkClientCon, err := net.Dial("tcp", s.l.Addr().String()) if !assert.NoError(t, err) { return } if !assert.NoError(t, serial.HandshakeServer(context.Background(), networkClientCon)) { return } containerConfig := &ssh.ServerConfig{ NoClientAuth: true, } signer, err := ssh.ParsePrivateKey(testdata.PEMBytes["dsa"]) if !assert.NoError(t, err) { return } containerConfig.AddHostKey(signer) // create the SSH server on the client. The attach server will ssh connect to this. sshConn, chans, reqs, err := ssh.NewServerConn(networkClientCon, containerConfig) if !assert.NoError(t, err) { return } defer sshConn.Close() // Service the incoming Channel channel. go func() { wg.Add(1) defer wg.Done() for req := range reqs { if req.Type == msgs.ContainersReq { msg := msgs.ContainersMsg{IDs: []string{expectedID}} req.Reply(true, msg.Marshal()) break } } }() go func() { wg.Add(1) defer wg.Done() for ch := range chans { assert.Equal(t, ch.ChannelType(), attachChannelType) _, _, _ = ch.Accept() break } }() wg.Wait() }