Browse Source

- Fix: meekConn.Write() should not abort
when pumpWrites returns an error
- add comment regarding io.MultiWriter order
- don't use io.EOF inappropriately

Rod Hynes 9 years ago
parent
commit
a707fd2261
1 changed files with 26 additions and 9 deletions
  1. 26 9
      psiphon/server/meek.go

+ 26 - 9
psiphon/server/meek.go

@@ -345,6 +345,10 @@ func (server *MeekServer) ServeHTTP(responseWriter http.ResponseWriter, request
 		// newly acquired buffers.
 		// newly acquired buffers.
 		session.cachedResponse.Reset()
 		session.cachedResponse.Reset()
 
 
+		// Note: this code depends on an implementation detail of
+		// io.MultiWriter: a Write() to the MultiWriter writes first
+		// to the cache, and then to the response writer. So if the
+		// write to the reponse writer fails, the payload is cached.
 		multiWriter := io.MultiWriter(session.cachedResponse, responseWriter)
 		multiWriter := io.MultiWriter(session.cachedResponse, responseWriter)
 
 
 		// The client expects 206, not 200, whenever it sets a Range header,
 		// The client expects 206, not 200, whenever it sets a Range header,
@@ -864,6 +868,8 @@ func (conn *meekConn) pumpReads(reader io.Reader) error {
 	return nil
 	return nil
 }
 }
 
 
+var errMeekConnectionHasClosed = errors.New("meek connection has closed")
+
 // Read reads from the meekConn into buffer. Read blocks until
 // Read reads from the meekConn into buffer. Read blocks until
 // some data is read or the meekConn closes. Under the hood, it
 // some data is read or the meekConn closes. Under the hood, it
 // waits for pumpReads to submit a reader to read from.
 // waits for pumpReads to submit a reader to read from.
@@ -877,7 +883,7 @@ func (conn *meekConn) Read(buffer []byte) (int, error) {
 	case readBuffer = <-conn.partialReadBuffer:
 	case readBuffer = <-conn.partialReadBuffer:
 	case readBuffer = <-conn.fullReadBuffer:
 	case readBuffer = <-conn.fullReadBuffer:
 	case <-conn.closeBroadcast:
 	case <-conn.closeBroadcast:
-		return 0, io.EOF
+		return 0, common.ContextError(errMeekConnectionHasClosed)
 	}
 	}
 
 
 	n, err := readBuffer.Read(buffer)
 	n, err := readBuffer.Read(buffer)
@@ -936,7 +942,7 @@ func (conn *meekConn) pumpWrites(writer io.Writer) error {
 		case <-timeout.C:
 		case <-timeout.C:
 			return nil
 			return nil
 		case <-conn.closeBroadcast:
 		case <-conn.closeBroadcast:
-			return io.EOF
+			return common.ContextError(errMeekConnectionHasClosed)
 		}
 		}
 	}
 	}
 }
 }
@@ -968,17 +974,28 @@ func (conn *meekConn) Write(buffer []byte) (int, error) {
 		select {
 		select {
 		case conn.nextWriteBuffer <- chunk:
 		case conn.nextWriteBuffer <- chunk:
 		case <-conn.closeBroadcast:
 		case <-conn.closeBroadcast:
-			return n, io.EOF
+			return n, common.ContextError(errMeekConnectionHasClosed)
 		}
 		}
 
 
 		// Wait for the buffer to be processed.
 		// Wait for the buffer to be processed.
 		select {
 		select {
-		case err := <-conn.writeResult:
-			if err != nil {
-				return n, err
-			}
+		case _ = <-conn.writeResult:
+			// The err from conn.writeResult comes from the
+			// io.MultiWriter used in pumpWrites, which writes
+			// to both the cached response and the HTTP response.
+			//
+			// Don't stop on error here, since only writing
+			// to the HTTP response will fail, and the client
+			// may retry and use the cached response.
+			//
+			// It's possible that the cached response buffer
+			// is too small for the client to successfully
+			// retry, but that cannot be determined. In this
+			// case, the meek connection will eventually fail.
+			//
+			// err is already logged in ServeHTTP.
 		case <-conn.closeBroadcast:
 		case <-conn.closeBroadcast:
-			return n, io.EOF
+			return n, common.ContextError(errMeekConnectionHasClosed)
 		}
 		}
 		n += len(chunk)
 		n += len(chunk)
 	}
 	}
@@ -1011,7 +1028,7 @@ func (conn *meekConn) RemoteAddr() net.Addr {
 // SetDeadline is not a true implementation of net.Conn.SetDeadline. It
 // SetDeadline is not a true implementation of net.Conn.SetDeadline. It
 // merely checks that the requested timeout exceeds the MEEK_MAX_SESSION_STALENESS
 // merely checks that the requested timeout exceeds the MEEK_MAX_SESSION_STALENESS
 // period. When it does, and the session is idle, the meekConn Read/Write will
 // period. When it does, and the session is idle, the meekConn Read/Write will
-// be interrupted and return io.EOF (not a timeout error) before the deadline.
+// be interrupted and return an error (not a timeout error) before the deadline.
 // In other words, this conn will approximate the desired functionality of
 // In other words, this conn will approximate the desired functionality of
 // timing out on idle on or before the requested deadline.
 // timing out on idle on or before the requested deadline.
 func (conn *meekConn) SetDeadline(t time.Time) error {
 func (conn *meekConn) SetDeadline(t time.Time) error {