mirror of
https://github.com/veops/oneterm.git
synced 2025-09-27 19:52:09 +08:00
fix(backend): resolve connection hanging issue after exit for telnet
This commit is contained in:
@@ -399,9 +399,6 @@ func HandleTerm(sess *gsession.Session, ctx *gin.Context) (err error) {
|
|||||||
protocols.WriteErrMsg(sess, msg)
|
protocols.WriteErrMsg(sess, msg)
|
||||||
logger.L().Info("closed by", zap.String("admin", closeBy))
|
logger.L().Info("closed by", zap.String("admin", closeBy))
|
||||||
return &myErrors.ApiError{Code: myErrors.ErrAdminClose, Data: map[string]any{"admin": closeBy}}
|
return &myErrors.ApiError{Code: myErrors.ErrAdminClose, Data: map[string]any{"admin": closeBy}}
|
||||||
case err = <-chs.ErrChan:
|
|
||||||
protocols.WriteErrMsg(sess, err.Error())
|
|
||||||
return
|
|
||||||
case in := <-chs.InChan:
|
case in := <-chs.InChan:
|
||||||
if sess.SessionType == model.SESSIONTYPE_WEB {
|
if sess.SessionType == model.SESSIONTYPE_WEB {
|
||||||
rt := in[0]
|
rt := in[0]
|
||||||
@@ -455,6 +452,5 @@ func HandleTerm(sess *gsession.Session, ctx *gin.Context) (err error) {
|
|||||||
if err = sess.G.Wait(); err != nil {
|
if err = sess.G.Wait(); err != nil {
|
||||||
logger.L().Debug("handle term wait end", zap.String("id", sess.SessionId), zap.Error(err))
|
logger.L().Debug("handle term wait end", zap.String("id", sess.SessionId), zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@@ -68,7 +68,6 @@ func ConnectTelnet(ctx *gin.Context, sess *gsession.Session, asset *model.Asset,
|
|||||||
logger.L().Error("telnet dial failed", zap.Error(err))
|
logger.L().Error("telnet dial failed", zap.Error(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
// Setup authentication control mechanisms
|
// Setup authentication control mechanisms
|
||||||
authDone := make(chan bool, 1)
|
authDone := make(chan bool, 1)
|
||||||
@@ -205,6 +204,7 @@ func ConnectTelnet(ctx *gin.Context, sess *gsession.Session, asset *model.Asset,
|
|||||||
// Data flow from client to server
|
// Data flow from client to server
|
||||||
// Reads from input pipe and writes to telnet connection
|
// Reads from input pipe and writes to telnet connection
|
||||||
sess.G.Go(func() error {
|
sess.G.Go(func() error {
|
||||||
|
|
||||||
buf := make([]byte, 1024)
|
buf := make([]byte, 1024)
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@@ -214,10 +214,11 @@ func ConnectTelnet(ctx *gin.Context, sess *gsession.Session, asset *model.Asset,
|
|||||||
n, err := chs.Rin.Read(buf)
|
n, err := chs.Rin.Read(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
continue
|
conn.Close()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
if err.Error() == "io: read/write on closed pipe" {
|
if err.Error() == "io: read/write on closed pipe" {
|
||||||
return nil // Normal exit condition, not an error
|
return nil
|
||||||
}
|
}
|
||||||
logger.L().Error("read from input pipe failed", zap.Error(err))
|
logger.L().Error("read from input pipe failed", zap.Error(err))
|
||||||
return err
|
return err
|
||||||
@@ -226,7 +227,7 @@ func ConnectTelnet(ctx *gin.Context, sess *gsession.Session, asset *model.Asset,
|
|||||||
if n > 0 {
|
if n > 0 {
|
||||||
_, err = conn.Write(buf[:n])
|
_, err = conn.Write(buf[:n])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.L().Error("write to telnet failed", zap.Error(err))
|
logger.L().Info("write to telnet failed, connection likely closed", zap.Error(err))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -237,6 +238,17 @@ func ConnectTelnet(ctx *gin.Context, sess *gsession.Session, asset *model.Asset,
|
|||||||
// Data flow from server to client
|
// Data flow from server to client
|
||||||
// Reads from telnet connection, processes telnet protocol, and sends to output channel
|
// Reads from telnet connection, processes telnet protocol, and sends to output channel
|
||||||
sess.G.Go(func() error {
|
sess.G.Go(func() error {
|
||||||
|
defer func() {
|
||||||
|
conn.Close()
|
||||||
|
if chs.Rin != nil {
|
||||||
|
chs.Rin.Close()
|
||||||
|
}
|
||||||
|
// Close AwayChan to signal connection end
|
||||||
|
sess.Once.Do(func() {
|
||||||
|
close(chs.AwayChan)
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
buf := make([]byte, 8192)
|
buf := make([]byte, 8192)
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@@ -253,11 +265,10 @@ func ConnectTelnet(ctx *gin.Context, sess *gsession.Session, asset *model.Asset,
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
logger.L().Info("telnet connection closed by server")
|
|
||||||
return fmt.Errorf("telnet connection closed")
|
return fmt.Errorf("telnet connection closed")
|
||||||
}
|
}
|
||||||
if strings.Contains(err.Error(), "use of closed network connection") {
|
if strings.Contains(err.Error(), "use of closed network connection") {
|
||||||
return nil // Normal connection close, not an error
|
return nil
|
||||||
}
|
}
|
||||||
logger.L().Error("read from telnet failed", zap.Error(err))
|
logger.L().Error("read from telnet failed", zap.Error(err))
|
||||||
return err
|
return err
|
||||||
@@ -275,8 +286,6 @@ func ConnectTelnet(ctx *gin.Context, sess *gsession.Session, asset *model.Asset,
|
|||||||
|
|
||||||
// Signal successful connection
|
// Signal successful connection
|
||||||
chs.ErrChan <- nil
|
chs.ErrChan <- nil
|
||||||
// Wait for all goroutines to complete
|
|
||||||
err = sess.G.Wait()
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -158,6 +158,39 @@ func Write(sess *gsession.Session, skipRecording ...bool) (err error) {
|
|||||||
// Read reads data from the session input
|
// Read reads data from the session input
|
||||||
func Read(sess *gsession.Session) error {
|
func Read(sess *gsession.Session) error {
|
||||||
chs := sess.Chans
|
chs := sess.Chans
|
||||||
|
|
||||||
|
// Handle CLIENT type with non-blocking reads
|
||||||
|
if sess.SessionType == model.SESSIONTYPE_CLIENT {
|
||||||
|
readChan := make(chan []byte)
|
||||||
|
errChan := make(chan error)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
p, err := sess.CliRw.Read()
|
||||||
|
if err != nil {
|
||||||
|
errChan <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
readChan <- p
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-sess.Gctx.Done():
|
||||||
|
return nil
|
||||||
|
case <-sess.Chans.AwayChan:
|
||||||
|
return nil
|
||||||
|
case err := <-errChan:
|
||||||
|
return err
|
||||||
|
case p := <-readChan:
|
||||||
|
chs.InChan <- p
|
||||||
|
sess.SetIdle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Original logic for WEB type
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-sess.Gctx.Done():
|
case <-sess.Gctx.Done():
|
||||||
@@ -180,13 +213,6 @@ func Read(sess *gsession.Session) error {
|
|||||||
sess.SetIdle() // TODO: performance issue
|
sess.SetIdle() // TODO: performance issue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if sess.SessionType == model.SESSIONTYPE_CLIENT {
|
|
||||||
p, err := sess.CliRw.Read()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
chs.InChan <- p
|
|
||||||
sess.SetIdle() // TODO: performance issue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -512,6 +512,7 @@ func (m *view) handleConnectionCommand(cmd string) tea.Cmd {
|
|||||||
m.Ctx.Params = append(m.Ctx.Params, gin.Param{Key: "asset_id", Value: cast.ToString(m.combines[cmd][1])})
|
m.Ctx.Params = append(m.Ctx.Params, gin.Param{Key: "asset_id", Value: cast.ToString(m.combines[cmd][1])})
|
||||||
m.Ctx.Params = append(m.Ctx.Params, gin.Param{Key: "protocol", Value: fmt.Sprintf("%s:%d", p, m.combines[cmd][2])})
|
m.Ctx.Params = append(m.Ctx.Params, gin.Param{Key: "protocol", Value: fmt.Sprintf("%s:%d", p, m.combines[cmd][2])})
|
||||||
m.Ctx = m.Ctx.Copy()
|
m.Ctx = m.Ctx.Copy()
|
||||||
|
m.Ctx.Set("sessionType", model.SESSIONTYPE_CLIENT)
|
||||||
m.connecting = true
|
m.connecting = true
|
||||||
|
|
||||||
return tea.Sequence(
|
return tea.Sequence(
|
||||||
@@ -758,8 +759,14 @@ func (conn *connector) Run() error {
|
|||||||
|
|
||||||
r, w := io.Pipe()
|
r, w := io.Pipe()
|
||||||
go func() {
|
go func() {
|
||||||
|
defer w.Close()
|
||||||
_, err := io.Copy(w, conn.stdin)
|
_, err := io.Copy(w, conn.stdin)
|
||||||
gsess.Chans.ErrChan <- err
|
// Don't block on sending error - HandleTerm may have already returned
|
||||||
|
select {
|
||||||
|
case gsess.Chans.ErrChan <- err:
|
||||||
|
default:
|
||||||
|
// Channel is closed or no one is listening, just return
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
gsess.CliRw = &session.CliRW{
|
gsess.CliRw = &session.CliRW{
|
||||||
@@ -784,7 +791,13 @@ func (conn *connector) Run() error {
|
|||||||
case <-gsess.Gctx.Done():
|
case <-gsess.Gctx.Done():
|
||||||
return
|
return
|
||||||
case w := <-ch:
|
case w := <-ch:
|
||||||
gsess.Chans.WindowChan <- w
|
// Non-blocking send to WindowChan
|
||||||
|
// Some protocols (like telnet) don't handle window changes
|
||||||
|
select {
|
||||||
|
case gsess.Chans.WindowChan <- w:
|
||||||
|
default:
|
||||||
|
// If no one is listening, just ignore
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
Reference in New Issue
Block a user