v3
This commit is contained in:
@@ -238,20 +238,25 @@ type vpnClient struct {
|
||||
ipv4, ipv6 netip.Addr
|
||||
mtu int
|
||||
timeout time.Duration
|
||||
batchDelay time.Duration
|
||||
reconnectEvery int
|
||||
upSizer, downSizer *adaptiveSizer
|
||||
control, upload, download *txnLane
|
||||
upPackets, downPackets, upBytes, downBytes atomic.Uint64
|
||||
upBatches, downBatches, localDropped atomic.Uint64
|
||||
stopped chan struct{}
|
||||
stopOnce sync.Once
|
||||
}
|
||||
|
||||
func newVPNClient(tun *os.File, addr, token string, v4, v6 netip.Addr, mtu, start, min, max, growAfter, reconnectEvery int, timeout time.Duration, adaptLog bool) (*vpnClient, error) {
|
||||
func newVPNClient(tun *os.File, addr, token string, v4, v6 netip.Addr, mtu, start, min, max, growAfter, reconnectEvery int, timeout, batchDelay time.Duration, adaptLog bool) (*vpnClient, error) {
|
||||
sid, err := randomSID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &vpnClient{tun: tun, sid: sid, serverAddr: addr, token: token, ipv4: v4, ipv6: v6, mtu: mtu, timeout: timeout, reconnectEvery: reconnectEvery,
|
||||
if batchDelay < 0 {
|
||||
batchDelay = 0
|
||||
}
|
||||
return &vpnClient{tun: tun, sid: sid, serverAddr: addr, token: token, ipv4: v4, ipv6: v6, mtu: mtu, timeout: timeout, batchDelay: batchDelay, reconnectEvery: reconnectEvery,
|
||||
upSizer: newSizer("upload", start, min, max, growAfter, adaptLog), downSizer: newSizer("download", start, min, max, growAfter, adaptLog),
|
||||
control: newTxnLane(addr, timeout, reconnectEvery), upload: newTxnLane(addr, timeout, reconnectEvery), download: newTxnLane(addr, timeout, reconnectEvery), stopped: make(chan struct{})}, nil
|
||||
}
|
||||
@@ -296,30 +301,113 @@ func (v *vpnClient) close() {
|
||||
})
|
||||
}
|
||||
|
||||
func (v *vpnClient) uploadLoop(errs chan<- error) {
|
||||
buf := make([]byte, 65535)
|
||||
var seq uint32
|
||||
func (v *vpnClient) logLocalDrop(reason string) {
|
||||
n := v.localDropped.Add(1)
|
||||
// Link-local/control traffic can be noisy. Keep it visible without filling
|
||||
// the Android live log or making a harmless packet fatal to the VPN.
|
||||
if n <= 8 || n%256 == 0 {
|
||||
fmt.Printf("VPN DROP local packet (%s) dropped=%d\n", reason, n)
|
||||
}
|
||||
}
|
||||
|
||||
func (v *vpnClient) tunReadLoop(out chan<- []byte, errs chan<- error) {
|
||||
buf := make([]byte, protocol.VPNMaxPacket)
|
||||
for {
|
||||
n, err := v.tun.Read(buf)
|
||||
if err != nil {
|
||||
errs <- err
|
||||
return
|
||||
}
|
||||
if n < 1 {
|
||||
if n < 1 || n > protocol.VPNMaxPacket {
|
||||
continue
|
||||
}
|
||||
packet := append([]byte(nil), buf[:n]...)
|
||||
if n > 65535 {
|
||||
src, _, err := protocol.PacketAddresses(packet)
|
||||
if err != nil {
|
||||
v.logLocalDrop(err.Error())
|
||||
continue
|
||||
}
|
||||
if src != v.ipv4 && src != v.ipv6 {
|
||||
v.logLocalDrop(fmt.Sprintf("source %s is not assigned VPN address", src))
|
||||
continue
|
||||
}
|
||||
select {
|
||||
case out <- packet:
|
||||
case <-v.stopped:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func batchWireSize(packets [][]byte) int {
|
||||
n := 1
|
||||
for _, p := range packets {
|
||||
n += 2 + len(p)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (v *vpnClient) uploadLoop(in <-chan []byte, errs chan<- error) {
|
||||
var seq uint32
|
||||
var carry []byte
|
||||
for {
|
||||
var first []byte
|
||||
if carry != nil {
|
||||
first, carry = carry, nil
|
||||
} else {
|
||||
select {
|
||||
case first = <-in:
|
||||
case <-v.stopped:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
packets := [][]byte{first}
|
||||
encodedSize := 1 + 2 + len(first)
|
||||
timer := time.NewTimer(v.batchDelay)
|
||||
collect:
|
||||
for encodedSize < protocol.VPNMaxBatch {
|
||||
select {
|
||||
case p := <-in:
|
||||
need := 2 + len(p)
|
||||
if encodedSize+need > protocol.VPNMaxBatch {
|
||||
carry = p
|
||||
break collect
|
||||
}
|
||||
packets = append(packets, p)
|
||||
encodedSize += need
|
||||
case <-timer.C:
|
||||
break collect
|
||||
case <-v.stopped:
|
||||
if !timer.Stop() {
|
||||
select {
|
||||
case <-timer.C:
|
||||
default:
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
if !timer.Stop() {
|
||||
select {
|
||||
case <-timer.C:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
batch, err := protocol.BuildVPNBatch(packets)
|
||||
if err != nil {
|
||||
errs <- err
|
||||
return
|
||||
}
|
||||
offset := 0
|
||||
for offset < n {
|
||||
for offset < len(batch) {
|
||||
limit := v.upSizer.Current()
|
||||
size := n - offset
|
||||
size := len(batch) - offset
|
||||
if size > limit {
|
||||
size = limit
|
||||
}
|
||||
req, e := protocol.BuildVPNPush(v.sid, seq, offset, n, packet[offset:offset+size])
|
||||
req, e := protocol.BuildVPNPush(v.sid, seq, offset, len(batch), batch[offset:offset+size])
|
||||
if e != nil {
|
||||
errs <- e
|
||||
return
|
||||
@@ -334,16 +422,20 @@ func (v *vpnClient) uploadLoop(errs chan<- error) {
|
||||
errs <- e
|
||||
return
|
||||
}
|
||||
if rseq != seq || accepted < offset || accepted > n {
|
||||
if rseq != seq || accepted < offset || accepted > len(batch) {
|
||||
errs <- errors.New("bad server upload ACK")
|
||||
return
|
||||
}
|
||||
fullRecord := size == limit
|
||||
v.upSizer.Success(size, fullRecord)
|
||||
v.upSizer.Success(size, size == limit)
|
||||
offset = accepted
|
||||
}
|
||||
v.upPackets.Add(1)
|
||||
v.upBytes.Add(uint64(n))
|
||||
var rawBytes uint64
|
||||
for _, p := range packets {
|
||||
rawBytes += uint64(len(p))
|
||||
}
|
||||
v.upPackets.Add(uint64(len(packets)))
|
||||
v.upBytes.Add(rawBytes)
|
||||
v.upBatches.Add(1)
|
||||
seq++
|
||||
}
|
||||
}
|
||||
@@ -352,7 +444,7 @@ func (v *vpnClient) downloadLoop(errs chan<- error) {
|
||||
var want uint32
|
||||
ack := protocol.VPNNoAck
|
||||
offset := 0
|
||||
var packet []byte
|
||||
var transfer []byte
|
||||
total := 0
|
||||
for {
|
||||
limit := v.downSizer.Current()
|
||||
@@ -374,42 +466,58 @@ func (v *vpnClient) downloadLoop(errs chan<- error) {
|
||||
if wait {
|
||||
continue
|
||||
}
|
||||
if seq != want || roff != offset || rtotal < 1 || rtotal > 65535 {
|
||||
if seq != want || roff != offset || rtotal < 1 || rtotal > protocol.VPNMaxBatch {
|
||||
errs <- errors.New("bad server download sequence")
|
||||
return
|
||||
}
|
||||
if offset == 0 {
|
||||
total = rtotal
|
||||
packet = make([]byte, 0, total)
|
||||
transfer = make([]byte, 0, total)
|
||||
} else if rtotal != total {
|
||||
errs <- errors.New("download packet size changed")
|
||||
errs <- errors.New("download transfer size changed")
|
||||
return
|
||||
}
|
||||
packet = append(packet, data...)
|
||||
transfer = append(transfer, data...)
|
||||
offset += len(data)
|
||||
v.downSizer.Success(len(data), len(data) == limit)
|
||||
if offset < total {
|
||||
continue
|
||||
}
|
||||
if offset != total {
|
||||
errs <- errors.New("download packet overflow")
|
||||
errs <- errors.New("download transfer overflow")
|
||||
return
|
||||
}
|
||||
n, e := v.tun.Write(packet)
|
||||
packets, e := protocol.ParseVPNBatch(transfer)
|
||||
if e != nil {
|
||||
errs <- e
|
||||
return
|
||||
// Compatibility with the first packet-VPN build, which used one raw
|
||||
// IP packet as each transfer object.
|
||||
if len(transfer) > 0 && (transfer[0]>>4 == 4 || transfer[0]>>4 == 6) {
|
||||
packets = [][]byte{transfer}
|
||||
} else {
|
||||
errs <- e
|
||||
return
|
||||
}
|
||||
}
|
||||
if n != len(packet) {
|
||||
errs <- io.ErrShortWrite
|
||||
return
|
||||
var rawBytes uint64
|
||||
for _, packet := range packets {
|
||||
n, e := v.tun.Write(packet)
|
||||
if e != nil {
|
||||
errs <- e
|
||||
return
|
||||
}
|
||||
if n != len(packet) {
|
||||
errs <- io.ErrShortWrite
|
||||
return
|
||||
}
|
||||
rawBytes += uint64(n)
|
||||
}
|
||||
v.downPackets.Add(1)
|
||||
v.downBytes.Add(uint64(n))
|
||||
v.downPackets.Add(uint64(len(packets)))
|
||||
v.downBytes.Add(rawBytes)
|
||||
v.downBatches.Add(1)
|
||||
ack = want
|
||||
want++
|
||||
offset = 0
|
||||
packet = nil
|
||||
transfer = nil
|
||||
total = 0
|
||||
}
|
||||
}
|
||||
@@ -419,8 +527,10 @@ func (v *vpnClient) run() error {
|
||||
return err
|
||||
}
|
||||
fmt.Println("VPN READY")
|
||||
errs := make(chan error, 2)
|
||||
go v.uploadLoop(errs)
|
||||
errs := make(chan error, 3)
|
||||
packets := make(chan []byte, 256)
|
||||
go v.tunReadLoop(packets, errs)
|
||||
go v.uploadLoop(packets, errs)
|
||||
go v.downloadLoop(errs)
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
defer ticker.Stop()
|
||||
@@ -429,7 +539,7 @@ func (v *vpnClient) run() error {
|
||||
case err := <-errs:
|
||||
return err
|
||||
case <-ticker.C:
|
||||
fmt.Printf("STATS up_packets=%d down_packets=%d up_bytes=%d down_bytes=%d upload_chunk=%d download_chunk=%d pollers=1\n", v.upPackets.Load(), v.downPackets.Load(), v.upBytes.Load(), v.downBytes.Load(), v.upSizer.Current(), v.downSizer.Current())
|
||||
fmt.Printf("STATS up_packets=%d down_packets=%d up_batches=%d down_batches=%d up_bytes=%d down_bytes=%d local_dropped=%d upload_chunk=%d download_chunk=%d pollers=1\n", v.upPackets.Load(), v.downPackets.Load(), v.upBatches.Load(), v.downBatches.Load(), v.upBytes.Load(), v.downBytes.Load(), v.localDropped.Load(), v.upSizer.Current(), v.downSizer.Current())
|
||||
case <-v.stopped:
|
||||
return nil
|
||||
}
|
||||
@@ -445,11 +555,12 @@ func main() {
|
||||
ipv4Text := flag.String("vpn-ipv4", "10.123.0.2", "client VPN IPv4 address")
|
||||
ipv6Text := flag.String("vpn-ipv6", "fd7a:4472:6167:6f6e::2", "client VPN IPv6 address")
|
||||
mtu := flag.Int("vpn-mtu", 1280, "VPN interface MTU")
|
||||
chunkMax := flag.Int("chunk-max", 65535, "maximum adaptive record bytes")
|
||||
chunkMax := flag.Int("chunk-max", protocol.VPNMaxFragment, "maximum adaptive record bytes (up to 1 MiB)")
|
||||
chunkMin := flag.Int("chunk-min", 32, "minimum adaptive record bytes")
|
||||
chunkStart := flag.Int("chunk-start", 65535, "starting record bytes; app sets this equal to max")
|
||||
chunkStart := flag.Int("chunk-start", protocol.VPNMaxFragment, "starting record bytes; app sets this equal to max")
|
||||
growAfter := flag.Int("chunk-grow-after", 64, "full successful records before increasing chunk size")
|
||||
timeout := flag.Duration("chunk-timeout", 2*time.Second, "framed transaction timeout")
|
||||
batchDelay := flag.Duration("batch-delay", time.Millisecond, "maximum delay used to combine adjacent TUN packets into one transfer object")
|
||||
reconnectEvery := flag.Int("chunk-reconnect-every", 32, "reconnect a TCP/53 lane after this many transactions; 0 keeps it open")
|
||||
adaptLog := flag.Bool("chunk-adapt-log", false, "log adaptive chunk changes")
|
||||
flag.Parse()
|
||||
@@ -496,7 +607,7 @@ func main() {
|
||||
}
|
||||
}
|
||||
addr := net.JoinHostPort(*serverHost, strconv.Itoa(*serverPort))
|
||||
client, err := newVPNClient(tun, addr, *token, v4, v6, *mtu, *chunkStart, *chunkMin, *chunkMax, *growAfter, *reconnectEvery, *timeout, *adaptLog)
|
||||
client, err := newVPNClient(tun, addr, *token, v4, v6, *mtu, *chunkStart, *chunkMin, *chunkMax, *growAfter, *reconnectEvery, *timeout, *batchDelay, *adaptLog)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
|
||||
Reference in New Issue
Block a user