mirror of
https://github.com/net-byte/vtun
synced 2024-03-14 10:50:03 +08:00
add h2 support
This commit is contained in:
parent
11e24d21fb
commit
aeec15fe62
@ -19,6 +19,7 @@ A simple VPN written in Go.
|
|||||||
* VPN over kcp
|
* VPN over kcp
|
||||||
* VPN over utls
|
* VPN over utls
|
||||||
* VPN over dtls
|
* VPN over dtls
|
||||||
|
* VPN over h2
|
||||||
# Usage
|
# Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -46,7 +47,7 @@ Usage of vtun:
|
|||||||
-obfs
|
-obfs
|
||||||
enable data obfuscation
|
enable data obfuscation
|
||||||
-p string
|
-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
|
-path string
|
||||||
websocket path (default "/freedom")
|
websocket path (default "/freedom")
|
||||||
-privatekey string
|
-privatekey string
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
* 支持kcp
|
* 支持kcp
|
||||||
* 支持utls
|
* 支持utls
|
||||||
* 支持dtls
|
* 支持dtls
|
||||||
|
* 支持h2
|
||||||
|
|
||||||
# 用法
|
# 用法
|
||||||
|
|
||||||
@ -47,7 +48,7 @@ Usage of vtun:
|
|||||||
-obfs
|
-obfs
|
||||||
enable data obfuscation
|
enable data obfuscation
|
||||||
-p string
|
-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
|
-path string
|
||||||
websocket path (default "/freedom")
|
websocket path (default "/freedom")
|
||||||
-privatekey string
|
-privatekey string
|
||||||
|
@ -2,6 +2,7 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/net-byte/vtun/dtls"
|
"github.com/net-byte/vtun/dtls"
|
||||||
|
"github.com/net-byte/vtun/h2"
|
||||||
"github.com/net-byte/vtun/kcp"
|
"github.com/net-byte/vtun/kcp"
|
||||||
"github.com/net-byte/vtun/quic"
|
"github.com/net-byte/vtun/quic"
|
||||||
"github.com/net-byte/vtun/utls"
|
"github.com/net-byte/vtun/utls"
|
||||||
@ -111,6 +112,12 @@ func (app *App) StartApp() {
|
|||||||
} else {
|
} else {
|
||||||
dtls.StartClient(app.Iface, *app.Config)
|
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:
|
default:
|
||||||
if app.Config.ServerMode {
|
if app.Config.ServerMode {
|
||||||
udp.StartServer(app.Iface, *app.Config)
|
udp.StartServer(app.Iface, *app.Config)
|
||||||
|
145
h2/h2client.go
Normal file
145
h2/h2client.go
Normal 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
41
h2/h2conn.go
Normal 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
144
h2/h2server.go
Normal 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
|
||||||
|
}
|
2
main.go
2
main.go
@ -23,7 +23,7 @@ func main() {
|
|||||||
flag.StringVar(&config.ServerIP, "sip", "172.16.0.1", "server ip")
|
flag.StringVar(&config.ServerIP, "sip", "172.16.0.1", "server ip")
|
||||||
flag.StringVar(&config.ServerIPv6, "sip6", "fced:9999::1", "server ipv6")
|
flag.StringVar(&config.ServerIPv6, "sip6", "fced:9999::1", "server ipv6")
|
||||||
flag.StringVar(&config.Key, "k", "freedom@2023", "key")
|
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.StringVar(&config.WebSocketPath, "path", "/freedom", "websocket path")
|
||||||
flag.BoolVar(&config.ServerMode, "S", false, "server mode")
|
flag.BoolVar(&config.ServerMode, "S", false, "server mode")
|
||||||
flag.BoolVar(&config.GlobalMode, "g", false, "client global mode")
|
flag.BoolVar(&config.GlobalMode, "g", false, "client global mode")
|
||||||
|
Loading…
Reference in New Issue
Block a user