Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions proxy/tun/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,25 @@ route add 1.1.1.1 mask 255.0.0.0 0.0.0.0 if 47
Note on ipv6 support. \
Despite Windows also giving the adapter autoconfigured ipv6 address, the ipv6 is not possible until the interface has any _routable_ ipv6 address (given link-local address will not accept traffic from external addresses). \
So everything applicable for ipv4 above also works for ipv6, you only need to give the interface some address manually, e.g. anything private like fc00::a:b:c:d/64 will do just fine

## MAC OS X SUPPORT

Darwin (Mac OS X) support of the same functionality is implemented through utun (userspace tunnel).

Interface name in the configuration must comply to the scheme "utunN", where N is some number. \
Most running OS'es create some amount of utun interfaces in advance for own needs. Please either check the interfaces you already have occupied by issuing following command:
```
ifconfig
```
Produced list will have all system interfaces listed, from which you will see how many "utun" ones already exists.
It's not required to select next available number, e.g. if you have utun1-utun7 interfaces, it's not required to have "utun8" in the config. You can choose any available name, even utun20, to get surely available interface number.

To attach routing to the interface, route command like following can be executed:
```
sudo route add -net 1.1.1.0/24 -iface utun10
```
```
sudo route add -inet6 -host 2606:4700:4700::1111 -iface utun10
sudo route add -inet6 -host 2606:4700:4700::1001 -iface utun10
```
Important to remember that everything written above about Linux routing concept, also apply to Mac OS X. If you simply route default route through utun interface, that will result network loop and immediate network failure.
155 changes: 155 additions & 0 deletions proxy/tun/stack_gvisor_endpoint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package tun

import (
"context"
"errors"

"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)

var ErrQueueEmpty = errors.New("queue is empty")

type GVisorDevice interface {
WritePacket(packet *stack.PacketBuffer) tcpip.Error
ReadPacket() (byte, *stack.PacketBuffer, error)
Wait()
}

// LinkEndpoint implements GVisor stack.LinkEndpoint
var _ stack.LinkEndpoint = (*LinkEndpoint)(nil)

type LinkEndpoint struct {
deviceMTU uint32
device GVisorDevice
dispatcherCancel context.CancelFunc
}

func (e *LinkEndpoint) MTU() uint32 {
return e.deviceMTU
}

func (e *LinkEndpoint) SetMTU(_ uint32) {
// not Implemented, as it is not expected GVisor will be asking tun device to be modified
}

func (e *LinkEndpoint) MaxHeaderLength() uint16 {
return 0
}

func (e *LinkEndpoint) LinkAddress() tcpip.LinkAddress {
return ""
}

func (e *LinkEndpoint) SetLinkAddress(_ tcpip.LinkAddress) {
// not Implemented, as it is not expected GVisor will be asking tun device to be modified
}

func (e *LinkEndpoint) Capabilities() stack.LinkEndpointCapabilities {
return stack.CapabilityRXChecksumOffload
}

func (e *LinkEndpoint) Attach(dispatcher stack.NetworkDispatcher) {
if e.dispatcherCancel != nil {
e.dispatcherCancel()
e.dispatcherCancel = nil
}

if dispatcher != nil {
ctx, cancel := context.WithCancel(context.Background())
go e.dispatchLoop(ctx, dispatcher)
e.dispatcherCancel = cancel
}
}

func (e *LinkEndpoint) IsAttached() bool {
return e.dispatcherCancel != nil
}

func (e *LinkEndpoint) Wait() {

}

func (e *LinkEndpoint) ARPHardwareType() header.ARPHardwareType {
return header.ARPHardwareNone
}

func (e *LinkEndpoint) AddHeader(buffer *stack.PacketBuffer) {
// tun interface doesn't have link layer header, it will be added by the OS
}

func (e *LinkEndpoint) ParseHeader(ptr *stack.PacketBuffer) bool {
return true
}

func (e *LinkEndpoint) Close() {
if e.dispatcherCancel != nil {
e.dispatcherCancel()
e.dispatcherCancel = nil
}
}

func (e *LinkEndpoint) SetOnCloseAction(_ func()) {

}

func (e *LinkEndpoint) WritePackets(packetBufferList stack.PacketBufferList) (int, tcpip.Error) {
var n int
var err tcpip.Error

for _, packetBuffer := range packetBufferList.AsSlice() {
err = e.device.WritePacket(packetBuffer)
if err != nil {
return n, &tcpip.ErrAborted{}
}
n++
}

return n, nil
}

func (e *LinkEndpoint) dispatchLoop(ctx context.Context, dispatcher stack.NetworkDispatcher) {
var networkProtocolNumber tcpip.NetworkProtocolNumber
var version byte
var packet *stack.PacketBuffer
var err error

for {
select {
case <-ctx.Done():
return
default:
version, packet, err = e.device.ReadPacket()
// on "queue empty", ask device to yield slightly and continue
if errors.Is(err, ErrQueueEmpty) {
e.device.Wait()
continue
}
// stop dispatcher loop on any other interface failure
if err != nil {
e.Attach(nil)
return
}

// extract network protocol number from the packet first byte
// (which is returned separately, since it is so incredibly hard to extract one byte from
// stack.PacketBuffer without additional memory allocation and full copying it back and forth)
switch version {
case 4:
networkProtocolNumber = header.IPv4ProtocolNumber
case 6:
networkProtocolNumber = header.IPv6ProtocolNumber
default:
// discard unknown network protocol packet
packet.DecRef()
continue
}

// dispatch the buffer to the stack
dispatcher.DeliverNetworkPacket(networkProtocolNumber, packet)
// signal the buffer that it can be released
packet.DecRef()
}
}
}
Loading