add h2 support

This commit is contained in:
NNdroid 2023-05-31 10:30:05 +08:00
parent 11e24d21fb
commit aeec15fe62
7 changed files with 342 additions and 3 deletions

View File

@ -19,6 +19,7 @@ A simple VPN written in Go.
* VPN over kcp
* VPN over utls
* VPN over dtls
* VPN over h2
# Usage
```
@ -46,7 +47,7 @@ Usage of vtun:
-obfs
enable data obfuscation
-p string
protocol udp/tls/grpc/quic/utls/dtls/ws/wss (default "udp")
protocol udp/tls/grpc/quic/utls/dtls/h2/ws/wss (default "udp")
-path string
websocket path (default "/freedom")
-privatekey string

View File

@ -19,6 +19,7 @@
* 支持kcp
* 支持utls
* 支持dtls
* 支持h2
# 用法
@ -47,7 +48,7 @@ Usage of vtun:
-obfs
enable data obfuscation
-p string
protocol udp/tls/grpc/quic/utls/dtls/ws/wss (default "udp")
protocol udp/tls/grpc/quic/utls/dtls/h2/ws/wss (default "udp")
-path string
websocket path (default "/freedom")
-privatekey string

View File

@ -2,6 +2,7 @@ package app
import (
"github.com/net-byte/vtun/dtls"
"github.com/net-byte/vtun/h2"
"github.com/net-byte/vtun/kcp"
"github.com/net-byte/vtun/quic"
"github.com/net-byte/vtun/utls"
@ -111,6 +112,12 @@ func (app *App) StartApp() {
} else {
dtls.StartClient(app.Iface, *app.Config)
}
case "h2":
if app.Config.ServerMode {
h2.StartServer(app.Iface, *app.Config)
} else {
h2.StartClient(app.Iface, *app.Config)
}
default:
if app.Config.ServerMode {
udp.StartServer(app.Iface, *app.Config)

145
h2/h2client.go Normal file
View File

@ -0,0 +1,145 @@
package h2
import (
"context"
"crypto/tls"
"fmt"
"github.com/golang/snappy"
"github.com/net-byte/vtun/common/cache"
"github.com/net-byte/vtun/common/cipher"
"github.com/net-byte/vtun/common/config"
"github.com/net-byte/vtun/common/counter"
"github.com/net-byte/vtun/common/netutil"
"github.com/net-byte/water"
"golang.org/x/net/http2"
"io"
"log"
"net/http"
"time"
)
// StartClient starts the h2 client
func StartClient(iFace *water.Interface, config config.Config) {
log.Println("vtun h2 client started")
go tunToH2(config, iFace)
tlsconfig := &tls.Config{
InsecureSkipVerify: config.TLSInsecureSkipVerify,
}
if config.TLSSni != "" {
tlsconfig.ServerName = config.TLSSni
}
client := &Client{
Client: &http.Client{
Transport: &http2.Transport{
TLSClientConfig: tlsconfig,
},
},
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
for {
conn, resp, err := client.Connect(ctx, fmt.Sprintf("https://%s%s", config.ServerAddr, config.WebSocketPath))
if err != nil {
log.Fatalf("Initiate conn: %s\n", err)
}
defer conn.Close()
if resp.StatusCode != http.StatusOK {
log.Fatalf("bad status code: %d\n", resp.StatusCode)
}
cache.GetCache().Set("h2conn", conn, 24*time.Hour)
h2ToTun(config, conn, iFace)
cache.GetCache().Delete("h2conn")
conn.Close()
}
}
// tunToH2 sends packets from tun to h2
func tunToH2(config config.Config, iFace *water.Interface) {
packet := make([]byte, config.BufferSize)
for {
n, err := iFace.Read(packet)
if err != nil {
netutil.PrintErr(err, config.Verbose)
break
}
if v, ok := cache.GetCache().Get("h2conn"); ok {
b := packet[:n]
if config.Obfs {
b = cipher.XOR(b)
}
if config.Compress {
b = snappy.Encode(nil, b)
}
conn := v.(*Conn)
n, err = conn.Write(b)
if err != nil {
netutil.PrintErr(err, config.Verbose)
continue
}
counter.IncrWrittenBytes(n)
}
}
}
// h2ToTun sends packets from h2 to tun
func h2ToTun(config config.Config, conn *Conn, iFace *water.Interface) {
packet := make([]byte, config.BufferSize)
for {
n, err := conn.Read(packet)
if err != nil {
netutil.PrintErr(err, config.Verbose)
break
}
b := packet[:n]
if config.Compress {
b, err = snappy.Decode(nil, b)
if err != nil {
netutil.PrintErr(err, config.Verbose)
break
}
}
if config.Obfs {
b = cipher.XOR(b)
}
_, err = iFace.Write(b)
if err != nil {
netutil.PrintErr(err, config.Verbose)
break
}
counter.IncrReadBytes(len(b))
}
}
type Client struct {
Method string
Header http.Header
Client *http.Client
}
func (c *Client) Connect(ctx context.Context, urlStr string) (*Conn, *http.Response, error) {
reader, writer := io.Pipe()
req, err := http.NewRequest(c.Method, urlStr, reader)
if err != nil {
return nil, nil, err
}
if c.Header != nil {
req.Header = c.Header
}
req = req.WithContext(ctx)
httpClient := c.Client
if httpClient == nil {
httpClient = defaultClient.Client
}
resp, err := httpClient.Do(req)
if err != nil {
return nil, nil, err
}
conn, ctx := newConn(req.Context(), resp.Body, writer)
resp.Request = req.WithContext(ctx)
return conn, resp, nil
}
var defaultClient = Client{
Method: http.MethodPost,
Client: &http.Client{Transport: &http2.Transport{}},
}

41
h2/h2conn.go Normal file
View File

@ -0,0 +1,41 @@
package h2
import (
"context"
"io"
"sync"
)
type Conn struct {
r io.Reader
wc io.WriteCloser
cancel context.CancelFunc
wLock sync.Mutex
rLock sync.Mutex
}
func newConn(ctx context.Context, r io.Reader, wc io.WriteCloser) (*Conn, context.Context) {
ctx, cancel := context.WithCancel(ctx)
return &Conn{
r: r,
wc: wc,
cancel: cancel,
}, ctx
}
func (c *Conn) Write(data []byte) (int, error) {
c.wLock.Lock()
defer c.wLock.Unlock()
return c.wc.Write(data)
}
func (c *Conn) Read(data []byte) (int, error) {
c.rLock.Lock()
defer c.rLock.Unlock()
return c.r.Read(data)
}
func (c *Conn) Close() error {
c.cancel()
return c.wc.Close()
}

144
h2/h2server.go Normal file
View File

@ -0,0 +1,144 @@
package h2
import (
"fmt"
"github.com/golang/snappy"
"github.com/net-byte/vtun/common/cache"
"github.com/net-byte/vtun/common/cipher"
"github.com/net-byte/vtun/common/config"
"github.com/net-byte/vtun/common/counter"
"github.com/net-byte/vtun/common/netutil"
"github.com/net-byte/water"
"io"
"log"
"net/http"
"time"
)
// StartServer starts the h2 server
func StartServer(iFace *water.Interface, config config.Config) {
log.Printf("vtun h2 server started on %v", config.LocalAddr)
mux := http.NewServeMux()
mux.Handle(config.WebSocketPath, http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
ServeHTTP(writer, request, config, iFace)
}))
srv := &http.Server{
Addr: config.LocalAddr,
Handler: mux,
}
go toClient(config, iFace)
log.Fatal(srv.ListenAndServeTLS(config.TLSCertificateFilePath, config.TLSCertificateKeyFilePath))
}
func ServeHTTP(w http.ResponseWriter, r *http.Request, config config.Config, iFace *water.Interface) {
conn, err := Accept(w, r)
if err != nil {
log.Printf("Failed creating connection from %s: %s", r.RemoteAddr, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
defer conn.Close()
toServer(conn, config, iFace)
}
// toClient sends packets from tun to h2
func toClient(config config.Config, iFace *water.Interface) {
packet := make([]byte, config.BufferSize)
for {
n, err := iFace.Read(packet)
if err != nil {
netutil.PrintErr(err, config.Verbose)
break
}
b := packet[:n]
if key := netutil.GetDstKey(b); key != "" {
if v, ok := cache.GetCache().Get(key); ok {
if config.Obfs {
b = cipher.XOR(b)
}
if config.Compress {
b = snappy.Encode(nil, b)
}
_, err = v.(*Conn).Write(b)
if err != nil {
cache.GetCache().Delete(key)
continue
}
counter.IncrWrittenBytes(n)
}
}
}
}
// toServer sends packets from h2 to tun
func toServer(conn *Conn, config config.Config, iFace *water.Interface) {
packet := make([]byte, config.BufferSize)
for {
n, err := conn.Read(packet)
if err != nil {
netutil.PrintErr(err, config.Verbose)
break
}
b := packet[:n]
if config.Compress {
b, err = snappy.Decode(nil, b)
if err != nil {
netutil.PrintErr(err, config.Verbose)
break
}
}
if config.Obfs {
b = cipher.XOR(b)
}
if key := netutil.GetSrcKey(b); key != "" {
cache.GetCache().Set(key, conn, 24*time.Hour)
iFace.Write(b)
counter.IncrReadBytes(len(b))
}
}
}
var ErrHTTP2NotSupported = fmt.Errorf("HTTP2 not supported")
type Server struct {
StatusCode int
}
func (u *Server) Accept(w http.ResponseWriter, r *http.Request) (*Conn, error) {
if !r.ProtoAtLeast(2, 0) {
return nil, ErrHTTP2NotSupported
}
flusher, ok := w.(http.Flusher)
if !ok {
return nil, ErrHTTP2NotSupported
}
c, ctx := newConn(r.Context(), r.Body, &flushWrite{w: w, f: flusher})
*r = *r.WithContext(ctx)
w.WriteHeader(u.StatusCode)
flusher.Flush()
return c, nil
}
var defaultUpgrade = Server{
StatusCode: http.StatusOK,
}
func Accept(w http.ResponseWriter, r *http.Request) (*Conn, error) {
return defaultUpgrade.Accept(w, r)
}
type flushWrite struct {
w io.Writer
f http.Flusher
}
func (w *flushWrite) Write(data []byte) (int, error) {
n, err := w.w.Write(data)
w.f.Flush()
return n, err
}
func (w *flushWrite) Close() error {
return nil
}

View File

@ -23,7 +23,7 @@ func main() {
flag.StringVar(&config.ServerIP, "sip", "172.16.0.1", "server ip")
flag.StringVar(&config.ServerIPv6, "sip6", "fced:9999::1", "server ipv6")
flag.StringVar(&config.Key, "k", "freedom@2023", "key")
flag.StringVar(&config.Protocol, "p", "udp", "protocol udp/tls/grpc/quic/utls/dtls/ws/wss")
flag.StringVar(&config.Protocol, "p", "udp", "protocol udp/tls/grpc/quic/utls/dtls/h2/ws/wss")
flag.StringVar(&config.WebSocketPath, "path", "/freedom", "websocket path")
flag.BoolVar(&config.ServerMode, "S", false, "server mode")
flag.BoolVar(&config.GlobalMode, "g", false, "client global mode")