Skip to content
This repository has been archived by the owner on May 6, 2024. It is now read-only.

Commit

Permalink
Don't use os.NewFile to create the TUN writer (#63)
Browse files Browse the repository at this point in the history
* Don't use os.NewFile to create the TUN writer

os.NewFile takes ownership of the file descriptor, and adds a finalizer
that closes the file descriptor when the file is garbage-collected.
This violates Outline's interface contract (caller retains ownership of
the TUN device), and could cause a problem if the file descriptor is
still being used

See Jigsaw-Code/Intra#285

* Return to MakeTunFile, but add a call to Dup

* Remove refactoring changes

* Close the tunWriter and update comments
  • Loading branch information
Benjamin M. Schwartz authored Sep 21, 2020
1 parent 6ab67d2 commit 9b609c0
Show file tree
Hide file tree
Showing 7 changed files with 20 additions and 5 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ require (
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect
golang.org/x/mobile v0.0.0-20200329125638-4c31acba0007 // indirect
golang.org/x/net v0.0.0-20200904194848-62affa334b73
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 // indirect
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009
google.golang.org/protobuf v1.25.0 // indirect
)
3 changes: 3 additions & 0 deletions intra/intra.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ func init() {
// rules. Currently, this only consists of redirecting DNS packets to a specified
// server; all other data flows directly to its destination.
//
// `fd` is the TUN device. The IntraTunnel acquires an additional reference to it, which
// is released by IntraTunnel.Disconnect(), so the caller must close `fd` _and_ call
// Disconnect() in order to close the TUN device.
// `fakedns` is the DNS server that the system believes it is using, in "host:port" style.
// The port is normally 53.
// `udpdns` and `tcpdns` are the location of the actual DNS server being used. For DNS
Expand Down
4 changes: 3 additions & 1 deletion outline/android/tun2socks.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ type OutlineTunnel interface {
// Returns an OutlineTunnel instance and does *not* take ownership of the TUN file descriptor; the
// caller is responsible for closing after OutlineTunnel disconnects.
//
// `fd` is the file descriptor to the VPN TUN device. Must be set to blocking mode.
// `fd` is the TUN device. The OutlineTunnel acquires an additional reference to it, which
// is released by OutlineTunnel.Disconnect(), so the caller must close `fd` _and_ call
// Disconnect() in order to close the TUN device.
// `host` is IP address of the Shadowsocks proxy server.
// `port` is the port of the Shadowsocks proxy server.
// `password` is the password of the Shadowsocks proxy.
Expand Down
2 changes: 1 addition & 1 deletion tunnel/intra.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ type intratunnel struct {
// `udpdns` and `tcpdns` are the actual location of the DNS server in use.
// These will normally be localhost with a high-numbered port.
// `dohdns` is the initial DOH transport.
// `tunWriter` is the downstream VPN tunnel
// `tunWriter` is the downstream VPN tunnel. IntraTunnel.Disconnect() will close `tunWriter`.
// `dialer` and `config` will be used for all network activity.
// `listener` will be notified at the completion of every tunneled socket.
func NewIntraTunnel(fakedns string, dohdns doh.Transport, tunWriter io.WriteCloser, dialer *net.Dialer, config *net.ListenConfig, listener IntraListener) (IntraTunnel, error) {
Expand Down
2 changes: 1 addition & 1 deletion tunnel/outline.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ type outlinetunnel struct {
// `password` is the password of the Shadowsocks proxy.
// `cipher` is the encryption cipher used by the Shadowsocks proxy.
// `isUDPEnabled` indicates if the Shadowsocks proxy and the network support proxying UDP traffic.
// `tunWriter` is used to output packets back to the TUN device.
// `tunWriter` is used to output packets back to the TUN device. OutlineTunnel.Disconnect() will close `tunWriter`.
func NewOutlineTunnel(host string, port int, password, cipher string, isUDPEnabled bool, tunWriter io.WriteCloser) (OutlineTunnel, error) {
if tunWriter == nil {
return nil, errors.New("Must provide a TUN writer")
Expand Down
11 changes: 10 additions & 1 deletion tunnel/tun.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,25 @@ import (

"github.com/eycorsican/go-tun2socks/common/log"
_ "github.com/eycorsican/go-tun2socks/common/log/simple" // Import simple log for the side effect of making logs printable.
"golang.org/x/sys/unix"
)

const vpnMtu = 1500

// MakeTunFile returns an os.File object from a TUN file descriptor `fd`.
// The returned os.File holds a separate reference to the underlying file,
// so the file will not be closed until both `fd` and the os.File are
// separately closed. (UNIX only.)
func MakeTunFile(fd int) (*os.File, error) {
if fd < 0 {
return nil, errors.New("Must provide a valid TUN file descriptor")
}
file := os.NewFile(uintptr(fd), "")
// Make a copy of `fd` so that os.File's finalizer doesn't close `fd`.
newfd, err := unix.Dup(fd)
if err != nil {
return nil, err
}
file := os.NewFile(uintptr(newfd), "")
if file == nil {
return nil, errors.New("Failed to open TUN file descriptor")
}
Expand Down
1 change: 1 addition & 0 deletions tunnel/tunnel.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func (t *tunnel) Disconnect() {
}
t.isConnected = false
t.lwipStack.Close()
t.tunWriter.Close()
}

func (t *tunnel) Write(data []byte) (int, error) {
Expand Down

2 comments on commit 9b609c0

@ignoramous
Copy link
Contributor

@ignoramous ignoramous commented on 9b609c0 Oct 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would an equivalent change to this be to send fd returned by ParcelFileDescriptor#detachFd instead of ParcelFileDescriptor#getFd from the Java land to tunnel.MakeTunFile but not use unix.Dup? Since, in this case, Java gives up ownership of the fd, the golang finalizer for os.File is free to close the fd whenever it sees fit (unless tunnel.Disconnect closes it beforehand, that is). Thoughts?

Refs:

https://github.com/google/vpn-reverse-tether/blob/304749e5c11d0466cfeef3220c91e91401c53167/src/vpntether/TetherService.java#L126

https://github.com/google/vpn-reverse-tether/blob/304749e5c11d0466cfeef3220c91e91401c53167/jni/forwarder.c#L171

@bemasc
Copy link
Contributor

@bemasc bemasc commented on 9b609c0 Oct 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that would work, but it makes seamless VPN handover trickier, as discussed in #62 (comment). Keeping the FD alive on both sides of the boundary allows Java to ensure that the FD isn't closed earlier than it wants.

Please sign in to comment.