mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2024-11-16 19:56:51 +08:00
Feature: vmess add websocket support
This commit is contained in:
parent
bcba14e05e
commit
d2174149c1
|
@ -38,6 +38,8 @@ type VmessOption struct {
|
||||||
AlterID int `proxy:"alterId"`
|
AlterID int `proxy:"alterId"`
|
||||||
Cipher string `proxy:"cipher"`
|
Cipher string `proxy:"cipher"`
|
||||||
TLS bool `proxy:"tls,omitempty"`
|
TLS bool `proxy:"tls,omitempty"`
|
||||||
|
Network string `proxy:"network,omitempty"`
|
||||||
|
WSPath string `proxy:"ws-path,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ss *Vmess) Name() string {
|
func (ss *Vmess) Name() string {
|
||||||
|
@ -54,17 +56,20 @@ func (ss *Vmess) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err er
|
||||||
return nil, fmt.Errorf("%s connect error", ss.server)
|
return nil, fmt.Errorf("%s connect error", ss.server)
|
||||||
}
|
}
|
||||||
tcpKeepAlive(c)
|
tcpKeepAlive(c)
|
||||||
c = ss.client.New(c, parseVmessAddr(metadata))
|
c, err = ss.client.New(c, parseVmessAddr(metadata))
|
||||||
return &VmessAdapter{conn: c}, err
|
return &VmessAdapter{conn: c}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewVmess(option VmessOption) (*Vmess, error) {
|
func NewVmess(option VmessOption) (*Vmess, error) {
|
||||||
security := strings.ToLower(option.Cipher)
|
security := strings.ToLower(option.Cipher)
|
||||||
client, err := vmess.NewClient(vmess.Config{
|
client, err := vmess.NewClient(vmess.Config{
|
||||||
UUID: option.UUID,
|
UUID: option.UUID,
|
||||||
AlterID: uint16(option.AlterID),
|
AlterID: uint16(option.AlterID),
|
||||||
Security: security,
|
Security: security,
|
||||||
TLS: option.TLS,
|
TLS: option.TLS,
|
||||||
|
Host: fmt.Sprintf("%s:%d", option.Server, option.Port),
|
||||||
|
NetWork: option.Network,
|
||||||
|
WebSocketPath: option.WSPath,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -5,9 +5,12 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
|
"net/url"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gofrs/uuid"
|
"github.com/gofrs/uuid"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Version of vmess
|
// Version of vmess
|
||||||
|
@ -62,27 +65,69 @@ type DstAddr struct {
|
||||||
|
|
||||||
// Client is vmess connection generator
|
// Client is vmess connection generator
|
||||||
type Client struct {
|
type Client struct {
|
||||||
user []*ID
|
user []*ID
|
||||||
uuid *uuid.UUID
|
uuid *uuid.UUID
|
||||||
security Security
|
security Security
|
||||||
tls bool
|
tls bool
|
||||||
|
host string
|
||||||
|
websocket bool
|
||||||
|
websocketPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config of vmess
|
// Config of vmess
|
||||||
type Config struct {
|
type Config struct {
|
||||||
UUID string
|
UUID string
|
||||||
AlterID uint16
|
AlterID uint16
|
||||||
Security string
|
Security string
|
||||||
TLS bool
|
TLS bool
|
||||||
|
Host string
|
||||||
|
NetWork string
|
||||||
|
WebSocketPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
// New return a Conn with net.Conn and DstAddr
|
// New return a Conn with net.Conn and DstAddr
|
||||||
func (c *Client) New(conn net.Conn, dst *DstAddr) net.Conn {
|
func (c *Client) New(conn net.Conn, dst *DstAddr) (net.Conn, error) {
|
||||||
r := rand.Intn(len(c.user))
|
r := rand.Intn(len(c.user))
|
||||||
if c.tls {
|
if c.websocket {
|
||||||
|
dialer := &websocket.Dialer{
|
||||||
|
NetDial: func(network, addr string) (net.Conn, error) {
|
||||||
|
return conn, nil
|
||||||
|
},
|
||||||
|
ReadBufferSize: 4 * 1024,
|
||||||
|
WriteBufferSize: 4 * 1024,
|
||||||
|
HandshakeTimeout: time.Second * 8,
|
||||||
|
}
|
||||||
|
scheme := "ws"
|
||||||
|
if c.tls {
|
||||||
|
scheme = "wss"
|
||||||
|
}
|
||||||
|
|
||||||
|
host, port, err := net.SplitHostPort(c.host)
|
||||||
|
if (scheme == "ws" && port != "80") || (scheme == "wss" && port != "443") {
|
||||||
|
host = c.host
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := url.URL{
|
||||||
|
Scheme: scheme,
|
||||||
|
Host: host,
|
||||||
|
Path: c.websocketPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
wsConn, resp, err := dialer.Dial(uri.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
var reason string
|
||||||
|
if resp != nil {
|
||||||
|
reason = resp.Status
|
||||||
|
}
|
||||||
|
println(uri.String(), err.Error())
|
||||||
|
return nil, fmt.Errorf("Dial %s error: %s", host, reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn = newWebsocketConn(wsConn, conn.RemoteAddr())
|
||||||
|
} else if c.tls {
|
||||||
conn = tls.Client(conn, tlsConfig)
|
conn = tls.Client(conn, tlsConfig)
|
||||||
}
|
}
|
||||||
return newConn(conn, c.user[r], dst, c.security)
|
return newConn(conn, c.user[r], dst, c.security), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient return Client instance
|
// NewClient return Client instance
|
||||||
|
@ -108,10 +153,18 @@ func NewClient(config Config) (*Client, error) {
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("Unknown security type: %s", config.Security)
|
return nil, fmt.Errorf("Unknown security type: %s", config.Security)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.NetWork != "" && config.NetWork != "ws" {
|
||||||
|
return nil, fmt.Errorf("Unknown network type: %s", config.NetWork)
|
||||||
|
}
|
||||||
|
|
||||||
return &Client{
|
return &Client{
|
||||||
user: newAlterIDs(newID(&uid), config.AlterID),
|
user: newAlterIDs(newID(&uid), config.AlterID),
|
||||||
uuid: &uid,
|
uuid: &uid,
|
||||||
security: security,
|
security: security,
|
||||||
tls: config.TLS,
|
tls: config.TLS,
|
||||||
|
host: config.Host,
|
||||||
|
websocket: config.NetWork == "ws",
|
||||||
|
websocketPath: config.WebSocketPath,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
99
component/vmess/websocket.go
Normal file
99
component/vmess/websocket.go
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
package vmess
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
type websocketConn struct {
|
||||||
|
conn *websocket.Conn
|
||||||
|
reader io.Reader
|
||||||
|
remoteAddr net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read implements net.Conn.Read()
|
||||||
|
func (wsc *websocketConn) Read(b []byte) (int, error) {
|
||||||
|
for {
|
||||||
|
reader, err := wsc.getReader()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nBytes, err := reader.Read(b)
|
||||||
|
if err == io.EOF {
|
||||||
|
wsc.reader = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return nBytes, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write implements io.Writer.
|
||||||
|
func (wsc *websocketConn) Write(b []byte) (int, error) {
|
||||||
|
if err := wsc.conn.WriteMessage(websocket.BinaryMessage, b); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wsc *websocketConn) Close() error {
|
||||||
|
var errors []string
|
||||||
|
if err := wsc.conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), time.Now().Add(time.Second*5)); err != nil {
|
||||||
|
errors = append(errors, err.Error())
|
||||||
|
}
|
||||||
|
if err := wsc.conn.Close(); err != nil {
|
||||||
|
errors = append(errors, err.Error())
|
||||||
|
}
|
||||||
|
if len(errors) > 0 {
|
||||||
|
return fmt.Errorf("Failed to close connection: %s", strings.Join(errors, ","))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wsc *websocketConn) getReader() (io.Reader, error) {
|
||||||
|
if wsc.reader != nil {
|
||||||
|
return wsc.reader, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, reader, err := wsc.conn.NextReader()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
wsc.reader = reader
|
||||||
|
return reader, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wsc *websocketConn) LocalAddr() net.Addr {
|
||||||
|
return wsc.conn.LocalAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wsc *websocketConn) RemoteAddr() net.Addr {
|
||||||
|
return wsc.remoteAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wsc *websocketConn) SetDeadline(t time.Time) error {
|
||||||
|
if err := wsc.SetReadDeadline(t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return wsc.SetWriteDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wsc *websocketConn) SetReadDeadline(t time.Time) error {
|
||||||
|
return wsc.conn.SetReadDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wsc *websocketConn) SetWriteDeadline(t time.Time) error {
|
||||||
|
return wsc.conn.SetWriteDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newWebsocketConn(conn *websocket.Conn, remoteAddr net.Addr) net.Conn {
|
||||||
|
return &websocketConn{
|
||||||
|
conn: conn,
|
||||||
|
remoteAddr: remoteAddr,
|
||||||
|
}
|
||||||
|
}
|
1
go.mod
1
go.mod
|
@ -7,6 +7,7 @@ require (
|
||||||
github.com/go-chi/cors v1.0.0
|
github.com/go-chi/cors v1.0.0
|
||||||
github.com/go-chi/render v1.0.1
|
github.com/go-chi/render v1.0.1
|
||||||
github.com/gofrs/uuid v3.1.0+incompatible
|
github.com/gofrs/uuid v3.1.0+incompatible
|
||||||
|
github.com/gorilla/websocket v1.4.0
|
||||||
github.com/oschwald/geoip2-golang v1.2.1
|
github.com/oschwald/geoip2-golang v1.2.1
|
||||||
github.com/oschwald/maxminddb-golang v1.3.0 // indirect
|
github.com/oschwald/maxminddb-golang v1.3.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.1.0
|
github.com/sirupsen/logrus v1.1.0
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -14,6 +14,8 @@ github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
|
||||||
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
|
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
|
||||||
github.com/gofrs/uuid v3.1.0+incompatible h1:q2rtkjaKT4YEr6E1kamy0Ha4RtepWlQBedyHx0uzKwA=
|
github.com/gofrs/uuid v3.1.0+incompatible h1:q2rtkjaKT4YEr6E1kamy0Ha4RtepWlQBedyHx0uzKwA=
|
||||||
github.com/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
github.com/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
|
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||||
|
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe h1:CHRGQ8V7OlCYtwaKPJi3iA7J+YdNKdo8j7nG5IgDhjs=
|
github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe h1:CHRGQ8V7OlCYtwaKPJi3iA7J+YdNKdo8j7nG5IgDhjs=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/oschwald/geoip2-golang v1.2.1 h1:3iz+jmeJc6fuCyWeKgtXSXu7+zvkxJbHFXkMT5FVebU=
|
github.com/oschwald/geoip2-golang v1.2.1 h1:3iz+jmeJc6fuCyWeKgtXSXu7+zvkxJbHFXkMT5FVebU=
|
||||||
|
|
Loading…
Reference in New Issue
Block a user