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 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
|
||||
|
@ -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
|
||||
|
@ -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
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.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")
|
||||
|
Loading…
Reference in New Issue
Block a user