Init commit

This commit is contained in:
世界 2022-07-11 17:15:22 +08:00
commit 4f7247190c
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
16 changed files with 573 additions and 0 deletions

6
.github/update_dependencies.sh vendored Executable file
View file

@ -0,0 +1,6 @@
#!/usr/bin/env bash
PROJECTS=$(dirname "$0")/../..
go get -x github.com/sagernet/sing@$(git -C $PROJECTS/sing rev-parse HEAD)
go mod tidy

40
.github/workflows/debug.yml vendored Normal file
View file

@ -0,0 +1,40 @@
name: Debug build
on:
push:
branches:
- dev
paths-ignore:
- '**.md'
- '.github/**'
- '!.github/workflows/debug.yml'
pull_request:
branches:
- dev
jobs:
build:
name: Debug build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Get latest go version
id: version
run: |
echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g')
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: ${{ steps.version.outputs.go_version }}
- name: Build
run: |
version=`git rev-parse HEAD`
mkdir build
pushd build
go mod init build
go get -v github.com/sagernet/sing-tun@$version
popd
go build -v ./...

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/.idea/
/vendor/

16
.golangci.yml Normal file
View file

@ -0,0 +1,16 @@
linters:
disable-all: true
enable:
- gofumpt
- govet
- gci
- staticcheck
linters-settings:
gci:
sections:
- standard
- prefix(github.com/sagernet/sing)
- default
staticcheck:
go: '1.18'

14
LICENSE Normal file
View file

@ -0,0 +1,14 @@
Copyright (C) 2022 by nekohasekai <contact-sagernet@sekai.icu>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

24
README.md Normal file
View file

@ -0,0 +1,24 @@
# sing-tun
Simple transparent proxy library.
Currently only for linux.
## License
```
Copyright (C) 2022 by nekohasekai <contact-sagernet@sekai.icu>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
```

7
format.go Normal file
View file

@ -0,0 +1,7 @@
package tun
//go:generate go install -v mvdan.cc/gofumpt@latest
//go:generate go install -v github.com/daixiang0/gci@latest
//go:generate gofumpt -l -w .
//go:generate gofmt -s -w .k
//go:generate gci write -s "standard,prefix(github.com/sagernet/),default" .

16
go.mod Normal file
View file

@ -0,0 +1,16 @@
module github.com/sagernet/sing-tun
go 1.18
require (
github.com/sagernet/sing v0.0.0-20220711062652-4394f7cbbae1
github.com/vishvananda/netlink v1.1.0
gvisor.dev/gvisor v0.0.0-20220711011657-cecae2f4234d
)
require (
github.com/google/btree v1.0.1 // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
)

17
go.sum Normal file
View file

@ -0,0 +1,17 @@
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/sagernet/sing v0.0.0-20220711062652-4394f7cbbae1 h1:gssTBQKiiXd1zALSOzQFZl3qwzCy4O76eSH0YY9A+Po=
github.com/sagernet/sing v0.0.0-20220711062652-4394f7cbbae1/go.mod h1:3ZmoGNg/nNJTyHAZFNRSPaXpNIwpDvyIiAUd0KIWV5c=
github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
gvisor.dev/gvisor v0.0.0-20220711011657-cecae2f4234d h1:KjI6i6P1ib9DiNdNIN8pb2TXfBewpKHf3O58cjj9vw4=
gvisor.dev/gvisor v0.0.0-20220711011657-cecae2f4234d/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=

139
gvisor.go Normal file
View file

