├── rsrc.syso ├── websocks.ico ├── .gitignore ├── core ├── header.go ├── mux │ ├── client.go │ ├── server.go │ ├── message.go │ ├── websocket.go │ ├── conn.go │ └── group.go ├── stats.go ├── websocket.go └── crypto.go ├── client ├── mux.go ├── config.go ├── conn.go ├── client.go └── socks5.go ├── .goreleaser.yml ├── server ├── config.go └── server.go ├── README.md ├── README-en.md ├── websocks.go └── LICENSE /rsrc.syso: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lzjluzijie/websocks/HEAD/rsrc.syso -------------------------------------------------------------------------------- /websocks.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lzjluzijie/websocks/HEAD/websocks.ico -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | Caddyfile 3 | caddy.log 4 | dist 5 | websocks.cer 6 | websocks.key 7 | *.pac 8 | *.json 9 | -------------------------------------------------------------------------------- /core/header.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | func NewHostHeader(host string) (header map[string][]string) { 4 | return map[string][]string{ 5 | "WebSocks-Host": {host}, 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /client/mux.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "github.com/lzjluzijie/websocks/core" 5 | "github.com/lzjluzijie/websocks/core/mux" 6 | ) 7 | 8 | func (client *WebSocksClient) OpenMux() (err error) { 9 | wsConn, _, err := client.dialer.Dial(client.ServerURL.String(), map[string][]string{ 10 | "WebSocks-Mux": {"v0.15"}, 11 | }) 12 | 13 | if err != nil { 14 | return 15 | } 16 | 17 | ws := core.NewWebSocket(wsConn, client.Stats) 18 | 19 | muxWS := mux.NewMuxWebSocket(ws) 20 | client.muxGroup.AddMuxWS(muxWS) 21 | return 22 | } 23 | -------------------------------------------------------------------------------- /core/mux/client.go: -------------------------------------------------------------------------------- 1 | package mux 2 | 3 | //NewMuxConn creates a new mux connection for client 4 | func (group *Group) NewMuxConn(host string) (conn *Conn, err error) { 5 | conn = &Conn{ 6 | ID: group.NextConnID(), 7 | wait: make(chan int), 8 | sendMessageID: new(uint32), 9 | group: group, 10 | } 11 | 12 | m := &Message{ 13 | Method: MessageMethodDial, 14 | MessageID: 4294967295, 15 | ConnID: conn.ID, 16 | Length: uint32(len(host)), 17 | Data: []byte(host), 18 | } 19 | 20 | err = group.Send(m) 21 | if err != nil { 22 | return 23 | } 24 | 25 | group.AddConn(conn) 26 | return 27 | } 28 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | builds: 2 | - binary: websocks 3 | main: ./websocks.go 4 | goos: 5 | - windows 6 | - darwin 7 | - linux 8 | - freebsd 9 | goarch: 10 | - amd64 11 | - 386 12 | - arm 13 | - arm64 14 | goarm: 15 | - 6 16 | - 7 17 | archive: 18 | name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}' 19 | replacements: 20 | darwin: Darwin 21 | linux: Linux 22 | windows: Windows 23 | 386: i386 24 | amd64: x86_64 25 | format_overrides: 26 | - goos: windows 27 | format: zip 28 | files: 29 | - LICENSE 30 | - README.md 31 | - README-en.md 32 | # - templates/**/* 33 | -------------------------------------------------------------------------------- /server/config.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/gorilla/websocket" 7 | "github.com/lzjluzijie/websocks/core" 8 | ) 9 | 10 | type Config struct { 11 | ListenAddr string 12 | Pattern string 13 | TLS bool 14 | CertPath string 15 | KeyPath string 16 | ReverseProxy string 17 | } 18 | 19 | func (config *Config) GetServer() (server *WebSocksServer) { 20 | server = &WebSocksServer{ 21 | Config: config, 22 | Upgrader: &websocket.Upgrader{ 23 | ReadBufferSize: 4 * 1024, 24 | WriteBufferSize: 4 * 1024, 25 | HandshakeTimeout: 10 * time.Second, 26 | }, 27 | CreatedAt: time.Now(), 28 | Stats: core.NewStats(), 29 | } 30 | return 31 | } 32 | -------------------------------------------------------------------------------- /core/mux/server.go: -------------------------------------------------------------------------------- 1 | package mux 2 | 3 | import ( 4 | "log" 5 | "net" 6 | ) 7 | 8 | //ServerHandleMessage is a server group function 9 | func (group *Group) ServerHandleMessage(m *Message) (err error) { 10 | //accept new conn 11 | if m.Method == MessageMethodDial { 12 | host := string(m.Data) 13 | 14 | //debug log 15 | //log.Printf("start to dial %s", host) 16 | 17 | conn := &Conn{ 18 | ID: m.ConnID, 19 | wait: make(chan int), 20 | sendMessageID: new(uint32), 21 | group: group, 22 | } 23 | 24 | //add to group before receive data 25 | group.AddConn(conn) 26 | 27 | tcpAddr, err := net.ResolveTCPAddr("tcp", host) 28 | if err != nil { 29 | conn.Close() 30 | log.Printf(err.Error()) 31 | return err 32 | } 33 | 34 | tcpConn, err := net.DialTCP("tcp", nil, tcpAddr) 35 | if err != nil { 36 | conn.Close() 37 | log.Printf(err.Error()) 38 | return err 39 | } 40 | 41 | //debug log 42 | log.Printf("Accepted mux conn: %x, %s", conn.ID, host) 43 | 44 | conn.Run(tcpConn) 45 | return err 46 | } 47 | return 48 | } 49 | -------------------------------------------------------------------------------- /core/mux/message.go: -------------------------------------------------------------------------------- 1 | package mux 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "io" 8 | ) 9 | 10 | const ( 11 | MessageMethodData = iota 12 | MessageMethodDial 13 | ) 14 | 15 | //MessageHeadLength = 13 16 | type Message struct { 17 | Method uint8 18 | ConnID uint32 19 | MessageID uint32 20 | Length uint32 21 | Data []byte 22 | 23 | r io.Reader 24 | buf []byte 25 | } 26 | 27 | func (m *Message) Read(p []byte) (n int, err error) { 28 | if m.r == nil { 29 | h := make([]byte, 13) 30 | h[0] = m.Method 31 | binary.BigEndian.PutUint32(h[1:5], m.ConnID) 32 | binary.BigEndian.PutUint32(h[5:9], m.MessageID) 33 | binary.BigEndian.PutUint32(h[9:13], m.Length) 34 | m.r = bytes.NewReader(append(h, m.Data...)) 35 | } 36 | 37 | return m.r.Read(p) 38 | } 39 | 40 | func LoadMessage(h []byte) (m *Message) { 41 | if len(h) != 13 { 42 | panic(fmt.Sprintf("wrong head length: %d", len(h))) 43 | return 44 | } 45 | 46 | m = &Message{ 47 | Method: h[0], 48 | ConnID: binary.BigEndian.Uint32(h[1:5]), 49 | MessageID: binary.BigEndian.Uint32(h[5:9]), 50 | Length: binary.BigEndian.Uint32(h[9:13]), 51 | } 52 | return 53 | } 54 | -------------------------------------------------------------------------------- /client/config.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "crypto/tls" 5 | "net" 6 | "net/url" 7 | "time" 8 | 9 | "github.com/gorilla/websocket" 10 | "github.com/lzjluzijie/websocks/core" 11 | ) 12 | 13 | type Config struct { 14 | ListenAddr string 15 | ServerURL string 16 | 17 | SNI string 18 | InsecureCert bool 19 | 20 | Mux bool 21 | } 22 | 23 | //GetClient return client from path 24 | func (config *Config) GetClient() (client *WebSocksClient, err error) { 25 | //tackle config 26 | serverURL, err := url.Parse(config.ServerURL) 27 | if err != nil { 28 | return 29 | } 30 | 31 | laddr, err := net.ResolveTCPAddr("tcp", config.ListenAddr) 32 | if err != nil { 33 | return 34 | } 35 | 36 | tlsConfig := &tls.Config{ 37 | InsecureSkipVerify: config.InsecureCert, 38 | ServerName: config.SNI, 39 | } 40 | 41 | client = &WebSocksClient{ 42 | ServerURL: serverURL, 43 | ListenAddr: laddr, 44 | dialer: &websocket.Dialer{ 45 | ReadBufferSize: 4 * 1024, 46 | WriteBufferSize: 4 * 1024, 47 | HandshakeTimeout: 10 * time.Second, 48 | TLSClientConfig: tlsConfig, 49 | }, 50 | 51 | Mux: config.Mux, 52 | CreatedAt: time.Now(), 53 | Stats: core.NewStats(), 54 | } 55 | return 56 | } 57 | -------------------------------------------------------------------------------- /core/stats.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | //todo better stats 9 | type Stats struct { 10 | Downloaded uint64 11 | DownloadSpeed uint64 12 | downloadMutex sync.Mutex 13 | downloadSpeedA uint64 14 | 15 | Uploaded uint64 16 | UploadSpeed uint64 17 | uploadMutex sync.Mutex 18 | uploadSpeedA uint64 19 | } 20 | 21 | func NewStats() (stats *Stats) { 22 | stats = &Stats{} 23 | go func() { 24 | for range time.Tick(time.Second) { 25 | stats.downloadMutex.Lock() 26 | stats.DownloadSpeed = stats.downloadSpeedA 27 | stats.downloadSpeedA = 0 28 | stats.downloadMutex.Unlock() 29 | } 30 | }() 31 | 32 | go func() { 33 | for range time.Tick(time.Second) { 34 | stats.uploadMutex.Lock() 35 | stats.UploadSpeed = stats.uploadSpeedA 36 | stats.uploadSpeedA = 0 37 | stats.uploadMutex.Unlock() 38 | } 39 | }() 40 | return 41 | } 42 | 43 | func (stats *Stats) AddDownloaded(downloaded uint64) { 44 | stats.downloadMutex.Lock() 45 | stats.Downloaded += downloaded 46 | stats.downloadSpeedA += downloaded 47 | stats.downloadMutex.Unlock() 48 | return 49 | } 50 | 51 | func (stats *Stats) AddUploaded(uploaded uint64) { 52 | stats.uploadMutex.Lock() 53 | stats.Uploaded += uploaded 54 | stats.uploadSpeedA += uploaded 55 | stats.uploadMutex.Unlock() 56 | return 57 | } 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebSocks 2 | 3 | [English](https://github.com/lzjluzijie/websocks/blob/master/README-en.md) 4 | 5 | 一个基于 WebSocket 的代理工具 6 | 7 | **由于本人学业的原因,websocks暂时停更几个月,各位大佬们对不住了,等我搞定大学一定会填坑的** 8 | 9 | 本项目目前还在开发中,更多功能仍在完善中。如果你对这个项目感兴趣,请star它来支持我,蟹蟹 10 | 11 | 有任何问题或建议可以直接发issue或者联系我 [@halulu](https://t.me/halulu),也可以来[TG群](https://t.me/websocks)水一水,开发记录可以看[我的博客](https://halu.lu/post/websocks-development/) 12 | 13 | 优点 14 | - 使用WS+TLS,十分安全且不易被检测,和普通HTTPS网站一样 15 | - 可以搭配使用cloudflare这类cdn,完全不怕被墙! 16 | 17 | 缺点就是刚刚开始开发,没有GUI客户端,功能也比较少,如果你能来帮我那就太好了! 18 | 19 | [官网](https://websocks.org/)|[社区](https://zhuji.lu/tags/websocks)|[一键脚本](https://zhuji.lu/topic/15/websocks-一键脚本-简易安装教程)|[电报群](https://t.me/websocks) 20 | 21 | ## 示例 22 | 23 | ### 内置 TLS 混淆域名并反向代理 24 | 25 | #### 服务端 26 | ``` 27 | ./websocks cert 28 | ./websocks server -l :443 -p websocks --reverse-proxy http://mirror.centos.org --tls 29 | ``` 30 | 31 | #### 客户端 32 | ``` 33 | ./websocks client -l :1080 -s wss://websocks.org:443/websocks -sni mirror.centos.com --insecure 34 | ``` 35 | 36 | 37 | ### Caddy TLS 38 | 39 | #### 服务端 40 | ``` 41 | ./websocks server -l :2333 -p /websocks 42 | ``` 43 | 44 | #### 客户端 45 | ``` 46 | ./websocks client -l :1080 -s wss://websocks.org/websocks 47 | ``` 48 | 49 | #### Caddyfile 50 | ``` 51 | https://websocks.org { 52 | proxy /websocks localhost:2333 { 53 | websocket 54 | } 55 | } 56 | ``` -------------------------------------------------------------------------------- /core/websocket.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | 7 | "github.com/gorilla/websocket" 8 | ) 9 | 10 | var ErrWebSocketClosed = errors.New("websocket closed") 11 | 12 | type WebSocket struct { 13 | conn *websocket.Conn 14 | buf []byte 15 | 16 | //stats 17 | createdAt time.Time 18 | closed bool 19 | stats *Stats 20 | } 21 | 22 | func NewWebSocket(conn *websocket.Conn, stats *Stats) (ws *WebSocket) { 23 | ws = &WebSocket{ 24 | conn: conn, 25 | createdAt: time.Now(), 26 | stats: stats, 27 | } 28 | return 29 | } 30 | 31 | func (ws *WebSocket) Read(p []byte) (n int, err error) { 32 | if ws.closed == true { 33 | return 0, ErrWebSocketClosed 34 | } 35 | 36 | if len(ws.buf) == 0 { 37 | //debug log 38 | //log.Println("empty buf, waiting") 39 | _, ws.buf, err = ws.conn.ReadMessage() 40 | if err != nil { 41 | return 42 | } 43 | } 44 | 45 | n = copy(p, ws.buf) 46 | ws.buf = ws.buf[n:] 47 | 48 | if ws.stats != nil { 49 | go ws.stats.AddDownloaded(uint64(n)) 50 | } 51 | return 52 | } 53 | 54 | func (ws *WebSocket) Write(p []byte) (n int, err error) { 55 | if ws.closed == true { 56 | return 0, ErrWebSocketClosed 57 | } 58 | 59 | err = ws.conn.WriteMessage(websocket.BinaryMessage, p) 60 | if err != nil { 61 | return 62 | } 63 | 64 | n = len(p) 65 | 66 | if ws.stats != nil { 67 | go ws.stats.AddUploaded(uint64(n)) 68 | } 69 | return 70 | } 71 | 72 | func (ws *WebSocket) Close() (err error) { 73 | ws.closed = true 74 | err = ws.conn.Close() 75 | return 76 | } 77 | -------------------------------------------------------------------------------- /client/conn.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "net" 7 | "time" 8 | 9 | "github.com/lzjluzijie/websocks/core" 10 | ) 11 | 12 | type LocalConn struct { 13 | Host string 14 | 15 | conn *net.TCPConn 16 | 17 | //stats 18 | createdAt time.Time 19 | closed bool 20 | } 21 | 22 | func NewLocalConn(conn *net.TCPConn) (lc *LocalConn, err error) { 23 | conn.SetLinger(0) 24 | err = handShake(conn) 25 | if err != nil { 26 | return 27 | } 28 | 29 | _, host, err := getRequest(conn) 30 | if err != nil { 31 | return 32 | } 33 | 34 | _, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x08, 0x43}) 35 | if err != nil { 36 | return 37 | } 38 | 39 | lc = &LocalConn{ 40 | Host: host, 41 | 42 | conn: conn, 43 | createdAt: time.Now(), 44 | } 45 | return 46 | } 47 | 48 | func (lc *LocalConn) Run(ws *core.WebSocket) { 49 | go func() { 50 | _, err := io.Copy(lc, ws) 51 | if err != nil { 52 | //log.Printf(err.Error()) 53 | return 54 | } 55 | return 56 | }() 57 | 58 | go func() { 59 | _, err := io.Copy(ws, lc) 60 | if err != nil { 61 | //log.Printf(err.Error()) 62 | return 63 | } 64 | }() 65 | return 66 | } 67 | 68 | func (lc *LocalConn) Read(p []byte) (n int, err error) { 69 | if lc.closed { 70 | return 0, errors.New("local conn closed") 71 | } 72 | 73 | n, err = lc.conn.Read(p) 74 | if err != nil { 75 | lc.closed = true 76 | } 77 | return 78 | } 79 | 80 | func (lc *LocalConn) Write(p []byte) (n int, err error) { 81 | if lc.closed { 82 | return 0, errors.New("local conn closed") 83 | } 84 | 85 | n, err = lc.conn.Write(p) 86 | if err != nil { 87 | lc.closed = true 88 | } 89 | return 90 | } 91 | -------------------------------------------------------------------------------- /core/mux/websocket.go: -------------------------------------------------------------------------------- 1 | package mux 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "log" 7 | "sync" 8 | 9 | "github.com/lzjluzijie/websocks/core" 10 | ) 11 | 12 | type MuxWebSocket struct { 13 | *core.WebSocket 14 | 15 | group *Group 16 | 17 | sMutex sync.Mutex 18 | rMutex sync.Mutex 19 | } 20 | 21 | func NewMuxWebSocket(ws *core.WebSocket) (muxWS *MuxWebSocket) { 22 | muxWS = &MuxWebSocket{ 23 | WebSocket: ws, 24 | } 25 | return 26 | } 27 | 28 | func (muxWS *MuxWebSocket) Send(m *Message) (err error) { 29 | muxWS.sMutex.Lock() 30 | _, err = io.Copy(muxWS, m) 31 | if err != nil { 32 | e := muxWS.Close() 33 | if e != nil { 34 | log.Println(e.Error()) 35 | } 36 | return 37 | } 38 | muxWS.sMutex.Unlock() 39 | 40 | //debug log 41 | //log.Printf("sent %#v", m) 42 | return 43 | } 44 | 45 | func (muxWS *MuxWebSocket) Receive() (m *Message, err error) { 46 | muxWS.rMutex.Lock() 47 | 48 | h := make([]byte, 13) 49 | 50 | _, err = muxWS.Read(h) 51 | if err != nil { 52 | e := muxWS.Close() 53 | if e != nil { 54 | log.Println(e.Error()) 55 | } 56 | return 57 | } 58 | 59 | //debug log 60 | //log.Printf("%d %x",n, h) 61 | 62 | m = LoadMessage(h) 63 | buf := &bytes.Buffer{} 64 | r := io.LimitReader(muxWS, int64(m.Length)) 65 | 66 | _, err = io.Copy(buf, r) 67 | if err != nil { 68 | e := muxWS.Close() 69 | if e != nil { 70 | log.Println(e.Error()) 71 | } 72 | return 73 | } 74 | muxWS.rMutex.Unlock() 75 | 76 | m.Data = buf.Bytes() 77 | 78 | ////debug log 79 | //log.Printf("received %#v", m) 80 | return 81 | } 82 | 83 | func (muxWS *MuxWebSocket) Close() (err error) { 84 | muxWS.group.MuxWSs = nil 85 | err = muxWS.WebSocket.Close() 86 | return 87 | } 88 | -------------------------------------------------------------------------------- /README-en.md: -------------------------------------------------------------------------------- 1 | # WebSocks 2 | 3 | A secure proxy based on websocket. 4 | 5 | **Websocks will be temporarily suspended for a few more months because I have to apply for university. I'm really sorry and I promise I will come back.** 6 | 7 | This project is still working in progress, more features are still in development. If you are interested in this project, please star this project in order to support me. Thank you. 8 | 9 | If you have any problems or suggestions, please do not hesitate to submit issues or contact me [@halulu](https://t.me/halulu). 10 | 11 | Advantages: 12 | 13 | - Using WebSocket and TLS which are very secure and difficult to be detected, same as regular HTTPS websites 14 | - Can be used with cdn such as cloudflare, not afraid of gfw at all! 15 | 16 | The disadvantage is that I have just started development, there is no GUI client, and features are not enough. I will appreciate if you can help me! 17 | 18 | [Official site](https://websocks.org/)|[Community](https://zhuji.lu/tags/websocks)|[Test node](https://zhuji.lu/topic/39/websocks测试节点)|[One-click script](https://zhuji.lu/topic/15/websocks-一键脚本-简易安装教程)|[Telegram group](https://t.me/websocks) 19 | 20 | ## Example 21 | 22 | ### Built-in TLS with fake server name and reversing proxy 23 | 24 | #### Server 25 | ``` 26 | ./websocks cert 27 | ./websocks server -l :443 -p websocks --reverse-proxy http://mirror.centos.org --tls 28 | ``` 29 | 30 | #### Client 31 | ``` 32 | ./websocks client -l :1080 -s wss://websocks.org:443/websocks -sni mirror.centos.com --insecure 33 | ``` 34 | 35 | ### Caddy TLS 36 | 37 | #### Server 38 | ``` 39 | ./websocks server -l :2333 -p /websocks 40 | ``` 41 | 42 | #### Client 43 | ``` 44 | ./websocks client -l :1080 -s wss://websocks.org/websocks 45 | ``` 46 | 47 | #### Caddyfile 48 | ``` 49 | https://websocks.org { 50 | proxy /websocks localhost:2333 { 51 | websocket 52 | } 53 | } 54 | ``` -------------------------------------------------------------------------------- /core/mux/conn.go: -------------------------------------------------------------------------------- 1 | package mux 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "log" 7 | "net" 8 | "sync" 9 | "sync/atomic" 10 | ) 11 | 12 | var ErrConnClosed = errors.New("mux conn closed") 13 | 14 | type Conn struct { 15 | ID uint32 16 | 17 | group *Group 18 | 19 | mutex sync.Mutex 20 | buf []byte 21 | wait chan int 22 | 23 | closed bool 24 | 25 | receiveMessageID uint32 26 | sendMessageID *uint32 27 | } 28 | 29 | func (conn *Conn) Write(p []byte) (n int, err error) { 30 | if conn.closed { 31 | return 0, ErrConnClosed 32 | } 33 | 34 | m := &Message{ 35 | Method: MessageMethodData, 36 | ConnID: conn.ID, 37 | MessageID: conn.SendMessageID(), 38 | Length: uint32(len(p)), 39 | Data: p, 40 | } 41 | 42 | err = conn.group.Send(m) 43 | if err != nil { 44 | return 0, err 45 | } 46 | return len(p), nil 47 | } 48 | 49 | func (conn *Conn) Read(p []byte) (n int, err error) { 50 | if conn.closed { 51 | return 0, ErrConnClosed 52 | } 53 | 54 | if len(conn.buf) == 0 { 55 | //log.Printf("%d buf is 0, waiting", conn.ID) 56 | <-conn.wait 57 | } 58 | 59 | conn.mutex.Lock() 60 | //log.Printf("%d buf: %v",conn.ID, conn.buf) 61 | n = copy(p, conn.buf) 62 | conn.buf = conn.buf[n:] 63 | conn.mutex.Unlock() 64 | return 65 | } 66 | 67 | func (conn *Conn) HandleMessage(m *Message) (err error) { 68 | if conn.closed { 69 | return ErrConnClosed 70 | } 71 | 72 | //debug log 73 | //log.Printf("handle message %d %d", m.ConnID, m.MessageID) 74 | 75 | for { 76 | if conn.receiveMessageID == m.MessageID { 77 | conn.mutex.Lock() 78 | conn.buf = append(conn.buf, m.Data...) 79 | conn.receiveMessageID++ 80 | close(conn.wait) 81 | conn.wait = make(chan int) 82 | conn.mutex.Unlock() 83 | //debug log 84 | //log.Printf("handled message %d %d", m.ConnID, m.MessageID) 85 | return 86 | } 87 | <-conn.wait 88 | } 89 | return 90 | } 91 | 92 | func (conn *Conn) SendMessageID() (id uint32) { 93 | id = atomic.LoadUint32(conn.sendMessageID) 94 | atomic.AddUint32(conn.sendMessageID, 1) 95 | return 96 | } 97 | 98 | func (conn *Conn) Run(c *net.TCPConn) { 99 | go func() { 100 | _, err := io.Copy(c, conn) 101 | if err != nil { 102 | conn.Close() 103 | log.Printf(err.Error()) 104 | } 105 | }() 106 | 107 | _, err := io.Copy(conn, c) 108 | if err != nil { 109 | conn.Close() 110 | log.Printf(err.Error()) 111 | } 112 | 113 | return 114 | } 115 | 116 | func (conn *Conn) Close() (err error) { 117 | conn.group.DeleteConn(conn.ID) 118 | //close(conn.wait) 119 | conn.closed = true 120 | return 121 | } 122 | -------------------------------------------------------------------------------- /client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "time" 7 | 8 | "github.com/lzjluzijie/websocks/core/mux" 9 | 10 | "net/url" 11 | 12 | "github.com/gorilla/websocket" 13 | "github.com/lzjluzijie/websocks/core" 14 | ) 15 | 16 | type WebSocksClient struct { 17 | ServerURL *url.URL 18 | ListenAddr *net.TCPAddr 19 | 20 | dialer *websocket.Dialer 21 | 22 | Mux bool 23 | 24 | muxGroup *mux.Group 25 | 26 | //todo 27 | //control 28 | stopC chan int 29 | 30 | CreatedAt time.Time 31 | Stats *core.Stats 32 | } 33 | 34 | func (client *WebSocksClient) Run() (err error) { 35 | if client.Mux { 36 | client.muxGroup = mux.NewGroup(true) 37 | log.Println("group created") 38 | go func() { 39 | //todo 40 | for { 41 | if len(client.muxGroup.MuxWSs) == 0 { 42 | err := client.OpenMux() 43 | if err != nil { 44 | log.Printf(err.Error()) 45 | continue 46 | } 47 | } 48 | //这个弱智BUG折腾了我一天 49 | time.Sleep(time.Second) 50 | } 51 | }() 52 | } 53 | 54 | listener, err := net.ListenTCP("tcp", client.ListenAddr) 55 | if err != nil { 56 | return err 57 | } 58 | 59 | log.Printf("Start to listen at %s", client.ListenAddr.String()) 60 | 61 | go func() { 62 | client.stopC = make(chan int) 63 | <-client.stopC 64 | err = listener.Close() 65 | if err != nil { 66 | log.Printf(err.Error()) 67 | return 68 | } 69 | 70 | log.Print("stopped") 71 | }() 72 | 73 | for { 74 | conn, err := listener.AcceptTCP() 75 | if err != nil { 76 | log.Printf(err.Error()) 77 | break 78 | } 79 | 80 | go client.HandleConn(conn) 81 | } 82 | return nil 83 | } 84 | 85 | func (client *WebSocksClient) Stop() { 86 | client.stopC <- 1911 87 | return 88 | } 89 | 90 | func (client *WebSocksClient) HandleConn(conn *net.TCPConn) { 91 | //debug log 92 | //log.Println("new socks5 conn") 93 | 94 | lc, err := NewLocalConn(conn) 95 | if err != nil { 96 | log.Printf(err.Error()) 97 | return 98 | } 99 | 100 | host := lc.Host 101 | 102 | if client.Mux { 103 | muxConn, err := client.muxGroup.NewMuxConn(host) 104 | if err != nil { 105 | log.Printf(err.Error()) 106 | return 107 | } 108 | 109 | //debug log 110 | log.Printf("created new mux conn: %x %s", muxConn.ID, host) 111 | 112 | muxConn.Run(conn) 113 | return 114 | } 115 | 116 | ws, err := client.DialWebSocket(core.NewHostHeader(host)) 117 | if err != nil { 118 | log.Printf(err.Error()) 119 | return 120 | } 121 | 122 | lc.Run(ws) 123 | return 124 | } 125 | 126 | func (client *WebSocksClient) DialWebSocket(header map[string][]string) (ws *core.WebSocket, err error) { 127 | wsConn, _, err := client.dialer.Dial(client.ServerURL.String(), header) 128 | if err != nil { 129 | return 130 | } 131 | 132 | ws = core.NewWebSocket(wsConn, client.Stats) 133 | return 134 | } 135 | -------------------------------------------------------------------------------- /core/mux/group.go: -------------------------------------------------------------------------------- 1 | package mux 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | type Group struct { 12 | client bool 13 | 14 | MuxWSs []*MuxWebSocket 15 | 16 | connMap map[uint32]*Conn 17 | connMapMutex sync.RWMutex 18 | 19 | connID uint32 20 | connIDMutex sync.Mutex 21 | } 22 | 23 | //true: client group 24 | //false: server group 25 | func NewGroup(client bool) (group *Group) { 26 | group = &Group{ 27 | client: client, 28 | connMap: make(map[uint32]*Conn), 29 | } 30 | return 31 | } 32 | 33 | func (group *Group) Send(m *Message) (err error) { 34 | //todo 35 | for group.MuxWSs != nil { 36 | err = group.MuxWSs[0].Send(m) 37 | return 38 | } 39 | return 40 | } 41 | 42 | func (group *Group) Handle(m *Message) { 43 | //log.Printf("group received %#v", m) 44 | 45 | if !group.client && m.Method != MessageMethodData { 46 | group.ServerHandleMessage(m) 47 | return 48 | } 49 | 50 | //get conn and send message 51 | for { 52 | conn := group.GetConn(m.ConnID) 53 | if conn == nil { 54 | //debug log 55 | err := errors.New(fmt.Sprintf("conn does not exist: %x", m.ConnID)) 56 | log.Println(err.Error()) 57 | log.Printf("%X %X %X %d", m.Method, m.ConnID, m.MessageID, m.Length) 58 | return 59 | } 60 | 61 | //this err should be nil or ErrConnClosed 62 | err := conn.HandleMessage(m) 63 | if err != nil { 64 | log.Println(err.Error()) 65 | return 66 | } 67 | } 68 | return 69 | } 70 | 71 | func (group *Group) AddConn(conn *Conn) { 72 | group.connMapMutex.Lock() 73 | group.connMap[conn.ID] = conn 74 | group.connMapMutex.Unlock() 75 | return 76 | } 77 | 78 | func (group *Group) DeleteConn(id uint32) { 79 | group.connMapMutex.Lock() 80 | delete(group.connMap, id) 81 | group.connMapMutex.Unlock() 82 | return 83 | } 84 | 85 | func (group *Group) GetConn(id uint32) (conn *Conn) { 86 | group.connMapMutex.RLock() 87 | conn = group.connMap[id] 88 | group.connMapMutex.RUnlock() 89 | 90 | if conn == nil { 91 | t := time.Now() 92 | for time.Now().Before(t.Add(time.Second)) { 93 | group.connMapMutex.RLock() 94 | conn = group.connMap[id] 95 | group.connMapMutex.RUnlock() 96 | if conn != nil { 97 | return conn 98 | } 99 | } 100 | } 101 | return 102 | } 103 | 104 | func (group *Group) NextConnID() (id uint32) { 105 | group.connIDMutex.Lock() 106 | group.connID++ 107 | id = group.connID 108 | group.connIDMutex.Unlock() 109 | return 110 | } 111 | 112 | func (group *Group) AddMuxWS(muxWS *MuxWebSocket) (err error) { 113 | muxWS.group = group 114 | group.MuxWSs = append(group.MuxWSs, muxWS) 115 | group.Listen(muxWS) 116 | return 117 | } 118 | 119 | func (group *Group) Listen(muxWS *MuxWebSocket) { 120 | go func() { 121 | for { 122 | m, err := muxWS.Receive() 123 | if err != nil { 124 | log.Println(err.Error()) 125 | return 126 | } 127 | 128 | go group.Handle(m) 129 | } 130 | }() 131 | } 132 | -------------------------------------------------------------------------------- /server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "net" 7 | "net/http" 8 | "time" 9 | 10 | "github.com/lzjluzijie/websocks/core/mux" 11 | 12 | "net/http/httputil" 13 | "net/url" 14 | 15 | "crypto/tls" 16 | 17 | "github.com/gorilla/websocket" 18 | "github.com/juju/loggo" 19 | "github.com/julienschmidt/httprouter" 20 | "github.com/lzjluzijie/websocks/core" 21 | ) 22 | 23 | type WebSocksServer struct { 24 | *Config 25 | LogLevel loggo.Level 26 | 27 | Upgrader *websocket.Upgrader 28 | 29 | //todo multiple clients 30 | muxGroup *mux.Group 31 | 32 | CreatedAt time.Time 33 | Stats *core.Stats 34 | } 35 | 36 | func (server *WebSocksServer) HandleWebSocket(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 37 | wsConn, err := server.Upgrader.Upgrade(w, r, nil) 38 | if err != nil { 39 | log.Printf(err.Error()) 40 | return 41 | } 42 | defer wsConn.Close() 43 | 44 | ws := core.NewWebSocket(wsConn, server.Stats) 45 | 46 | //mux 47 | //todo multiple clients 48 | if r.Header.Get("WebSocks-Mux") == "v0.15" { 49 | if server.muxGroup == nil { 50 | server.muxGroup = mux.NewGroup(false) 51 | } 52 | muxWS := mux.NewMuxWebSocket(ws) 53 | server.muxGroup.AddMuxWS(muxWS) 54 | time.Sleep(time.Hour) 55 | return 56 | } 57 | 58 | host := r.Header.Get("WebSocks-Host") 59 | log.Printf("Dial %s", host) 60 | conn, err := server.DialRemote(host) 61 | if err != nil { 62 | log.Printf(err.Error()) 63 | return 64 | } 65 | 66 | go func() { 67 | _, err = io.Copy(conn, ws) 68 | if err != nil { 69 | log.Printf(err.Error()) 70 | return 71 | } 72 | }() 73 | 74 | _, err = io.Copy(ws, conn) 75 | if err != nil { 76 | log.Printf(err.Error()) 77 | return 78 | } 79 | 80 | return 81 | } 82 | 83 | func (server *WebSocksServer) DialRemote(host string) (conn net.Conn, err error) { 84 | conn, err = net.Dial("tcp", host) 85 | if err != nil { 86 | return 87 | } 88 | return 89 | } 90 | 91 | func (server *WebSocksServer) Run() (err error) { 92 | r := httprouter.New() 93 | r.GET(server.Pattern, server.HandleWebSocket) 94 | 95 | if server.ReverseProxy != "" { 96 | remote, err := url.Parse(server.ReverseProxy) 97 | if err != nil { 98 | panic(err) 99 | } 100 | proxy := httputil.NewSingleHostReverseProxy(remote) 101 | r.NotFound = proxy 102 | } 103 | 104 | s := http.Server{ 105 | Addr: server.ListenAddr, 106 | Handler: r, 107 | } 108 | 109 | log.Printf("Start to listen at %s", server.ListenAddr) 110 | 111 | if !server.TLS { 112 | err = s.ListenAndServe() 113 | if err != nil { 114 | return err 115 | } 116 | return 117 | } 118 | 119 | s.TLSConfig = &tls.Config{ 120 | CipherSuites: []uint16{ 121 | tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, 122 | tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, 123 | tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 124 | tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 125 | tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, 126 | tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, 127 | }, 128 | } 129 | 130 | err = s.ListenAndServeTLS(server.Config.CertPath, server.Config.KeyPath) 131 | if err != nil { 132 | return err 133 | } 134 | return 135 | } 136 | -------------------------------------------------------------------------------- /core/crypto.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "crypto/elliptic" 6 | "crypto/rand" 7 | "crypto/rsa" 8 | "crypto/x509" 9 | "crypto/x509/pkix" 10 | "encoding/pem" 11 | "math/big" 12 | "net" 13 | "time" 14 | ) 15 | 16 | //Modified https://github.com/Shyp/generate-tls-cert 17 | func GenP256(hosts []string) (key, cert []byte, err error) { 18 | notBefore := time.Now() 19 | notAfter := notBefore.Add(time.Hour * 24 * 366) 20 | serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) 21 | serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) 22 | if err != nil { 23 | return 24 | } 25 | serverKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 26 | if err != nil { 27 | return 28 | } 29 | 30 | serverTemplate := x509.Certificate{ 31 | SerialNumber: serialNumber, 32 | Subject: pkix.Name{ 33 | Organization: []string{"WebSocks"}, 34 | CommonName: "WebSocks Server CA", 35 | }, 36 | NotBefore: notBefore, 37 | NotAfter: notAfter, 38 | KeyUsage: x509.KeyUsageCertSign, 39 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 40 | BasicConstraintsValid: true, 41 | IsCA: true, 42 | } 43 | 44 | for _, host := range hosts { 45 | if ip := net.ParseIP(host); ip != nil { 46 | serverTemplate.IPAddresses = append(serverTemplate.IPAddresses, ip) 47 | } else { 48 | serverTemplate.DNSNames = append(serverTemplate.DNSNames, host) 49 | } 50 | } 51 | 52 | serverCert, err := x509.CreateCertificate(rand.Reader, &serverTemplate, &serverTemplate, &serverKey.PublicKey, serverKey) 53 | if err != nil { 54 | return 55 | } 56 | 57 | x509Key, err := x509.MarshalECPrivateKey(serverKey) 58 | if err != nil { 59 | return 60 | } 61 | 62 | key = pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: x509Key}) 63 | cert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: serverCert}) 64 | return 65 | } 66 | 67 | func GenRSA2048(hosts []string) (key, cert []byte, err error) { 68 | notBefore := time.Now() 69 | notAfter := notBefore.Add(time.Hour * 24 * 366) 70 | serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) 71 | serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) 72 | if err != nil { 73 | return 74 | } 75 | serverKey, err := rsa.GenerateKey(rand.Reader, 2048) 76 | if err != nil { 77 | return 78 | } 79 | 80 | serverTemplate := x509.Certificate{ 81 | SerialNumber: serialNumber, 82 | Subject: pkix.Name{ 83 | Organization: []string{"WebSocks"}, 84 | CommonName: "WebSocks Server CA", 85 | }, 86 | NotBefore: notBefore, 87 | NotAfter: notAfter, 88 | KeyUsage: x509.KeyUsageCertSign, 89 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 90 | BasicConstraintsValid: true, 91 | IsCA: true, 92 | } 93 | 94 | for _, host := range hosts { 95 | if ip := net.ParseIP(host); ip != nil { 96 | serverTemplate.IPAddresses = append(serverTemplate.IPAddresses, ip) 97 | } else { 98 | serverTemplate.DNSNames = append(serverTemplate.DNSNames, host) 99 | } 100 | } 101 | 102 | serverCert, err := x509.CreateCertificate(rand.Reader, &serverTemplate, &serverTemplate, &serverKey.PublicKey, serverKey) 103 | if err != nil { 104 | return 105 | } 106 | 107 | x509Key := x509.MarshalPKCS1PrivateKey(serverKey) 108 | 109 | key = pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509Key}) 110 | cert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: serverCert}) 111 | return 112 | } 113 | -------------------------------------------------------------------------------- /client/socks5.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "io" 7 | "net" 8 | "strconv" 9 | ) 10 | 11 | /* 12 | Apache License 2.0 13 | https://github.com/shadowsocks/shadowsocks-go/blob/master/cmd/shadowsocks-local/local.go 14 | */ 15 | 16 | var ( 17 | errAddrType = errors.New("socks addr type not supported") 18 | errVer = errors.New("socks version not supported") 19 | errMethod = errors.New("socks only support 1 method now") 20 | errAuthExtraData = errors.New("socks authentication get extra data") 21 | errReqExtraData = errors.New("socks request get extra data") 22 | errCmd = errors.New("socks command not supported") 23 | ) 24 | 25 | const ( 26 | socksVer5 = 5 27 | socksCmdConnect = 1 28 | ) 29 | 30 | func handShake(conn net.Conn) (err error) { 31 | const ( 32 | idVer = 0 33 | idNmethod = 1 34 | ) 35 | // version identification and method selection message in theory can have 36 | // at most 256 methods, plus version and nmethod field in total 258 bytes 37 | // the current rfc defines only 3 authentication methods (plus 2 reserved), 38 | // so it won't be such long in practice 39 | 40 | buf := make([]byte, 258) 41 | 42 | var n int 43 | //ss.SetReadTimeout(conn) 44 | // make sure we get the nmethod field 45 | if n, err = io.ReadAtLeast(conn, buf, idNmethod+1); err != nil { 46 | return 47 | } 48 | if buf[idVer] != socksVer5 { 49 | return errVer 50 | } 51 | nmethod := int(buf[idNmethod]) 52 | msgLen := nmethod + 2 53 | if n == msgLen { // handshake done, common case 54 | // do nothing, jump directly to send confirmation 55 | } else if n < msgLen { // has more methods to read, rare case 56 | if _, err = io.ReadFull(conn, buf[n:msgLen]); err != nil { 57 | return 58 | } 59 | } else { // error, should not get extra data 60 | return errAuthExtraData 61 | } 62 | // send confirmation: version 5, no authentication required 63 | _, err = conn.Write([]byte{socksVer5, 0}) 64 | return 65 | } 66 | 67 | func getRequest(conn net.Conn) (rawaddr []byte, host string, err error) { 68 | const ( 69 | idVer = 0 70 | idCmd = 1 71 | idType = 3 // address type index 72 | idIP0 = 4 // ip address start index 73 | idDmLen = 4 // domain address length index 74 | idDm0 = 5 // domain address start index 75 | 76 | typeIPv4 = 1 // type is ipv4 address 77 | typeDm = 3 // type is domain address 78 | typeIPv6 = 4 // type is ipv6 address 79 | 80 | lenIPv4 = 3 + 1 + net.IPv4len + 2 // 3(ver+cmd+rsv) + 1addrType + ipv4 + 2port 81 | lenIPv6 = 3 + 1 + net.IPv6len + 2 // 3(ver+cmd+rsv) + 1addrType + ipv6 + 2port 82 | lenDmBase = 3 + 1 + 1 + 2 // 3 + 1addrType + 1addrLen + 2port, plus addrLen 83 | ) 84 | // refer to getRequest in server.go for why set buffer size to 263 85 | buf := make([]byte, 263) 86 | var n int 87 | //ss.SetReadTimeout(conn) 88 | // read till we get possible domain length field 89 | if n, err = io.ReadAtLeast(conn, buf, idDmLen+1); err != nil { 90 | return 91 | } 92 | // check version and cmd 93 | if buf[idVer] != socksVer5 { 94 | err = errVer 95 | return 96 | } 97 | if buf[idCmd] != socksCmdConnect { 98 | err = errCmd 99 | return 100 | } 101 | 102 | reqLen := -1 103 | switch buf[idType] { 104 | case typeIPv4: 105 | reqLen = lenIPv4 106 | case typeIPv6: 107 | reqLen = lenIPv6 108 | case typeDm: 109 | reqLen = int(buf[idDmLen]) + lenDmBase 110 | default: 111 | err = errAddrType 112 | return 113 | } 114 | 115 | if n == reqLen { 116 | // common case, do nothing 117 | } else if n < reqLen { // rare case 118 | if _, err = io.ReadFull(conn, buf[n:reqLen]); err != nil { 119 | return 120 | } 121 | } else { 122 | err = errReqExtraData 123 | return 124 | } 125 | 126 | rawaddr = buf[idType:reqLen] 127 | 128 | switch buf[idType] { 129 | case typeIPv4: 130 | host = net.IP(buf[idIP0 : idIP0+net.IPv4len]).String() 131 | case typeIPv6: 132 | host = net.IP(buf[idIP0 : idIP0+net.IPv6len]).String() 133 | case typeDm: 134 | host = string(buf[idDm0 : idDm0+buf[idDmLen]]) 135 | } 136 | port := binary.BigEndian.Uint16(buf[reqLen-2 : reqLen]) 137 | host = net.JoinHostPort(host, strconv.Itoa(int(port))) 138 | 139 | return 140 | } 141 | -------------------------------------------------------------------------------- /websocks.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | "os/exec" 9 | "runtime" 10 | 11 | "github.com/lzjluzijie/websocks/client" 12 | "github.com/lzjluzijie/websocks/core" 13 | "github.com/lzjluzijie/websocks/server" 14 | "github.com/urfave/cli" 15 | ) 16 | 17 | func main() { 18 | app := cli.App{ 19 | Name: "WebSocks", 20 | /* 21 | todo more websocket connections 22 | todo better log 23 | todo better stats 24 | */ 25 | Version: "0.15.1", 26 | Usage: "A secure proxy based on WebSocket.", 27 | Description: "websocks.org", 28 | Author: "Halulu", 29 | Email: "lzjluzijie@gmail.com", 30 | Commands: []cli.Command{ 31 | { 32 | Name: "client", 33 | Aliases: []string{"c"}, 34 | Usage: "start websocks client", 35 | Flags: []cli.Flag{ 36 | cli.StringFlag{ 37 | Name: "l", 38 | Value: "127.0.0.1:10801", 39 | Usage: "local listening port", 40 | }, 41 | cli.StringFlag{ 42 | Name: "s", 43 | Value: "ws://localhost:23333/websocks", 44 | Usage: "server url", 45 | }, 46 | cli.BoolFlag{ 47 | Name: "mux", 48 | //todo 49 | Usage: "mux mode(test)", 50 | }, 51 | cli.StringFlag{ 52 | Name: "sni", 53 | Value: "", 54 | Usage: "fake server name for tls client hello, leave blank to disable", 55 | }, 56 | cli.BoolFlag{ 57 | Name: "insecure", 58 | Usage: "InsecureSkipVerify: true", 59 | }, 60 | }, 61 | Action: func(c *cli.Context) (err error) { 62 | listenAddr := c.String("l") 63 | serverURL := c.String("s") 64 | mux := c.Bool("mux") 65 | sni := c.String("sni") 66 | insecureCert := false 67 | if c.Bool("insecure") { 68 | insecureCert = true 69 | } 70 | 71 | config := &client.Config{ 72 | ListenAddr: listenAddr, 73 | ServerURL: serverURL, 74 | SNI: sni, 75 | InsecureCert: insecureCert, 76 | Mux: mux, 77 | } 78 | 79 | wc, err := config.GetClient() 80 | if err != nil { 81 | return 82 | } 83 | 84 | err = wc.Run() 85 | return 86 | }, 87 | }, 88 | { 89 | Name: "server", 90 | Aliases: []string{"s"}, 91 | Usage: "start websocks server", 92 | Flags: []cli.Flag{ 93 | cli.StringFlag{ 94 | Name: "l", 95 | Value: "0.0.0.0:23333", 96 | Usage: "local listening port", 97 | }, 98 | cli.StringFlag{ 99 | Name: "p", 100 | Value: "/websocks", 101 | Usage: "websocks.org/pattern", 102 | }, 103 | cli.BoolFlag{ 104 | Name: "tls", 105 | Usage: "enable built-in tls", 106 | }, 107 | cli.StringFlag{ 108 | Name: "cert", 109 | Value: "websocks.cer", 110 | Usage: "tls cert path", 111 | }, 112 | cli.StringFlag{ 113 | Name: "key", 114 | Value: "websocks.key", 115 | Usage: "tls key path", 116 | }, 117 | cli.StringFlag{ 118 | Name: "reverse-proxy", 119 | Value: "", 120 | Usage: "reverse proxy url, leave blank to disable", 121 | }, 122 | }, 123 | Action: func(c *cli.Context) (err error) { 124 | listenAddr := c.String("l") 125 | pattern := c.String("p") 126 | tls := c.Bool("tls") 127 | certPath := c.String("cert") 128 | keyPath := c.String("key") 129 | reverseProxy := c.String("reverse-proxy") 130 | 131 | if pattern[0] != '/' { 132 | pattern = "/" + pattern 133 | } 134 | 135 | config := server.Config{ 136 | Pattern: pattern, 137 | ListenAddr: listenAddr, 138 | TLS: tls, 139 | CertPath: certPath, 140 | KeyPath: keyPath, 141 | ReverseProxy: reverseProxy, 142 | } 143 | 144 | websocksServer := config.GetServer() 145 | log.Printf("Listening at %s", listenAddr) 146 | err = websocksServer.Run() 147 | if err != nil { 148 | return 149 | } 150 | 151 | return 152 | }, 153 | }, 154 | { 155 | Name: "cert", 156 | Aliases: []string{"cert"}, 157 | Usage: "generate self signed key and cert(default rsa 2048)", 158 | Flags: []cli.Flag{ 159 | cli.BoolFlag{ 160 | Name: "ecdsa", 161 | Usage: "generate ecdsa key and cert(P-256)", 162 | }, 163 | cli.StringSliceFlag{ 164 | Name: "hosts", 165 | Value: nil, 166 | Usage: "certificate hosts", 167 | }, 168 | }, 169 | Action: func(c *cli.Context) (err error) { 170 | ecdsa := c.Bool("ecdsa") 171 | hosts := c.StringSlice("hosts") 172 | 173 | var key, cert []byte 174 | if ecdsa { 175 | key, cert, err = core.GenP256(hosts) 176 | log.Printf("Generated ecdsa P-256 key and cert") 177 | } else { 178 | key, cert, err = core.GenRSA2048(hosts) 179 | log.Printf("Generated rsa 2048 key and cert") 180 | } 181 | 182 | err = ioutil.WriteFile("websocks.key", key, 0600) 183 | if err != nil { 184 | return 185 | } 186 | err = ioutil.WriteFile("websocks.cer", cert, 0600) 187 | if err != nil { 188 | return 189 | } 190 | return 191 | }, 192 | }, 193 | { 194 | Name: "pac", 195 | Aliases: []string{"pac"}, 196 | Usage: "set pac for windows(test)", 197 | Action: func(c *cli.Context) (err error) { 198 | if runtime.GOOS != "windows" { 199 | err = errors.New("not windows") 200 | return 201 | } 202 | 203 | err = exec.Command("REG", "ADD", `HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings`, "/v", "AutoConfigURL", "/d", "http://127.0.0.1:10801/pac", "/f").Run() 204 | return 205 | }, 206 | }, 207 | }, 208 | } 209 | 210 | ////pprof debug 211 | //go func() { 212 | // f, err := os.Create(fmt.Sprintf("%d.prof", time.Now().Unix())) 213 | // if err != nil { 214 | // panic(err) 215 | // } 216 | // 217 | // err = pprof.StartCPUProfile(f) 218 | // if err != nil { 219 | // panic(err) 220 | // } 221 | // 222 | // time.Sleep(time.Second * 30) 223 | // pprof.StopCPUProfile() 224 | // os.Exit(0) 225 | //}() 226 | 227 | err := app.Run(os.Args) 228 | if err != nil { 229 | log.Printf(err.Error()) 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------