Skip to content

Commit

Permalink
Adding io resource closing to unblock io.Copy. Also replaced unbuffer…
Browse files Browse the repository at this point in the history
…ed error channel with the buffered channels for unblocking send and receive. (#503)

Co-authored-by: Serhat Toktamisoglu <[email protected]>
  • Loading branch information
stoktamisoglu and Serhat Toktamisoglu authored Nov 6, 2024
1 parent 1789ded commit 6bc43a1
Showing 1 changed file with 40 additions and 7 deletions.
47 changes: 40 additions & 7 deletions ios/tunnel/userspace_tunnel.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"io"
"log/slog"
"net"
"sync"
"time"

"github.com/danielpaulus/go-ios/ios"
Expand All @@ -21,6 +22,22 @@ import (
"gvisor.dev/gvisor/pkg/waiter"
)

// ioResourceCloser is a type for closing function.
type ioResourceCloser func()

// createIoCloser returns a ioResourceCloser for closing both writer and together
func createIoCloser(rw1, rw2 io.ReadWriteCloser) ioResourceCloser {

// Using sync.Once is essential to close writer and reader just once
var once sync.Once
return func() {
once.Do(func() {
rw1.Close()
rw2.Close()
})
}
}

// UserSpaceTUNInterface uses gVisor's netstack to create a userspace virtual network interface.
// You can use it to connect local tcp connections to remote adresses on the network.
// Set it up with the Init method and provide a io.ReadWriter to a IP/TUN compatible device.
Expand Down Expand Up @@ -78,17 +95,33 @@ func (iface *UserSpaceTUNInterface) TunnelRWCThroughInterface(localPort uint16,
return nil
}

func proxyConns(rw1 io.ReadWriter, rw2 io.ReadWriter) error {
err1 := make(chan error)
err2 := make(chan error)
go ioCopyWithErr(rw1, rw2, err1)
go ioCopyWithErr(rw2, rw1, err2)
return errors.Join(<-err1, <-err2)
func proxyConns(rw1 io.ReadWriteCloser, rw2 io.ReadWriteCloser) error {

// Use buffered channel for non-blocking send recieve. We use the same single channel 2 times for 2 ioCopyWithErr.
errCh := make(chan error, 2)

// Create a IO closing functions to unblock stuck io.Copy() call
ioCloser := createIoCloser(rw1, rw2)

// Send same error channel and the io close function
go ioCopyWithErr(rw1, rw2, errCh, ioCloser)
go ioCopyWithErr(rw2, rw1, errCh, ioCloser)

// Read from error channel. As the channel is a FIFO queue first in first out, each <-errCh will read one message and remove it from the channel.
// Order of messages are not important.
err1 := <-errCh
err2 := <-errCh

return errors.Join(err1, err2)
}

func ioCopyWithErr(w io.Writer, r io.Reader, errCh chan error) {
func ioCopyWithErr(w io.Writer, r io.Reader, errCh chan error, ioCloser ioResourceCloser) {
_, err := io.Copy(w, r)
errCh <- err

// Close the writer and reader to notify the second io.Copy() if one part of the connection closed.
// This is also necessary to avoid resource leaking.
ioCloser()
}

// Init initializes the virtual network interface.
Expand Down

0 comments on commit 6bc43a1

Please sign in to comment.