mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2025-09-20 12:35:59 +08:00
chore: update anytls (#1851)
* Implement deadline for `Stream` * chore: code cleanup * fix: buffer release * fix: do not use buffer for `cmdUpdatePaddingScheme` --------- Co-authored-by: anytls <anytls>
This commit is contained in:
@ -135,6 +135,8 @@ func (l *Listener) HandleConn(conn net.Conn, h *sing.ListenerHandler) {
|
|||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
b := buf.NewPacket()
|
b := buf.NewPacket()
|
||||||
|
defer b.Release()
|
||||||
|
|
||||||
_, err := b.ReadOnceFrom(conn)
|
_, err := b.ReadOnceFrom(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -177,6 +179,6 @@ func (l *Listener) HandleConn(conn net.Conn, h *sing.ListenerHandler) {
|
|||||||
Destination: destination,
|
Destination: destination,
|
||||||
})
|
})
|
||||||
}, &l.padding)
|
}, &l.padding)
|
||||||
session.Run(true)
|
session.Run()
|
||||||
session.Close()
|
session.Close()
|
||||||
}
|
}
|
||||||
|
@ -71,6 +71,8 @@ func (c *Client) CreateOutboundTLSConnection(ctx context.Context) (net.Conn, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
b := buf.NewPacket()
|
b := buf.NewPacket()
|
||||||
|
defer b.Release()
|
||||||
|
|
||||||
b.Write(c.passwordSha256)
|
b.Write(c.passwordSha256)
|
||||||
var paddingLen int
|
var paddingLen int
|
||||||
if pad := c.padding.Load().GenerateRecordPayloadSizes(0); len(pad) > 0 {
|
if pad := c.padding.Load().GenerateRecordPayloadSizes(0); len(pad) > 0 {
|
||||||
|
74
transport/anytls/pipe/deadline.go
Normal file
74
transport/anytls/pipe/deadline.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package pipe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PipeDeadline is an abstraction for handling timeouts.
|
||||||
|
type PipeDeadline struct {
|
||||||
|
mu sync.Mutex // Guards timer and cancel
|
||||||
|
timer *time.Timer
|
||||||
|
cancel chan struct{} // Must be non-nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakePipeDeadline() PipeDeadline {
|
||||||
|
return PipeDeadline{cancel: make(chan struct{})}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets the point in time when the deadline will time out.
|
||||||
|
// A timeout event is signaled by closing the channel returned by waiter.
|
||||||
|
// Once a timeout has occurred, the deadline can be refreshed by specifying a
|
||||||
|
// t value in the future.
|
||||||
|
//
|
||||||
|
// A zero value for t prevents timeout.
|
||||||
|
func (d *PipeDeadline) Set(t time.Time) {
|
||||||
|
d.mu.Lock()
|
||||||
|
defer d.mu.Unlock()
|
||||||
|
|
||||||
|
if d.timer != nil && !d.timer.Stop() {
|
||||||
|
<-d.cancel // Wait for the timer callback to finish and close cancel
|
||||||
|
}
|
||||||
|
d.timer = nil
|
||||||
|
|
||||||
|
// Time is zero, then there is no deadline.
|
||||||
|
closed := isClosedChan(d.cancel)
|
||||||
|
if t.IsZero() {
|
||||||
|
if closed {
|
||||||
|
d.cancel = make(chan struct{})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time in the future, setup a timer to cancel in the future.
|
||||||
|
if dur := time.Until(t); dur > 0 {
|
||||||
|
if closed {
|
||||||
|
d.cancel = make(chan struct{})
|
||||||
|
}
|
||||||
|
d.timer = time.AfterFunc(dur, func() {
|
||||||
|
close(d.cancel)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time in the past, so close immediately.
|
||||||
|
if !closed {
|
||||||
|
close(d.cancel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait returns a channel that is closed when the deadline is exceeded.
|
||||||
|
func (d *PipeDeadline) Wait() chan struct{} {
|
||||||
|
d.mu.Lock()
|
||||||
|
defer d.mu.Unlock()
|
||||||
|
return d.cancel
|
||||||
|
}
|
||||||
|
|
||||||
|
func isClosedChan(c <-chan struct{}) bool {
|
||||||
|
select {
|
||||||
|
case <-c:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
232
transport/anytls/pipe/io_pipe.go
Normal file
232
transport/anytls/pipe/io_pipe.go
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Pipe adapter to connect code expecting an io.Reader
|
||||||
|
// with code expecting an io.Writer.
|
||||||
|
|
||||||
|
package pipe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// onceError is an object that will only store an error once.
|
||||||
|
type onceError struct {
|
||||||
|
sync.Mutex // guards following
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *onceError) Store(err error) {
|
||||||
|
a.Lock()
|
||||||
|
defer a.Unlock()
|
||||||
|
if a.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
a.err = err
|
||||||
|
}
|
||||||
|
func (a *onceError) Load() error {
|
||||||
|
a.Lock()
|
||||||
|
defer a.Unlock()
|
||||||
|
return a.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// A pipe is the shared pipe structure underlying PipeReader and PipeWriter.
|
||||||
|
type pipe struct {
|
||||||
|
wrMu sync.Mutex // Serializes Write operations
|
||||||
|
wrCh chan []byte
|
||||||
|
rdCh chan int
|
||||||
|
|
||||||
|
once sync.Once // Protects closing done
|
||||||
|
done chan struct{}
|
||||||
|
rerr onceError
|
||||||
|
werr onceError
|
||||||
|
|
||||||
|
readDeadline PipeDeadline
|
||||||
|
writeDeadline PipeDeadline
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pipe) read(b []byte) (n int, err error) {
|
||||||
|
select {
|
||||||
|
case <-p.done:
|
||||||
|
return 0, p.readCloseError()
|
||||||
|
case <-p.readDeadline.Wait():
|
||||||
|
return 0, os.ErrDeadlineExceeded
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case bw := <-p.wrCh:
|
||||||
|
nr := copy(b, bw)
|
||||||
|
p.rdCh <- nr
|
||||||
|
return nr, nil
|
||||||
|
case <-p.done:
|
||||||
|
return 0, p.readCloseError()
|
||||||
|
case <-p.readDeadline.Wait():
|
||||||
|
return 0, os.ErrDeadlineExceeded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pipe) closeRead(err error) error {
|
||||||
|
if err == nil {
|
||||||
|
err = io.ErrClosedPipe
|
||||||
|
}
|
||||||
|
p.rerr.Store(err)
|
||||||
|
p.once.Do(func() { close(p.done) })
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pipe) write(b []byte) (n int, err error) {
|
||||||
|
select {
|
||||||
|
case <-p.done:
|
||||||
|
return 0, p.writeCloseError()
|
||||||
|
case <-p.writeDeadline.Wait():
|
||||||
|
return 0, os.ErrDeadlineExceeded
|
||||||
|
default:
|
||||||
|
p.wrMu.Lock()
|
||||||
|
defer p.wrMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
for once := true; once || len(b) > 0; once = false {
|
||||||
|
select {
|
||||||
|
case p.wrCh <- b:
|
||||||
|
nw := <-p.rdCh
|
||||||
|
b = b[nw:]
|
||||||
|
n += nw
|
||||||
|
case <-p.done:
|
||||||
|
return n, p.writeCloseError()
|
||||||
|
case <-p.writeDeadline.Wait():
|
||||||
|
return n, os.ErrDeadlineExceeded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pipe) closeWrite(err error) error {
|
||||||
|
if err == nil {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
p.werr.Store(err)
|
||||||
|
p.once.Do(func() { close(p.done) })
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readCloseError is considered internal to the pipe type.
|
||||||
|
func (p *pipe) readCloseError() error {
|
||||||
|
rerr := p.rerr.Load()
|
||||||
|
if werr := p.werr.Load(); rerr == nil && werr != nil {
|
||||||
|
return werr
|
||||||
|
}
|
||||||
|
return io.ErrClosedPipe
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeCloseError is considered internal to the pipe type.
|
||||||
|
func (p *pipe) writeCloseError() error {
|
||||||
|
werr := p.werr.Load()
|
||||||
|
if rerr := p.rerr.Load(); werr == nil && rerr != nil {
|
||||||
|
return rerr
|
||||||
|
}
|
||||||
|
return io.ErrClosedPipe
|
||||||
|
}
|
||||||
|
|
||||||
|
// A PipeReader is the read half of a pipe.
|
||||||
|
type PipeReader struct{ pipe }
|
||||||
|
|
||||||
|
// Read implements the standard Read interface:
|
||||||
|
// it reads data from the pipe, blocking until a writer
|
||||||
|
// arrives or the write end is closed.
|
||||||
|
// If the write end is closed with an error, that error is
|
||||||
|
// returned as err; otherwise err is EOF.
|
||||||
|
func (r *PipeReader) Read(data []byte) (n int, err error) {
|
||||||
|
return r.pipe.read(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the reader; subsequent writes to the
|
||||||
|
// write half of the pipe will return the error [ErrClosedPipe].
|
||||||
|
func (r *PipeReader) Close() error {
|
||||||
|
return r.CloseWithError(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseWithError closes the reader; subsequent writes
|
||||||
|
// to the write half of the pipe will return the error err.
|
||||||
|
//
|
||||||
|
// CloseWithError never overwrites the previous error if it exists
|
||||||
|
// and always returns nil.
|
||||||
|
func (r *PipeReader) CloseWithError(err error) error {
|
||||||
|
return r.pipe.closeRead(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A PipeWriter is the write half of a pipe.
|
||||||
|
type PipeWriter struct{ r PipeReader }
|
||||||
|
|
||||||
|
// Write implements the standard Write interface:
|
||||||
|
// it writes data to the pipe, blocking until one or more readers
|
||||||
|
// have consumed all the data or the read end is closed.
|
||||||
|
// If the read end is closed with an error, that err is
|
||||||
|
// returned as err; otherwise err is [ErrClosedPipe].
|
||||||
|
func (w *PipeWriter) Write(data []byte) (n int, err error) {
|
||||||
|
return w.r.pipe.write(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the writer; subsequent reads from the
|
||||||
|
// read half of the pipe will return no bytes and EOF.
|
||||||
|
func (w *PipeWriter) Close() error {
|
||||||
|
return w.CloseWithError(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseWithError closes the writer; subsequent reads from the
|
||||||
|
// read half of the pipe will return no bytes and the error err,
|
||||||
|
// or EOF if err is nil.
|
||||||
|
//
|
||||||
|
// CloseWithError never overwrites the previous error if it exists
|
||||||
|
// and always returns nil.
|
||||||
|
func (w *PipeWriter) CloseWithError(err error) error {
|
||||||
|
return w.r.pipe.closeWrite(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pipe creates a synchronous in-memory pipe.
|
||||||
|
// It can be used to connect code expecting an [io.Reader]
|
||||||
|
// with code expecting an [io.Writer].
|
||||||
|
//
|
||||||
|
// Reads and Writes on the pipe are matched one to one
|
||||||
|
// except when multiple Reads are needed to consume a single Write.
|
||||||
|
// That is, each Write to the [PipeWriter] blocks until it has satisfied
|
||||||
|
// one or more Reads from the [PipeReader] that fully consume
|
||||||
|
// the written data.
|
||||||
|
// The data is copied directly from the Write to the corresponding
|
||||||
|
// Read (or Reads); there is no internal buffering.
|
||||||
|
//
|
||||||
|
// It is safe to call Read and Write in parallel with each other or with Close.
|
||||||
|
// Parallel calls to Read and parallel calls to Write are also safe:
|
||||||
|
// the individual calls will be gated sequentially.
|
||||||
|
//
|
||||||
|
// Added SetReadDeadline and SetWriteDeadline methods based on `io.Pipe`.
|
||||||
|
func Pipe() (*PipeReader, *PipeWriter) {
|
||||||
|
pw := &PipeWriter{r: PipeReader{pipe: pipe{
|
||||||
|
wrCh: make(chan []byte),
|
||||||
|
rdCh: make(chan int),
|
||||||
|
done: make(chan struct{}),
|
||||||
|
readDeadline: MakePipeDeadline(),
|
||||||
|
writeDeadline: MakePipeDeadline(),
|
||||||
|
}}}
|
||||||
|
return &pw.r, pw
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PipeReader) SetReadDeadline(t time.Time) error {
|
||||||
|
if isClosedChan(p.done) {
|
||||||
|
return io.ErrClosedPipe
|
||||||
|
}
|
||||||
|
p.readDeadline.Set(t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PipeWriter) SetWriteDeadline(t time.Time) error {
|
||||||
|
if isClosedChan(p.r.done) {
|
||||||
|
return io.ErrClosedPipe
|
||||||
|
}
|
||||||
|
p.r.writeDeadline.Set(t)
|
||||||
|
return nil
|
||||||
|
}
|
@ -123,7 +123,7 @@ func (c *Client) createSession(ctx context.Context) (*Session, error) {
|
|||||||
c.idleSession.Remove(math.MaxUint64 - session.seq)
|
c.idleSession.Remove(math.MaxUint64 - session.seq)
|
||||||
c.idleSessionLock.Unlock()
|
c.idleSessionLock.Unlock()
|
||||||
}
|
}
|
||||||
session.Run(false)
|
session.Run()
|
||||||
return session, nil
|
return session, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,10 +36,11 @@ type Session struct {
|
|||||||
padding *atomic.TypedValue[*padding.PaddingFactory]
|
padding *atomic.TypedValue[*padding.PaddingFactory]
|
||||||
|
|
||||||
// client
|
// client
|
||||||
isClient bool
|
isClient bool
|
||||||
buffering bool
|
sendPadding bool
|
||||||
buffer []byte
|
buffering bool
|
||||||
pktCounter atomic.Uint32
|
buffer []byte
|
||||||
|
pktCounter atomic.Uint32
|
||||||
|
|
||||||
// server
|
// server
|
||||||
onNewStream func(stream *Stream)
|
onNewStream func(stream *Stream)
|
||||||
@ -47,9 +48,10 @@ type Session struct {
|
|||||||
|
|
||||||
func NewClientSession(conn net.Conn, _padding *atomic.TypedValue[*padding.PaddingFactory]) *Session {
|
func NewClientSession(conn net.Conn, _padding *atomic.TypedValue[*padding.PaddingFactory]) *Session {
|
||||||
s := &Session{
|
s := &Session{
|
||||||
conn: conn,
|
conn: conn,
|
||||||
isClient: true,
|
isClient: true,
|
||||||
padding: _padding,
|
sendPadding: true,
|
||||||
|
padding: _padding,
|
||||||
}
|
}
|
||||||
s.die = make(chan struct{})
|
s.die = make(chan struct{})
|
||||||
s.streams = make(map[uint32]*Stream)
|
s.streams = make(map[uint32]*Stream)
|
||||||
@ -60,7 +62,6 @@ func NewServerSession(conn net.Conn, onNewStream func(stream *Stream), _padding
|
|||||||
s := &Session{
|
s := &Session{
|
||||||
conn: conn,
|
conn: conn,
|
||||||
onNewStream: onNewStream,
|
onNewStream: onNewStream,
|
||||||
isClient: false,
|
|
||||||
padding: _padding,
|
padding: _padding,
|
||||||
}
|
}
|
||||||
s.die = make(chan struct{})
|
s.die = make(chan struct{})
|
||||||
@ -68,8 +69,8 @@ func NewServerSession(conn net.Conn, onNewStream func(stream *Stream), _padding
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Session) Run(isServer bool) {
|
func (s *Session) Run() {
|
||||||
if isServer {
|
if !s.isClient {
|
||||||
s.recvLoop()
|
s.recvLoop()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -257,19 +258,18 @@ func (s *Session) recvLoop() error {
|
|||||||
}
|
}
|
||||||
case cmdUpdatePaddingScheme:
|
case cmdUpdatePaddingScheme:
|
||||||
if hdr.Length() > 0 {
|
if hdr.Length() > 0 {
|
||||||
buffer := pool.Get(int(hdr.Length()))
|
// `rawScheme` Do not use buffer to prevent subsequent misuse
|
||||||
if _, err := io.ReadFull(s.conn, buffer); err != nil {
|
rawScheme := make([]byte, int(hdr.Length()))
|
||||||
pool.Put(buffer)
|
if _, err := io.ReadFull(s.conn, rawScheme); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if s.isClient {
|
if s.isClient {
|
||||||
if padding.UpdatePaddingScheme(buffer, s.padding) {
|
if padding.UpdatePaddingScheme(rawScheme, s.padding) {
|
||||||
log.Infoln("[Update padding succeed] %x\n", md5.Sum(buffer))
|
log.Infoln("[Update padding succeed] %x\n", md5.Sum(rawScheme))
|
||||||
} else {
|
} else {
|
||||||
log.Warnln("[Update padding failed] %x\n", md5.Sum(buffer))
|
log.Warnln("[Update padding failed] %x\n", md5.Sum(rawScheme))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pool.Put(buffer)
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
// I don't know what command it is (can't have data)
|
// I don't know what command it is (can't have data)
|
||||||
@ -319,7 +319,7 @@ func (s *Session) writeConn(b []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// calulate & send padding
|
// calulate & send padding
|
||||||
if s.isClient {
|
if s.sendPadding {
|
||||||
pkt := s.pktCounter.Add(1)
|
pkt := s.pktCounter.Add(1)
|
||||||
paddingF := s.padding.Load()
|
paddingF := s.padding.Load()
|
||||||
if pkt < paddingF.Stop {
|
if pkt < paddingF.Stop {
|
||||||
@ -333,7 +333,6 @@ func (s *Session) writeConn(b []byte) (n int, err error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// logrus.Debugln(pkt, "write", l, "len", remainPayloadLen, "remain", remainPayloadLen-l)
|
|
||||||
if remainPayloadLen > l { // this packet is all payload
|
if remainPayloadLen > l { // this packet is all payload
|
||||||
_, err = s.conn.Write(b[:l])
|
_, err = s.conn.Write(b[:l])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -371,7 +370,12 @@ func (s *Session) writeConn(b []byte) (n int, err error) {
|
|||||||
// maybe still remain payload to write
|
// maybe still remain payload to write
|
||||||
if len(b) == 0 {
|
if len(b) == 0 {
|
||||||
return
|
return
|
||||||
|
} else {
|
||||||
|
n2, err := s.conn.Write(b)
|
||||||
|
return n + n2, err
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
s.sendPadding = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/metacubex/mihomo/transport/anytls/pipe"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Stream implements net.Conn
|
// Stream implements net.Conn
|
||||||
@ -14,8 +16,9 @@ type Stream struct {
|
|||||||
|
|
||||||
sess *Session
|
sess *Session
|
||||||
|
|
||||||
pipeR *io.PipeReader
|
pipeR *pipe.PipeReader
|
||||||
pipeW *io.PipeWriter
|
pipeW *pipe.PipeWriter
|
||||||
|
writeDeadline pipe.PipeDeadline
|
||||||
|
|
||||||
dieOnce sync.Once
|
dieOnce sync.Once
|
||||||
dieHook func()
|
dieHook func()
|
||||||
@ -26,7 +29,8 @@ func newStream(id uint32, sess *Session) *Stream {
|
|||||||
s := new(Stream)
|
s := new(Stream)
|
||||||
s.id = id
|
s.id = id
|
||||||
s.sess = sess
|
s.sess = sess
|
||||||
s.pipeR, s.pipeW = io.Pipe()
|
s.pipeR, s.pipeW = pipe.Pipe()
|
||||||
|
s.writeDeadline = pipe.MakePipeDeadline()
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,6 +41,11 @@ func (s *Stream) Read(b []byte) (n int, err error) {
|
|||||||
|
|
||||||
// Write implements net.Conn
|
// Write implements net.Conn
|
||||||
func (s *Stream) Write(b []byte) (n int, err error) {
|
func (s *Stream) Write(b []byte) (n int, err error) {
|
||||||
|
select {
|
||||||
|
case <-s.writeDeadline.Wait():
|
||||||
|
return 0, os.ErrDeadlineExceeded
|
||||||
|
default:
|
||||||
|
}
|
||||||
f := newFrame(cmdPSH, s.id)
|
f := newFrame(cmdPSH, s.id)
|
||||||
f.data = b
|
f.data = b
|
||||||
n, err = s.sess.writeFrame(f)
|
n, err = s.sess.writeFrame(f)
|
||||||
@ -67,15 +76,17 @@ func (s *Stream) sessionClose() (once bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Stream) SetReadDeadline(t time.Time) error {
|
func (s *Stream) SetReadDeadline(t time.Time) error {
|
||||||
return os.ErrNotExist
|
return s.pipeR.SetReadDeadline(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Stream) SetWriteDeadline(t time.Time) error {
|
func (s *Stream) SetWriteDeadline(t time.Time) error {
|
||||||
return os.ErrNotExist
|
s.writeDeadline.Set(t)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Stream) SetDeadline(t time.Time) error {
|
func (s *Stream) SetDeadline(t time.Time) error {
|
||||||
return os.ErrNotExist
|
s.SetWriteDeadline(t)
|
||||||
|
return s.SetReadDeadline(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LocalAddr satisfies net.Conn interface
|
// LocalAddr satisfies net.Conn interface
|
||||||
|
Reference in New Issue
Block a user