123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337 |
- package ldap
- import (
- "bytes"
- "errors"
- "io"
- "net"
- "net/http"
- "net/http/httptest"
- "runtime"
- "sync"
- "testing"
- "time"
- "gopkg.in/asn1-ber.v1"
- )
- func TestUnresponsiveConnection(t *testing.T) {
- // The do-nothing server that accepts requests and does nothing
- ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- }))
- defer ts.Close()
- c, err := net.Dial(ts.Listener.Addr().Network(), ts.Listener.Addr().String())
- if err != nil {
- t.Fatalf("error connecting to localhost tcp: %v", err)
- }
- // Create an Ldap connection
- conn := NewConn(c, false)
- conn.SetTimeout(time.Millisecond)
- conn.Start()
- defer conn.Close()
- // Mock a packet
- packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
- packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, conn.nextMessageID(), "MessageID"))
- bindRequest := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request")
- bindRequest.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version"))
- packet.AppendChild(bindRequest)
- // Send packet and test response
- msgCtx, err := conn.sendMessage(packet)
- if err != nil {
- t.Fatalf("error sending message: %v", err)
- }
- defer conn.finishMessage(msgCtx)
- packetResponse, ok := <-msgCtx.responses
- if !ok {
- t.Fatalf("no PacketResponse in response channel")
- }
- packet, err = packetResponse.ReadPacket()
- if err == nil {
- t.Fatalf("expected timeout error")
- }
- if err.Error() != "ldap: connection timed out" {
- t.Fatalf("unexpected error: %v", err)
- }
- }
- // TestFinishMessage tests that we do not enter deadlock when a goroutine makes
- // a request but does not handle all responses from the server.
- func TestFinishMessage(t *testing.T) {
- ptc := newPacketTranslatorConn()
- defer ptc.Close()
- conn := NewConn(ptc, false)
- conn.Start()
- // Test sending 5 different requests in series. Ensure that we can
- // get a response packet from the underlying connection and also
- // ensure that we can gracefully ignore unhandled responses.
- for i := 0; i < 5; i++ {
- t.Logf("serial request %d", i)
- // Create a message and make sure we can receive responses.
- msgCtx := testSendRequest(t, ptc, conn)
- testReceiveResponse(t, ptc, msgCtx)
- // Send a few unhandled responses and finish the message.
- testSendUnhandledResponsesAndFinish(t, ptc, conn, msgCtx, 5)
- t.Logf("serial request %d done", i)
- }
- // Test sending 5 different requests in parallel.
- var wg sync.WaitGroup
- for i := 0; i < 5; i++ {
- wg.Add(1)
- go func(i int) {
- defer wg.Done()
- t.Logf("parallel request %d", i)
- // Create a message and make sure we can receive responses.
- msgCtx := testSendRequest(t, ptc, conn)
- testReceiveResponse(t, ptc, msgCtx)
- // Send a few unhandled responses and finish the message.
- testSendUnhandledResponsesAndFinish(t, ptc, conn, msgCtx, 5)
- t.Logf("parallel request %d done", i)
- }(i)
- }
- wg.Wait()
- // We cannot run Close() in a defer because t.FailNow() will run it and
- // it will block if the processMessage Loop is in a deadlock.
- conn.Close()
- }
- func testSendRequest(t *testing.T, ptc *packetTranslatorConn, conn *Conn) (msgCtx *messageContext) {
- var msgID int64
- runWithTimeout(t, time.Second, func() {
- msgID = conn.nextMessageID()
- })
- requestPacket := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
- requestPacket.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, msgID, "MessageID"))
- var err error
- runWithTimeout(t, time.Second, func() {
- msgCtx, err = conn.sendMessage(requestPacket)
- if err != nil {
- t.Fatalf("unable to send request message: %s", err)
- }
- })
- // We should now be able to get this request packet out from the other
- // side.
- runWithTimeout(t, time.Second, func() {
- if _, err = ptc.ReceiveRequest(); err != nil {
- t.Fatalf("unable to receive request packet: %s", err)
- }
- })
- return msgCtx
- }
- func testReceiveResponse(t *testing.T, ptc *packetTranslatorConn, msgCtx *messageContext) {
- // Send a mock response packet.
- responsePacket := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Response")
- responsePacket.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, msgCtx.id, "MessageID"))
- runWithTimeout(t, time.Second, func() {
- if err := ptc.SendResponse(responsePacket); err != nil {
- t.Fatalf("unable to send response packet: %s", err)
- }
- })
- // We should be able to receive the packet from the connection.
- runWithTimeout(t, time.Second, func() {
- if _, ok := <-msgCtx.responses; !ok {
- t.Fatal("response channel closed")
- }
- })
- }
- func testSendUnhandledResponsesAndFinish(t *testing.T, ptc *packetTranslatorConn, conn *Conn, msgCtx *messageContext, numResponses int) {
- // Send a mock response packet.
- responsePacket := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Response")
- responsePacket.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, msgCtx.id, "MessageID"))
- // Send extra responses but do not attempt to receive them on the
- // client side.
- for i := 0; i < numResponses; i++ {
- runWithTimeout(t, time.Second, func() {
- if err := ptc.SendResponse(responsePacket); err != nil {
- t.Fatalf("unable to send response packet: %s", err)
- }
- })
- }
- // Finally, attempt to finish this message.
- runWithTimeout(t, time.Second, func() {
- conn.finishMessage(msgCtx)
- })
- }
- func runWithTimeout(t *testing.T, timeout time.Duration, f func()) {
- done := make(chan struct{})
- go func() {
- f()
- close(done)
- }()
- select {
- case <-done: // Success!
- case <-time.After(timeout):
- _, file, line, _ := runtime.Caller(1)
- t.Fatalf("%s:%d timed out", file, line)
- }
- }
- // packetTranslatorConn is a helpful type which can be used with various tests
- // in this package. It implements the net.Conn interface to be used as an
- // underlying connection for a *ldap.Conn. Most methods are no-ops but the
- // Read() and Write() methods are able to translate ber-encoded packets for
- // testing LDAP requests and responses.
- //
- // Test cases can simulate an LDAP server sending a response by calling the
- // SendResponse() method with a ber-encoded LDAP response packet. Test cases
- // can simulate an LDAP server receiving a request from a client by calling the
- // ReceiveRequest() method which returns a ber-encoded LDAP request packet.
- type packetTranslatorConn struct {
- lock sync.Mutex
- isClosed bool
- responseCond sync.Cond
- requestCond sync.Cond
- responseBuf bytes.Buffer
- requestBuf bytes.Buffer
- }
- var errPacketTranslatorConnClosed = errors.New("connection closed")
- func newPacketTranslatorConn() *packetTranslatorConn {
- conn := &packetTranslatorConn{}
- conn.responseCond = sync.Cond{L: &conn.lock}
- conn.requestCond = sync.Cond{L: &conn.lock}
- return conn
- }
- // Read is called by the reader() loop to receive response packets. It will
- // block until there are more packet bytes available or this connection is
- // closed.
- func (c *packetTranslatorConn) Read(b []byte) (n int, err error) {
- c.lock.Lock()
- defer c.lock.Unlock()
- for !c.isClosed {
- // Attempt to read data from the response buffer. If it fails
- // with an EOF, wait and try again.
- n, err = c.responseBuf.Read(b)
- if err != io.EOF {
- return n, err
- }
- c.responseCond.Wait()
- }
- return 0, errPacketTranslatorConnClosed
- }
- // SendResponse writes the given response packet to the response buffer for
- // this connection, signalling any goroutine waiting to read a response.
- func (c *packetTranslatorConn) SendResponse(packet *ber.Packet) error {
- c.lock.Lock()
- defer c.lock.Unlock()
- if c.isClosed {
- return errPacketTranslatorConnClosed
- }
- // Signal any goroutine waiting to read a response.
- defer c.responseCond.Broadcast()
- // Writes to the buffer should always succeed.
- c.responseBuf.Write(packet.Bytes())
- return nil
- }
- // Write is called by the processMessages() loop to send request packets.
- func (c *packetTranslatorConn) Write(b []byte) (n int, err error) {
- c.lock.Lock()
- defer c.lock.Unlock()
- if c.isClosed {
- return 0, errPacketTranslatorConnClosed
- }
- // Signal any goroutine waiting to read a request.
- defer c.requestCond.Broadcast()
- // Writes to the buffer should always succeed.
- return c.requestBuf.Write(b)
- }
- // ReceiveRequest attempts to read a request packet from this connection. It
- // will block until it is able to read a full request packet or until this
- // connection is closed.
- func (c *packetTranslatorConn) ReceiveRequest() (*ber.Packet, error) {
- c.lock.Lock()
- defer c.lock.Unlock()
- for !c.isClosed {
- // Attempt to parse a request packet from the request buffer.
- // If it fails with an unexpected EOF, wait and try again.
- requestReader := bytes.NewReader(c.requestBuf.Bytes())
- packet, err := ber.ReadPacket(requestReader)
- switch err {
- case io.EOF, io.ErrUnexpectedEOF:
- c.requestCond.Wait()
- case nil:
- // Advance the request buffer by the number of bytes
- // read to decode the request packet.
- c.requestBuf.Next(c.requestBuf.Len() - requestReader.Len())
- return packet, nil
- default:
- return nil, err
- }
- }
- return nil, errPacketTranslatorConnClosed
- }
- // Close closes this connection causing Read() and Write() calls to fail.
- func (c *packetTranslatorConn) Close() error {
- c.lock.Lock()
- defer c.lock.Unlock()
- c.isClosed = true
- c.responseCond.Broadcast()
- c.requestCond.Broadcast()
- return nil
- }
- func (c *packetTranslatorConn) LocalAddr() net.Addr {
- return (*net.TCPAddr)(nil)
- }
- func (c *packetTranslatorConn) RemoteAddr() net.Addr {
- return (*net.TCPAddr)(nil)
- }
- func (c *packetTranslatorConn) SetDeadline(t time.Time) error {
- return nil
- }
- func (c *packetTranslatorConn) SetReadDeadline(t time.Time) error {
- return nil
- }
- func (c *packetTranslatorConn) SetWriteDeadline(t time.Time) error {
- return nil
- }
|