@ -0,0 +1,139 @@
package tun
import (
"context"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
"gvisor.dev/gvisor/pkg/waiter"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
)
const defaultNIC tcpip.NICID = 1
type GVisorTun struct {
ctx context.Context
tunFd uintptr
tunMtu uint32
handler Handler
stack *stack.Stack
}
func NewGVisor(ctx context.Context, tunFd uintptr, tunMtu uint32, handler Handler) *GVisorTun {
return &GVisorTun{
ctx: ctx,
tunFd: tunFd,
tunMtu: tunMtu,
handler: handler,
}
}
func (t *GVisorTun) Start() error {
linkEndpoint, err := NewEndpoint(t.tunFd, t.tunMtu)
if err != nil {
return err
}
ipStack := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{
ipv4.NewProtocol,
ipv6.NewProtocol,
},
TransportProtocols: []stack.TransportProtocolFactory{
tcp.NewProtocol,
udp.NewProtocol,
icmp.NewProtocol4,
icmp.NewProtocol6,
},
})
tErr := ipStack.CreateNIC(defaultNIC, linkEndpoint)
if tErr != nil {
return E.New("create nic: ", tErr)
}
ipStack.SetRouteTable([]tcpip.Route{
{Destination: header.IPv4EmptySubnet, NIC: defaultNIC},
{Destination: header.IPv6EmptySubnet, NIC: defaultNIC},
})
ipStack.SetSpoofing(defaultNIC, true)
ipStack.SetPromiscuousMode(defaultNIC, true)
bufSize := 20 * 1024
ipStack.SetTransportProtocolOption(tcp.ProtocolNumber, &tcpip.TCPReceiveBufferSizeRangeOption{
Min: 1,
Default: bufSize,
Max: bufSize,
})
ipStack.SetTransportProtocolOption(tcp.ProtocolNumber, &tcpip.TCPSendBufferSizeRangeOption{
Min: 1,
Default: bufSize,
Max: bufSize,
})
sOpt := tcpip.TCPSACKEnabled(true)
ipStack.SetTransportProtocolOption(tcp.ProtocolNumber, &sOpt)
mOpt := tcpip.TCPModerateReceiveBufferOption(true)
ipStack.SetTransportProtocolOption(tcp.ProtocolNumber, &mOpt)
tcpForwarder := tcp.NewForwarder(ipStack, 0, 1024, func(r *tcp.ForwarderRequest) {
var wq waiter.Queue
endpoint, err := r.CreateEndpoint(&wq)
if err != nil {
r.Complete(true)
return
}
r.Complete(false)
endpoint.SocketOptions().SetKeepAlive(true)
tcpConn := gonet.NewTCPConn(&wq, endpoint)
lAddr := tcpConn.RemoteAddr()
rAddr := tcpConn.LocalAddr()
if lAddr == nil || rAddr == nil {
tcpConn.Close()
return
}
go func() {
var metadata M.Metadata
metadata.Source = M.SocksaddrFromNet(lAddr)
metadata.Destination = M.SocksaddrFromNet(rAddr)
t.handler.NewConnection(t.ctx, tcpConn, metadata)
}()
})
ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, func(id stack.TransportEndpointID, buffer *stack.PacketBuffer) bool {
return tcpForwarder.HandlePacket(id, buffer)
})
udpForwarder := udp.NewForwarder(ipStack, func(request *udp.ForwarderRequest) {
var wq waiter.Queue
endpoint, err := request.CreateEndpoint(&wq)
if err != nil {
return
}
udpConn := gonet.NewUDPConn(ipStack, &wq, endpoint)
lAddr := udpConn.RemoteAddr()
rAddr := udpConn.LocalAddr()
if lAddr == nil || rAddr == nil {
udpConn.Close()
return
}
go func() {
var metadata M.Metadata
metadata.Source = M.SocksaddrFromNet(lAddr)
metadata.Destination = M.SocksaddrFromNet(rAddr)
t.handler.NewPacketConnection(t.ctx, bufio.NewPacketConn(&bufio.UnbindPacketConn{ExtendedConn: bufio.NewExtendedConn(udpConn), Addr: M.SocksaddrFromNet(rAddr)}), metadata)
}()
})
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, udpForwarder.HandlePacket)
t.stack = ipStack
return nil
}
func (t *GVisorTun) Close() error {
return common.Close(
common.PtrOrNil(t.stack),
)
}

22
gvisor_linux.go Normal file
View file

@ -0,0 +1,22 @@
package tun
import (
"runtime"
"gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
func NewEndpoint(tunFd uintptr, tunMtu uint32) (stack.LinkEndpoint, error) {
var packetDispatchMode fdbased.PacketDispatchMode
if runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64" {
packetDispatchMode = fdbased.PacketMMap
} else {
packetDispatchMode = fdbased.RecvMMsg
}
return fdbased.New(&fdbased.Options{
FDs: []int{int(tunFd)},
MTU: tunMtu,
PacketDispatchMode: packetDispatchMode,
})
}

9
gvisor_nonlinux.go Normal file
View file

@ -0,0 +1,9 @@
//go:build !linux
package tun
import "gvisor.dev/gvisor/pkg/tcpip/stack"
func NewEndpoint(tunFd uintptr, tunMtu uint32) (stack.LinkEndpoint, error) {
return NewPosixEndpoint(tunFd, tunMtu)
}

118
gvisor_posix.go Normal file
View file

