You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tailscale-android/cmd/tailscale/multitun.go

288 lines
6.0 KiB
Go

// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"os"
"github.com/tailscale/wireguard-go/tun"
)
// multiTUN implements a tun.Device that supports multiple
// underlying devices. This is necessary because Android VPN devices
// have static configurations and wgengine.NewUserspaceEngineAdvanced
// assumes a single static tun.Device.
type multiTUN struct {
// devices is for adding new devices.
devices chan tun.Device
// event is the combined event channel from all active devices.
events chan tun.Event
close chan struct{}
closeErr chan error
reads chan ioRequest
writes chan ioRequest
flushes chan chan error
mtus chan chan mtuReply
names chan chan nameReply
shutdowns chan struct{}
}
// tunDevice wraps and drives a single run.Device.
type tunDevice struct {
dev tun.Device
// close closes the device.
close chan struct{}
closeDone chan error
// readDone is notified when the read goroutine is done.
readDone chan struct{}
}
type ioRequest struct {
data []byte
offset int
reply chan<- ioReply
}
type ioReply struct {
bytes int
err error
}
type mtuReply struct {
mtu int
err error
}
type nameReply struct {
name string
err error
}
func newTUNDevices() *multiTUN {
d := &multiTUN{
devices: make(chan tun.Device),
events: make(chan tun.Event),
close: make(chan struct{}),
closeErr: make(chan error),
reads: make(chan ioRequest),
writes: make(chan ioRequest),
flushes: make(chan chan error),
mtus: make(chan chan mtuReply),
names: make(chan chan nameReply),
shutdowns: make(chan struct{}),
}
go d.run()
return d
}
func (d *multiTUN) run() {
var devices []*tunDevice
// readDone is the readDone channel of the device being read from.
var readDone chan struct{}
// runDone is the closeDone channel of the device being written to.
var runDone chan error
for {
select {
case <-readDone:
// The oldest device has reached EOF, replace it.
n := copy(devices, devices[1:])
devices = devices[:n]
if len(devices) > 0 {
// Start reading from the next device.
dev := devices[0]
readDone = dev.readDone
go d.readFrom(dev)
}
case <-runDone:
// A device completed runDevice, replace it.
if len(devices) > 0 {
dev := devices[len(devices)-1]
runDone = dev.closeDone
go d.runDevice(dev)
}
case <-d.shutdowns:
// Shut down all devices.
for _, dev := range devices {
close(dev.close)
<-dev.closeDone
<-dev.readDone
}
devices = nil
case <-d.close:
var derr error
for _, dev := range devices {
if err := <-dev.closeDone; err != nil {
derr = err
}
}
d.closeErr <- derr
return
case dev := <-d.devices:
if len(devices) > 0 {
// Ask the most recent device to stop.
prev := devices[len(devices)-1]
close(prev.close)
}
wrap := &tunDevice{
dev: dev,
close: make(chan struct{}),
closeDone: make(chan error),
readDone: make(chan struct{}, 1),
}
if len(devices) == 0 {
// Start using this first device.
readDone = wrap.readDone
go d.readFrom(wrap)
runDone = wrap.closeDone
go d.runDevice(wrap)
}
devices = append(devices, wrap)
case f := <-d.flushes:
var err error
if len(devices) > 0 {
dev := devices[len(devices)-1]
err = dev.dev.Flush()
}
f <- err
case m := <-d.mtus:
r := mtuReply{mtu: defaultMTU}
if len(devices) > 0 {
dev := devices[len(devices)-1]
r.mtu, r.err = dev.dev.MTU()
}
m <- r
case n := <-d.names:
var r nameReply
if len(devices) > 0 {
dev := devices[len(devices)-1]
r.name, r.err = dev.dev.Name()
}
n <- r
}
}
}
func (d *multiTUN) readFrom(dev *tunDevice) {
defer func() {
dev.readDone <- struct{}{}
}()
for {
select {
case r := <-d.reads:
n, err := dev.dev.Read(r.data, r.offset)
stop := false
if err != nil {
select {
case <-dev.close:
stop = true
err = nil
default:
}
}
r.reply <- ioReply{n, err}
if stop {
return
}
case <-d.close:
return
}
}
}
func (d *multiTUN) runDevice(dev *tunDevice) {
defer func() {
// The documentation for https://developer.android.com/reference/android/net/VpnService.Builder#establish()
// states that "Therefore, after draining the old file
// descriptor...", but pending Reads are never unblocked
// when a new descriptor is created.
//
// Close it instead and hope that no packets are lost.
dev.closeDone <- dev.dev.Close()
}()
// Pump device events.
go func() {
for {
select {
case e := <-dev.dev.Events():
d.events <- e
case <-dev.close:
return
}
}
}()
for {
select {
case w := <-d.writes:
n, err := dev.dev.Write(w.data, w.offset)
w.reply <- ioReply{n, err}
case <-dev.close:
// Device closed.
return
case <-d.close:
// Multi-device closed.
return
}
}
}
func (d *multiTUN) add(dev tun.Device) {
d.devices <- dev
}
func (d *multiTUN) File() *os.File {
// The underlying file descriptor is not constant on Android.
// Let's hope no-one uses it.
panic("not available on Android")
}
func (d *multiTUN) Read(data []byte, offset int) (int, error) {
r := make(chan ioReply)
d.reads <- ioRequest{data, offset, r}
rep := <-r
return rep.bytes, rep.err
}
func (d *multiTUN) Write(data []byte, offset int) (int, error) {
r := make(chan ioReply)
d.writes <- ioRequest{data, offset, r}
rep := <-r
return rep.bytes, rep.err
}
func (d *multiTUN) Flush() error {
r := make(chan error)
d.flushes <- r
return <-r
}
func (d *multiTUN) MTU() (int, error) {
r := make(chan mtuReply)
d.mtus <- r
rep := <-r
return rep.mtu, rep.err
}
func (d *multiTUN) Name() (string, error) {
r := make(chan nameReply)
d.names <- r
rep := <-r
return rep.name, rep.err
}
func (d *multiTUN) Events() chan tun.Event {
return d.events
}
func (d *multiTUN) Shutdown() {
d.shutdowns <- struct{}{}
}
func (d *multiTUN) Close() error {
close(d.close)
return <-d.closeErr
}