]> Cypherpunks.ru repositories - gostls13.git/commitdiff
crypto/tls: add CloseWrite method to Conn
authorBen Burkert <ben@benburkert.com>
Sun, 24 Jul 2016 22:13:56 +0000 (15:13 -0700)
committerBrad Fitzpatrick <bradfitz@golang.org>
Mon, 17 Oct 2016 14:26:55 +0000 (14:26 +0000)
The CloseWrite method sends a close_notify alert record to the other
side of the connection. This record indicates that the sender has
finished sending on the connection. Unlike the Close method, the sender
may still read from the connection until it recieves a close_notify
record (or the underlying connection is closed). This is analogous to a
TCP half-close.

Updates #8579

Change-Id: I9c6bc193efcb25cc187f7735ee07170afa7fdde3
Reviewed-on: https://go-review.googlesource.com/25159
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>

src/crypto/tls/conn.go
src/crypto/tls/tls_test.go

index a0c29f0c48f36d7f051c1d3fced77323f61ca261..a44d81cb9e2136b3f9543ea57f54ff562aa260f3 100644 (file)
@@ -64,6 +64,13 @@ type Conn struct {
        // the first transmitted Finished message is the tls-unique
        // channel-binding value.
        clientFinishedIsFirst bool
+
+       // closeNotifyErr is any error from sending the alertCloseNotify record.
+       closeNotifyErr error
+       // closeNotifySent is true if the Conn attempted to send an
+       // alertCloseNotify record.
+       closeNotifySent bool
+
        // clientFinished and serverFinished contain the Finished message sent
        // by the client or server in the most recent handshake. This is
        // retained to support the renegotiation extension and tls-unique
@@ -992,7 +999,10 @@ func (c *Conn) readHandshake() (interface{}, error) {
        return m, nil
 }
 
-var errClosed = errors.New("tls: use of closed connection")
+var (
+       errClosed   = errors.New("tls: use of closed connection")
+       errShutdown = errors.New("tls: protocol is shutdown")
+)
 
 // Write writes data to the connection.
 func (c *Conn) Write(b []byte) (int, error) {
@@ -1023,6 +1033,10 @@ func (c *Conn) Write(b []byte) (int, error) {
                return 0, alertInternalError
        }
 
+       if c.closeNotifySent {
+               return 0, errShutdown
+       }
+
        // SSL 3.0 and TLS 1.0 are susceptible to a chosen-plaintext
        // attack when using block mode ciphers due to predictable IVs.
        // This can be prevented by splitting each Application Data
@@ -1186,7 +1200,7 @@ func (c *Conn) Close() error {
        c.handshakeMutex.Lock()
        defer c.handshakeMutex.Unlock()
        if c.handshakeComplete {
-               alertErr = c.sendAlert(alertCloseNotify)
+               alertErr = c.closeNotify()
        }
 
        if err := c.conn.Close(); err != nil {
@@ -1195,6 +1209,32 @@ func (c *Conn) Close() error {
        return alertErr
 }
 
+var errEarlyCloseWrite = errors.New("tls: CloseWrite called before handshake complete")
+
+// CloseWrite shuts down the writing side of the connection. It should only be
+// called once the handshake has completed and does not call CloseWrite on the
+// underlying connection. Most callers should just use Close.
+func (c *Conn) CloseWrite() error {
+       c.handshakeMutex.Lock()
+       defer c.handshakeMutex.Unlock()
+       if !c.handshakeComplete {
+               return errEarlyCloseWrite
+       }
+
+       return c.closeNotify()
+}
+
+func (c *Conn) closeNotify() error {
+       c.out.Lock()
+       defer c.out.Unlock()
+
+       if !c.closeNotifySent {
+               c.closeNotifyErr = c.sendAlertLocked(alertCloseNotify)
+               c.closeNotifySent = true
+       }
+       return c.closeNotifyErr
+}
+
 // Handshake runs the client or server handshake
 // protocol if it has not yet been run.
 // Most uses of this package need not call Handshake
index 8b8dfa4e1e6ab7cbc4abb278e2e8df75f965bfbf..04da4928a998f3d3209df7e69da468e822158729 100644 (file)
@@ -11,6 +11,7 @@ import (
        "fmt"
        "internal/testenv"
        "io"
+       "io/ioutil"
        "math"
        "math/rand"
        "net"
@@ -458,6 +459,91 @@ func TestConnCloseBreakingWrite(t *testing.T) {
        }
 }
 
+func TestConnCloseWrite(t *testing.T) {
+       ln := newLocalListener(t)
+       defer ln.Close()
+
+       go func() {
+               sconn, err := ln.Accept()
+               if err != nil {
+                       t.Fatal(err)
+               }
+
+               serverConfig := testConfig.Clone()
+               srv := Server(sconn, serverConfig)
+               if err := srv.Handshake(); err != nil {
+                       t.Fatalf("handshake: %v", err)
+               }
+               defer srv.Close()
+
+               data, err := ioutil.ReadAll(srv)
+               if err != nil {
+                       t.Fatal(err)
+               }
+               if len(data) > 0 {
+                       t.Errorf("Read data = %q; want nothing", data)
+               }
+
+               if err = srv.CloseWrite(); err != nil {
+                       t.Errorf("server CloseWrite: %v", err)
+               }
+       }()
+
+       clientConfig := testConfig.Clone()
+       conn, err := Dial("tcp", ln.Addr().String(), clientConfig)
+       if err != nil {
+               t.Fatal(err)
+       }
+       if err = conn.Handshake(); err != nil {
+               t.Fatal(err)
+       }
+       defer conn.Close()
+
+       if err = conn.CloseWrite(); err != nil {
+               t.Errorf("client CloseWrite: %v", err)
+       }
+
+       if _, err := conn.Write([]byte{0}); err != errShutdown {
+               t.Errorf("CloseWrite error = %v; want errShutdown", err)
+       }
+
+       data, err := ioutil.ReadAll(conn)
+       if err != nil {
+               t.Fatal(err)
+       }
+       if len(data) > 0 {
+               t.Errorf("Read data = %q; want nothing", data)
+       }
+
+       // test CloseWrite called before handshake finished
+
+       ln2 := newLocalListener(t)
+       defer ln2.Close()
+
+       go func() {
+               sconn, err := ln2.Accept()
+               if err != nil {
+                       t.Fatal(err)
+               }
+
+               serverConfig := testConfig.Clone()
+               srv := Server(sconn, serverConfig)
+
+               srv.Handshake()
+               srv.Close()
+       }()
+
+       netConn, err := net.Dial("tcp", ln2.Addr().String())
+       if err != nil {
+               t.Fatal(err)
+       }
+       conn = Client(netConn, clientConfig)
+
+       if err = conn.CloseWrite(); err != errEarlyCloseWrite {
+               t.Errorf("CloseWrite error = %v; want errEarlyCloseWrite", err)
+       }
+}
+
 func TestClone(t *testing.T) {
        var c1 Config
        v := reflect.ValueOf(&c1).Elem()