@ -0,0 +1,118 @@
package tun
import (
"os"
gBuffer "gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/rw"
)
var _ stack.LinkEndpoint = (*PosixEndpoint)(nil)
type PosixEndpoint struct {
fd uintptr
mtu uint32
file *os.File
dispatcher stack.NetworkDispatcher
}
func NewPosixEndpoint(tunFd uintptr, tunMtu uint32) (stack.LinkEndpoint, error) {
return &PosixEndpoint{
fd: tunFd,
mtu: tunMtu,
file: os.NewFile(tunFd, "tun"),
}, nil
}
func (e *PosixEndpoint) MTU() uint32 {
return e.mtu
}
func (e *PosixEndpoint) MaxHeaderLength() uint16 {
return 0
}
func (e *PosixEndpoint) LinkAddress() tcpip.LinkAddress {
return ""
}
func (e *PosixEndpoint) Capabilities() stack.LinkEndpointCapabilities {
return stack.CapabilityNone
}
func (e *PosixEndpoint) Attach(dispatcher stack.NetworkDispatcher) {
if dispatcher == nil && e.dispatcher != nil {
e.dispatcher = nil
return
}
if dispatcher != nil && e.dispatcher == nil {
e.dispatcher = dispatcher
go e.dispatchLoop()
}
}
func (e *PosixEndpoint) dispatchLoop() {
_buffer := buf.StackNewPacket()
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
defer buffer.Release()
for {
n, err := e.file.Read(buffer.FreeBytes())
if err != nil {
break
}
var view gBuffer.View
view.Append(buffer.To(n))
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
Payload: view,
IsForwardedPacket: true,
})
defer pkt.DecRef()
var p tcpip.NetworkProtocolNumber
ipHeader, ok := pkt.Data().PullUp(1)
if !ok {
continue
}
switch header.IPVersion(ipHeader) {
case header.IPv4Version:
p = header.IPv4ProtocolNumber
case header.IPv6Version:
p = header.IPv6ProtocolNumber
default:
continue
}
e.dispatcher.DeliverNetworkPacket(p, pkt)
}
}
func (e *PosixEndpoint) IsAttached() bool {
return e.dispatcher != nil
}
func (e *PosixEndpoint) Wait() {
}
func (e *PosixEndpoint) ARPHardwareType() header.ARPHardwareType {
return header.ARPHardwareNone
}
func (e *PosixEndpoint) AddHeader(buffer *stack.PacketBuffer) {
}
func (e *PosixEndpoint) WritePackets(pkts stack.PacketBufferList) (int, tcpip.Error) {
var n int
for _, packet := range pkts.AsSlice() {
_, err := rw.WriteV(e.fd, packet.Slices())
if err != nil {
return n, &tcpip.ErrAborted{}
}
n++
}
return n, nil
}

10
tun.go Normal file
View file

@ -0,0 +1,10 @@
package tun
import (
N "github.com/sagernet/sing/common/network"
)
type Handler interface {
N.TCPConnectionHandler
N.UDPConnectionHandler
}

113
tun_linux.go Normal file
View file

@ -0,0 +1,113 @@
package tun
import (
"net"
"net/netip"
"github.com/vishvananda/netlink"
"gvisor.dev/gvisor/pkg/tcpip/link/tun"
)
func Open(name string) (uintptr, error) {
tunFd, err := tun.Open(name)
if err != nil {
return 0, err
}
return uintptr(tunFd), nil
}
func Configure(name string, inet4Address netip.Prefix, inet6Address netip.Prefix, mtu uint32, autoRoute bool) error {
tunLink, err := netlink.LinkByName(name)
if err != nil {
return err
}
if inet4Address.IsValid() {
addr4, _ := netlink.ParseAddr(inet4Address.String())
err = netlink.AddrAdd(tunLink, addr4)
if err != nil {
return err
}
}
if inet6Address.IsValid() {
addr6, _ := netlink.ParseAddr(inet6Address.String())
err = netlink.AddrAdd(tunLink, addr6)
if err != nil {
return err
}
}
err = netlink.LinkSetMTU(tunLink, int(mtu))
if err != nil {
return err
}
err = netlink.LinkSetUp(tunLink)
if err != nil {
return err
}
if autoRoute {
if inet4Address.IsValid() {
err = netlink.RouteAdd(&netlink.Route{
Dst: &net.IPNet{
IP: net.IPv4zero,
Mask: net.CIDRMask(0, 32),
},
LinkIndex: tunLink.Attrs().Index,
})
if err != nil {
return err
}
}
if inet6Address.IsValid() {
err = netlink.RouteAdd(&netlink.Route{
Dst: &net.IPNet{
IP: net.IPv6zero,
Mask: net.CIDRMask(0, 128),
},
LinkIndex: tunLink.Attrs().Index,
})
if err != nil {
return err
}
}
}
return nil
}
func UnConfigure(name string, inet4Address netip.Prefix, inet6Address netip.Prefix, autoRoute bool) error {
if autoRoute {
tunLink, err := netlink.LinkByName(name)
if err != nil {
return err
}
if inet4Address.IsValid() {
err = netlink.RouteDel(&netlink.Route{
Dst: &net.IPNet{
IP: net.IPv4zero,
Mask: net.CIDRMask(0, 32),
},
LinkIndex: tunLink.Attrs().Index,
})
if err != nil {
return err
}
}
if inet6Address.IsValid() {
err = netlink.RouteDel(&netlink.Route{
Dst: &net.IPNet{
IP: net.IPv6zero,
Mask: net.CIDRMask(0, 128),
},
LinkIndex: tunLink.Attrs().Index,
})
if err != nil {
return err
}
}
}
return nil
}

20
tun_other.go Normal file
View file

@ -0,0 +1,20 @@
//go:build !linux
package tun
import (
"net/netip"
"os"
)
func Open(name string) (uintptr, error) {
return 0, os.ErrInvalid
}
func Configure(name string, inet4Address netip.Prefix, inet6Address netip.Prefix, mtu uint32, autoRoute bool) error {
return os.ErrInvalid
}
func UnConfigure(name string, inet4Address netip.Prefix, inet6Address netip.Prefix, autoRoute bool) error {
return os.ErrInvalid
}