tunnel: add write mutex for concurrent safety

WriteFrames and keepalive both write multi-part messages to the TLS
connection. Without synchronization, their writes could interleave
and corrupt the framing. Add writeMu to serialize all tunnel writes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Git Sagar 2026-06-06 22:53:06 +05:30
parent 6416159164
commit b3f4c5f42b

View file

@ -28,6 +28,7 @@ const (
// SoftEther VPN session. Each "block" is one Ethernet frame. // SoftEther VPN session. Each "block" is one Ethernet frame.
type Tunnel struct { type Tunnel struct {
sess *Session sess *Session
writeMu sync.Mutex
stopCh chan struct{} stopCh chan struct{}
stopped sync.Once stopped sync.Once
} }
@ -83,7 +84,11 @@ func (t *Tunnel) ReadFrames() ([][]byte, error) {
} }
// WriteFrames sends a batch of Ethernet frames to the server. // WriteFrames sends a batch of Ethernet frames to the server.
// Safe for concurrent use from multiple goroutines.
func (t *Tunnel) WriteFrames(frames [][]byte) error { func (t *Tunnel) WriteFrames(frames [][]byte) error {
t.writeMu.Lock()
defer t.writeMu.Unlock()
if err := binary.Write(t.sess.Conn, binary.BigEndian, uint32(len(frames))); err != nil { if err := binary.Write(t.sess.Conn, binary.BigEndian, uint32(len(frames))); err != nil {
return fmt.Errorf("write num blocks: %w", err) return fmt.Errorf("write num blocks: %w", err)
} }
@ -170,14 +175,20 @@ func (t *Tunnel) StartKeepalive() {
return return
case <-ticker.C: case <-ticker.C:
size := uint32(rng.Intn(int(maxKeepaliveSize))) + 1 size := uint32(rng.Intn(int(maxKeepaliveSize))) + 1
if err := binary.Write(t.sess.Conn, binary.BigEndian, keepAliveMagic); err != nil {
return
}
if err := binary.Write(t.sess.Conn, binary.BigEndian, size); err != nil {
return
}
rng.Read(randBuf[:size]) rng.Read(randBuf[:size])
if _, err := t.sess.Conn.Write(randBuf[:size]); err != nil {
t.writeMu.Lock()
err1 := binary.Write(t.sess.Conn, binary.BigEndian, keepAliveMagic)
var err2, err3 error
if err1 == nil {
err2 = binary.Write(t.sess.Conn, binary.BigEndian, size)
}
if err2 == nil {
_, err3 = t.sess.Conn.Write(randBuf[:size])
}
t.writeMu.Unlock()
if err1 != nil || err2 != nil || err3 != nil {
return return
} }
} }