From 5e41afbe30d9675dc1c356eddd626f51d2b9c372 Mon Sep 17 00:00:00 2001 From: liminggui Date: Tue, 30 Mar 2021 23:31:23 +0800 Subject: [PATCH 01/32] websocket --- examples/websocket/client.go | 76 ++++++++++++++++++++++++++ examples/websocket/conn.go | 101 +++++++++++++++++++++++++++++++++++ examples/websocket/hub.go | 54 +++++++++++++++++++ examples/websocket/main.go | 35 ++++++++++++ 4 files changed, 266 insertions(+) create mode 100644 examples/websocket/client.go create mode 100644 examples/websocket/conn.go create mode 100644 examples/websocket/hub.go create mode 100644 examples/websocket/main.go diff --git a/examples/websocket/client.go b/examples/websocket/client.go new file mode 100644 index 0000000..44bd4dd --- /dev/null +++ b/examples/websocket/client.go @@ -0,0 +1,76 @@ +// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build ignore + +package main + +import ( + "flag" + "log" + "net/url" + "os" + "os/signal" + "time" + + "github.com/gorilla/websocket" +) + +var addr = flag.String("addr", "localhost:8080", "http service address") + +func main() { + flag.Parse() + log.SetFlags(0) + + interrupt := make(chan os.Signal, 1) + signal.Notify(interrupt, os.Interrupt) + + u := url.URL{Scheme: "ws", Host: *addr, Path: "/ws"} + log.Printf("connecting to %s", u.String()) + + c, _, err := websocket.DefaultDialer.Dial(u.String(), nil) + if err != nil { + log.Fatal("dial:", err) + } + defer c.Close() + + done := make(chan struct{}) + + go func() { + defer close(done) + for { + _, message, err := c.ReadMessage() + if err != nil { + log.Println("read:", err) + return + } + log.Printf("recv: %s", message) + } + }() + + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + + for { + select { + case <-done: + return + case <-interrupt: + log.Println("interrupt") + + // Cleanly close the connection by sending a close message and then + // waiting (with timeout) for the server to close the connection. + err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) + if err != nil { + log.Println("write close:", err) + return + } + select { + case <-done: + case <-time.After(time.Second): + } + return + } + } +} diff --git a/examples/websocket/conn.go b/examples/websocket/conn.go new file mode 100644 index 0000000..b7dc933 --- /dev/null +++ b/examples/websocket/conn.go @@ -0,0 +1,101 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "log" + "net/http" + "time" + + "github.com/gorilla/websocket" +) + +const ( + // Time allowed to write a message to the peer. + writeWait = 10 * time.Second + + // Time allowed to read the next pong message from the peer. + pongWait = 60 * time.Second + + // Send pings to peer with this period. Must be less than pongWait. + pingPeriod = (pongWait * 9) / 10 + + // Maximum message size allowed from peer. + maxMessageSize = 512 +) + +var ( + newline = []byte{'\n'} + space = []byte{' '} +) + +var upgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, +} + +// Client is a middleman between the websocket connection and the hub. +type Client struct { + hub *Hub + + // The websocket connection. + conn *websocket.Conn + + // Buffered channel of outbound messages. + send chan []byte +} + +// writePump pumps messages from the hub to the websocket connection. +// +// A goroutine running writePump is started for each connection. The +// application ensures that there is at most one writer to a connection by +// executing all writes from this goroutine. +func (c *Client) writePump() { + ticker := time.NewTicker(pingPeriod) + defer func() { + ticker.Stop() + c.conn.Close() + }() + for { + select { + case message, ok := <-c.send: + c.conn.SetWriteDeadline(time.Now().Add(writeWait)) + if !ok { + // The hub closed the channel. + c.conn.WriteMessage(websocket.CloseMessage, []byte{}) + return + } + + w, err := c.conn.NextWriter(websocket.BinaryMessage) + if err != nil { + return + } + w.Write(message) + if err := w.Close(); err != nil { + return + } + case <-ticker.C: + c.conn.SetWriteDeadline(time.Now().Add(writeWait)) + if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil { + return + } + } + } +} + +// serveWs handles websocket requests from the peer. +func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Println(err) + return + } + client := &Client{hub: hub, conn: conn, send: make(chan []byte, 256)} + client.hub.register <- client + + // Allow collection of memory referenced by the caller by doing all work in + // new goroutines. + go client.writePump() +} diff --git a/examples/websocket/hub.go b/examples/websocket/hub.go new file mode 100644 index 0000000..960b046 --- /dev/null +++ b/examples/websocket/hub.go @@ -0,0 +1,54 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// Hub maintains the set of active clients and broadcasts messages to the +// clients. +type Hub struct { + // Registered clients. + clients map[*Client]bool + + // Inbound messages from the clients. + broadcast chan []byte + + // Register requests from the clients. + register chan *Client + + // Unregister requests from clients. + unregister chan *Client +} + +func newHub() *Hub { + return &Hub{ + broadcast: make(chan []byte), + register: make(chan *Client), + unregister: make(chan *Client), + clients: make(map[*Client]bool), + } +} + +func (h *Hub) run() { + for { + select { + case client := <-h.register: + h.clients[client] = true + case client := <-h.unregister: + if _, ok := h.clients[client]; ok { + delete(h.clients, client) + close(client.send) + } + case message := <-h.broadcast: + //fmt.Println(string(message)) + for client := range h.clients { + select { + case client.send <- message: + default: + close(client.send) + delete(h.clients, client) + } + } + } + } +} diff --git a/examples/websocket/main.go b/examples/websocket/main.go new file mode 100644 index 0000000..5a68eac --- /dev/null +++ b/examples/websocket/main.go @@ -0,0 +1,35 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "flag" + "fmt" + "log" + "net/http" + "time" +) + +var addr = flag.String("addr", ":8080", "http service address") + +func main() { + flag.Parse() + hub := newHub() + go hub.run() + go func() { + // 发布消息 + for { + hub.broadcast <- []byte(fmt.Sprintf("test - %d", time.Now().Second())) + time.Sleep(time.Duration(2) * time.Second) + } + }() + http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { + serveWs(hub, w, r) + }) + err := http.ListenAndServe(*addr, nil) + if err != nil { + log.Fatal("ListenAndServe: ", err) + } +} From 9d15dce9d1da61bea215fab44e29d157053ef43f Mon Sep 17 00:00:00 2001 From: liminggui Date: Thu, 1 Apr 2021 19:32:31 +0800 Subject: [PATCH 02/32] =?UTF-8?q?websocket=20=E5=86=85=E7=BD=91=E7=A9=BF?= =?UTF-8?q?=E9=80=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- anyproxy.go | 13 +++ examples/websocket/client.go | 3 - go.mod | 1 + go.sum | 2 + nat/client.go | 157 +++++++++++++++++++++++++++++++++++ nat/conn.go | 97 ++++++++++++++++++++++ nat/hub.go | 65 +++++++++++++++ nat/server.go | 20 +++++ proto/http.go | 12 +++ proto/websocket.go | 110 ++++++++++++++++++++++++ 10 files changed, 477 insertions(+), 3 deletions(-) create mode 100644 nat/client.go create mode 100644 nat/conn.go create mode 100644 nat/hub.go create mode 100644 nat/server.go create mode 100644 proto/websocket.go diff --git a/anyproxy.go b/anyproxy.go index b9524e9..11699bc 100644 --- a/anyproxy.go +++ b/anyproxy.go @@ -13,6 +13,7 @@ import ( "github.com/keminar/anyproxy/config" "github.com/keminar/anyproxy/grace" "github.com/keminar/anyproxy/logging" + "github.com/keminar/anyproxy/nat" "github.com/keminar/anyproxy/proto" "github.com/keminar/anyproxy/utils/conf" "github.com/keminar/anyproxy/utils/daemon" @@ -22,6 +23,8 @@ import ( var ( gListenAddrPort string gProxyServerSpec string + gWebsocketListen string + gWebsocketConn string gHelp bool gDebug int gPprof string @@ -31,6 +34,8 @@ func init() { flag.Usage = help.Usage flag.StringVar(&gListenAddrPort, "l", "", "Address and port to listen on") flag.StringVar(&gProxyServerSpec, "p", "", "Proxy servers to use") + flag.StringVar(&gWebsocketListen, "ws-listen", "", "Websocket address and port to listen on") + flag.StringVar(&gWebsocketConn, "ws-connect", "", "websocket Address and port to connect") flag.IntVar(&gDebug, "debug", 0, "debug mode (0, 1, 2)") flag.StringVar(&gPprof, "pprof", "", "pprof port, disable if empty") flag.BoolVar(&gHelp, "h", false, "This usage message") @@ -94,6 +99,14 @@ func main() { log.Println(http.ListenAndServe(gPprof, nil)) }() } + + if gWebsocketListen != "" { + go nat.NewServer(&gWebsocketListen) + } + + if gWebsocketConn != "" { + go nat.NewClient(&gWebsocketConn) + } server := grace.NewServer(gListenAddrPort, proto.ClientHandler) server.ListenAndServe() } diff --git a/examples/websocket/client.go b/examples/websocket/client.go index 44bd4dd..6142662 100644 --- a/examples/websocket/client.go +++ b/examples/websocket/client.go @@ -49,9 +49,6 @@ func main() { } }() - ticker := time.NewTicker(time.Second) - defer ticker.Stop() - for { select { case <-done: diff --git a/go.mod b/go.mod index baef4c9..8ef34dd 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/keminar/anyproxy go 1.12 require ( + github.com/gorilla/websocket v1.4.2 golang.org/x/net v0.0.0-20200602114024-627f9648deb9 gopkg.in/yaml.v2 v2.3.0 ) diff --git a/go.sum b/go.sum index b555ff7..e665047 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM= golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= diff --git a/nat/client.go b/nat/client.go new file mode 100644 index 0000000..073074d --- /dev/null +++ b/nat/client.go @@ -0,0 +1,157 @@ +package nat + +import ( + "fmt" + "io" + "log" + "net" + "net/url" + "os" + "os/signal" + "time" + + "github.com/gorilla/websocket" + "github.com/keminar/anyproxy/proto/tcp" +) + +var interruptClose bool + +var proxyConn net.Conn + +func NewClient(addr *string) { + interruptClose = false + interrupt := make(chan os.Signal, 1) + signal.Notify(interrupt, os.Interrupt) + + connTimeout := time.Duration(5) * time.Second + var err error + proxyConn, err = net.DialTimeout("tcp", fmt.Sprintf("%s:%d", "127.0.0.1", 4003), connTimeout) + fmt.Println(err) + + for { + conn(addr, interrupt) + if interruptClose { + break + } + } +} + +func copyBuffer(dst io.Writer, src io.Reader, srcname string) (written int64, err error) { + //如果设置过大会耗内存高,4k比较合理 + size := 4 * 1024 + buf := make([]byte, size) + i := 0 + for { + i++ + nr, er := src.Read(buf) + if nr > 0 { + fmt.Println("test", string(buf[0:nr])) + nw, ew := dst.Write(buf[0:nr]) + if nw > 0 { + written += int64(nw) + } + if ew != nil { + err = ew + break + } + if nr != nw { + err = io.ErrShortWrite + break + } + } + if er != nil { + if er != io.EOF { + err = er + } + break + } + } + return written, err +} + +type WsHelp struct { + c *websocket.Conn +} + +func newWsHelp(c *websocket.Conn) *WsHelp { + return &WsHelp{c: c} +} + +func (h *WsHelp) Read(p []byte) (n int, err error) { + _, message, err := h.c.ReadMessage() + n = copy(p, message) + return n, err +} + +func (h *WsHelp) Write(p []byte) (n int, err error) { + h.c.SetWriteDeadline(time.Now().Add(writeWait)) + + w, err := h.c.NextWriter(websocket.BinaryMessage) + if err != nil { + return 0, err + } + w.Write(p) + if err := w.Close(); err != nil { + return 0, err + } + return len(p), nil +} + +func conn(addr *string, interrupt chan os.Signal) { + + u := url.URL{Scheme: "ws", Host: *addr, Path: "/ws"} + log.Printf("connecting to %s", u.String()) + + c, _, err := websocket.DefaultDialer.Dial(u.String(), nil) + if err != nil { + log.Println("dial:", err) + time.Sleep(time.Duration(3) * time.Second) + return + } + defer c.Close() + + w := newWsHelp(c) + + done := make(chan struct{}) + + go func() { + defer close(done) + /*for { + _, message, err := c.ReadMessage() + if err != nil { + log.Println("read:", err) + return + } + log.Printf("recv: %s", message) + }*/ + go func() { + readSize, err := copyBuffer(proxyConn.(*net.TCPConn), w, "client") + fmt.Println(readSize, err) + }() + writeSize, err := copyBuffer(w, tcp.NewReader(proxyConn.(*net.TCPConn)), "server") + fmt.Println(writeSize, err) + }() + + for { + select { + case <-done: + return + case <-interrupt: + log.Println("interrupt") + interruptClose = true + + // Cleanly close the connection by sending a close message and then + // waiting (with timeout) for the server to close the connection. + err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) + if err != nil { + log.Println("write close:", err) + return + } + select { + case <-done: + case <-time.After(time.Second): + } + return + } + } +} diff --git a/nat/conn.go b/nat/conn.go new file mode 100644 index 0000000..c81b856 --- /dev/null +++ b/nat/conn.go @@ -0,0 +1,97 @@ +package nat + +import ( + "log" + "net/http" + "time" + + "github.com/gorilla/websocket" +) + +const ( + // Time allowed to write a message to the peer. + writeWait = 10 * time.Second + + // Time allowed to read the next pong message from the peer. + pongWait = 60 * time.Second + + // Send pings to peer with this period. Must be less than pongWait. + pingPeriod = (pongWait * 9) / 10 + + // Maximum message size allowed from peer. + maxMessageSize = 512 +) + +var ( + newline = []byte{'\n'} + space = []byte{' '} +) + +var upgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, +} + +// Client is a middleman between the websocket connection and the hub. +type Client struct { + hub *Hub + + // The websocket connection. + conn *websocket.Conn + + // Buffered channel of outbound messages. + send chan []byte +} + +// writePump pumps messages from the hub to the websocket connection. +// +// A goroutine running writePump is started for each connection. The +// application ensures that there is at most one writer to a connection by +// executing all writes from this goroutine. +func (c *Client) writePump() { + ticker := time.NewTicker(pingPeriod) + defer func() { + ticker.Stop() + c.conn.Close() + }() + for { + select { + case message, ok := <-c.send: + c.conn.SetWriteDeadline(time.Now().Add(writeWait)) + if !ok { + // The hub closed the channel. + c.conn.WriteMessage(websocket.CloseMessage, []byte{}) + return + } + + w, err := c.conn.NextWriter(websocket.BinaryMessage) + if err != nil { + return + } + w.Write(message) + if err := w.Close(); err != nil { + return + } + case <-ticker.C: + c.conn.SetWriteDeadline(time.Now().Add(writeWait)) + if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil { + return + } + } + } +} + +// serveWs handles websocket requests from the peer. +func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Println(err) + return + } + client := &Client{hub: hub, conn: conn, send: make(chan []byte)} + client.hub.register <- client + + // Allow collection of memory referenced by the caller by doing all work in + // new goroutines. + go client.writePump() +} diff --git a/nat/hub.go b/nat/hub.go new file mode 100644 index 0000000..458a442 --- /dev/null +++ b/nat/hub.go @@ -0,0 +1,65 @@ +package nat + +import "errors" + +// Hub maintains the set of active clients and broadcasts messages to the +// clients. +type Hub struct { + // Registered clients. + clients map[*Client]bool + + // Inbound messages from the clients. + broadcast chan []byte + + // Register requests from the clients. + register chan *Client + + // Unregister requests from clients. + unregister chan *Client +} + +func newHub() *Hub { + return &Hub{ + broadcast: make(chan []byte), + register: make(chan *Client), + unregister: make(chan *Client), + clients: make(map[*Client]bool), + } +} + +func (h *Hub) run() { + for { + select { + case client := <-h.register: + h.clients[client] = true + case client := <-h.unregister: + if _, ok := h.clients[client]; ok { + delete(h.clients, client) + close(client.send) + } + case message := <-h.broadcast: + for client := range h.clients { + select { + case client.send <- message: + default: + close(client.send) + delete(h.clients, client) + } + } + } + } +} + +func (h *Hub) Write(p []byte) (n int, err error) { + h.broadcast <- p + return len(p), nil +} + +func (h *Hub) Read(p []byte) (n int, err error) { + for client := range h.clients { + _, message, err := client.conn.ReadMessage() + n = copy(p, message) + return n, err + } + return 0, errors.New("fail") +} diff --git a/nat/server.go b/nat/server.go new file mode 100644 index 0000000..2de49ef --- /dev/null +++ b/nat/server.go @@ -0,0 +1,20 @@ +package nat + +import ( + "log" + "net/http" +) + +var WsHub *Hub + +func NewServer(addr *string) { + WsHub = newHub() + go WsHub.run() + http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { + serveWs(WsHub, w, r) + }) + err := http.ListenAndServe(*addr, nil) + if err != nil { + log.Fatal("ListenAndServe: ", err) + } +} diff --git a/proto/http.go b/proto/http.go index 13f0b5f..97cc5c8 100644 --- a/proto/http.go +++ b/proto/http.go @@ -258,6 +258,18 @@ func (that *httpStream) badRequest(err error) { } func (that *httpStream) response() error { + if test, ok := that.Header["Anyproxy-Action"]; ok && test[0] == "websocket" { + that.Header.Del("Anyproxy-Action") + tunnel := newWsTunnel(that.req) + // 先将请求头部发出 + tunnel.buffer.Write([]byte(fmt.Sprintf("%s\r\n", that.FirstLine))) + that.Header.Write(tunnel.buffer) + tunnel.buffer.Write([]byte("\r\n")) + // 多读取的body部分 + tunnel.buffer.Write(that.BodyBuf) + tunnel.transfer() + return nil + } tunnel := newTunnel(that.req) if ip, ok := tunnel.isAllowed(); !ok { err := errors.New(ip + " is not allowed") diff --git a/proto/websocket.go b/proto/websocket.go new file mode 100644 index 0000000..b571d10 --- /dev/null +++ b/proto/websocket.go @@ -0,0 +1,110 @@ +package proto + +import ( + "bytes" + "fmt" + "io" + "log" + + "github.com/keminar/anyproxy/config" + "github.com/keminar/anyproxy/nat" + "github.com/keminar/anyproxy/utils/trace" +) + +// 转发实体 +type wsTunnel struct { + req *Request + + readSize int64 + writeSize int64 + + buffer *bytes.Buffer +} + +// newTunnel 实例 +func newWsTunnel(req *Request) *wsTunnel { + s := &wsTunnel{ + req: req, + buffer: new(bytes.Buffer), + } + return s +} + +// copyBuffer 传输数据 +func (s *wsTunnel) copyBuffer(dst io.Writer, src io.Reader, srcname string) (written int64, err error) { + //如果设置过大会耗内存高,4k比较合理 + size := 4 * 1024 + buf := make([]byte, size) + i := 0 + for { + i++ + if config.DebugLevel >= config.LevelDebug { + log.Printf("%s receive from %s, n=%d\n", trace.ID(s.req.ID), srcname, i) + } + nr, er := src.Read(buf) + if nr > 0 { + if config.DebugLevel >= config.LevelDebugBody { + log.Printf("%s receive from %s, n=%d, data len: %d\n", trace.ID(s.req.ID), srcname, i, nr) + fmt.Println(trace.ID(s.req.ID), string(buf[0:nr])) + } + nw, ew := dst.Write(buf[0:nr]) + if nw > 0 { + written += int64(nw) + } + if ew != nil { + err = ew + break + } + if nr != nw { + err = io.ErrShortWrite + break + } + } + if er != nil { + if er != io.EOF { + err = er + } + break + } + } + return written, err +} + +// transfer 交换数据 +func (s *wsTunnel) transfer() { + if config.DebugLevel >= config.LevelLong { + log.Println(trace.ID(s.req.ID), "transfer start") + } + var err error + done := make(chan int, 1) + + //发送请求 + go func() { + defer func() { + done <- 1 + close(done) + }() + nat.WsHub.Write([]byte(s.buffer.String())) + s.readSize, err = s.copyBuffer(nat.WsHub, s.req.reader, "client") + s.logCopyErr("client->websocket", err) + log.Println(trace.ID(s.req.ID), "request body size", s.readSize) + }() + //取返回结果 + s.writeSize, err = s.copyBuffer(s.req.conn, nat.WsHub, "websocket") + s.logCopyErr("websocket->client", err) + + <-done + // 不管是不是正常结束,只要server结束了,函数就会返回,然后底层会自动断开与client的连接 + //log.Println(trace.ID(s.req.ID), "transfer finished, response size", s.writeSize) +} + +func (s *wsTunnel) logCopyErr(name string, err error) { + if err == nil { + return + } + if config.DebugLevel >= config.LevelLong { + log.Println(trace.ID(s.req.ID), name, err.Error()) + } else if err != io.EOF { + log.Println(trace.ID(s.req.ID), name, err.Error()) + } +} From c9d2d4126cc12d44fc8f21833109cac31d4207ab Mon Sep 17 00:00:00 2001 From: liminggui Date: Fri, 2 Apr 2021 10:34:41 +0800 Subject: [PATCH 03/32] =?UTF-8?q?=E7=AE=80=E5=8D=95=E5=AE=9E=E7=8E=B0ws?= =?UTF-8?q?=E9=80=9A=E4=BF=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nat/client.go | 56 +++++++++++++++++++++++++++++++++++++--------- proto/websocket.go | 13 ++++++++++- 2 files changed, 57 insertions(+), 12 deletions(-) diff --git a/nat/client.go b/nat/client.go index 073074d..6e5a4b1 100644 --- a/nat/client.go +++ b/nat/client.go @@ -17,17 +17,14 @@ import ( var interruptClose bool var proxyConn net.Conn +var proxyBool bool func NewClient(addr *string) { interruptClose = false interrupt := make(chan os.Signal, 1) signal.Notify(interrupt, os.Interrupt) - connTimeout := time.Duration(5) * time.Second - var err error - proxyConn, err = net.DialTimeout("tcp", fmt.Sprintf("%s:%d", "127.0.0.1", 4003), connTimeout) - fmt.Println(err) - + proxyBool = false for { conn(addr, interrupt) if interruptClose { @@ -36,6 +33,16 @@ func NewClient(addr *string) { } } +func dial() { + connTimeout := time.Duration(5) * time.Second + var err error + proxyConn, err = net.DialTimeout("tcp", fmt.Sprintf("%s:%d", "127.0.0.1", 4003), connTimeout) + if err != nil { + fmt.Println("dial self", err) + } + proxyBool = true +} + func copyBuffer(dst io.Writer, src io.Reader, srcname string) (written int64, err error) { //如果设置过大会耗内存高,4k比较合理 size := 4 * 1024 @@ -45,6 +52,10 @@ func copyBuffer(dst io.Writer, src io.Reader, srcname string) (written int64, er i++ nr, er := src.Read(buf) if nr > 0 { + if srcname == "client" && string(buf[0:nr]) == "ok" { + fmt.Println("recv ok") + break + } fmt.Println("test", string(buf[0:nr])) nw, ew := dst.Write(buf[0:nr]) if nw > 0 { @@ -124,12 +135,35 @@ func conn(addr *string, interrupt chan os.Signal) { } log.Printf("recv: %s", message) }*/ - go func() { - readSize, err := copyBuffer(proxyConn.(*net.TCPConn), w, "client") - fmt.Println(readSize, err) - }() - writeSize, err := copyBuffer(w, tcp.NewReader(proxyConn.(*net.TCPConn)), "server") - fmt.Println(writeSize, err) + for { + if proxyBool == false { + dial() + } + // 如果与服务器断开,需要重连 + reConn := false + done2 := make(chan int, 1) + go func() { + defer func() { + done2 <- 1 + close(done2) + }() + readSize, err := copyBuffer(proxyConn.(*net.TCPConn), w, "client") + fmt.Println("websocket->client", readSize, err) + if err != nil { + reConn = true + } + proxyConn.(*net.TCPConn).CloseWrite() + proxyBool = false + }() + writeSize, err := copyBuffer(w, tcp.NewReader(proxyConn.(*net.TCPConn)), "websocket") + fmt.Println("client->websocket", writeSize, err) + w.Write([]byte("ok")) + <-done2 + log.Println("websocket transfer finished, response size", writeSize) + if reConn { + break + } + } }() for { diff --git a/proto/websocket.go b/proto/websocket.go index b571d10..ac0a2ed 100644 --- a/proto/websocket.go +++ b/proto/websocket.go @@ -43,6 +43,10 @@ func (s *wsTunnel) copyBuffer(dst io.Writer, src io.Reader, srcname string) (wri } nr, er := src.Read(buf) if nr > 0 { + if srcname == "websocket" && string(buf[0:nr]) == "ok" { + fmt.Println("recv ok") + break + } if config.DebugLevel >= config.LevelDebugBody { log.Printf("%s receive from %s, n=%d, data len: %d\n", trace.ID(s.req.ID), srcname, i, nr) fmt.Println(trace.ID(s.req.ID), string(buf[0:nr])) @@ -64,8 +68,15 @@ func (s *wsTunnel) copyBuffer(dst io.Writer, src io.Reader, srcname string) (wri if er != io.EOF { err = er } + + if srcname == "client" { + fmt.Println("send ok") + // 当客户端断开或出错了,服务端也不用再读了,可以关闭,解决读Server卡住不能到EOF的问题 + dst.Write([]byte("ok")) + } break } + } return written, err } @@ -95,7 +106,7 @@ func (s *wsTunnel) transfer() { <-done // 不管是不是正常结束,只要server结束了,函数就会返回,然后底层会自动断开与client的连接 - //log.Println(trace.ID(s.req.ID), "transfer finished, response size", s.writeSize) + log.Println(trace.ID(s.req.ID), "websocket transfer finished, response size", s.writeSize) } func (s *wsTunnel) logCopyErr(name string, err error) { From 05ac454f6c2124276bf37bc34548adcd2b7b21e9 Mon Sep 17 00:00:00 2001 From: liminggui Date: Fri, 2 Apr 2021 17:08:23 +0800 Subject: [PATCH 04/32] =?UTF-8?q?=E6=B6=88=E6=81=AF=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nat/client.go | 101 ++++++++++++++++++++++++++++++++++++++----------- nat/conn.go | 26 ++++++++++++- nat/message.go | 21 ++++++++++ 3 files changed, 125 insertions(+), 23 deletions(-) create mode 100644 nat/message.go diff --git a/nat/client.go b/nat/client.go index 6e5a4b1..91bf7bf 100644 --- a/nat/client.go +++ b/nat/client.go @@ -1,6 +1,7 @@ package nat import ( + "errors" "fmt" "io" "log" @@ -11,6 +12,7 @@ import ( "time" "github.com/gorilla/websocket" + "github.com/keminar/anyproxy/config" "github.com/keminar/anyproxy/proto/tcp" ) @@ -36,10 +38,12 @@ func NewClient(addr *string) { func dial() { connTimeout := time.Duration(5) * time.Second var err error - proxyConn, err = net.DialTimeout("tcp", fmt.Sprintf("%s:%d", "127.0.0.1", 4003), connTimeout) + localProxy := fmt.Sprintf("%s:%d", "127.0.0.1", config.ListenPort) + proxyConn, err = net.DialTimeout("tcp", localProxy, connTimeout) if err != nil { - fmt.Println("dial self", err) + fmt.Println("dial local proxy", err) } + log.Printf("websocket connecting to %s", localProxy) proxyBool = true } @@ -80,21 +84,21 @@ func copyBuffer(dst io.Writer, src io.Reader, srcname string) (written int64, er return written, err } -type WsHelp struct { +type ClientHandler struct { c *websocket.Conn } -func newWsHelp(c *websocket.Conn) *WsHelp { - return &WsHelp{c: c} +func newClientHandler(c *websocket.Conn) *ClientHandler { + return &ClientHandler{c: c} } -func (h *WsHelp) Read(p []byte) (n int, err error) { +func (h *ClientHandler) Read(p []byte) (n int, err error) { _, message, err := h.c.ReadMessage() n = copy(p, message) return n, err } -func (h *WsHelp) Write(p []byte) (n int, err error) { +func (h *ClientHandler) Write(p []byte) (n int, err error) { h.c.SetWriteDeadline(time.Now().Add(writeWait)) w, err := h.c.NextWriter(websocket.BinaryMessage) @@ -108,6 +112,43 @@ func (h *WsHelp) Write(p []byte) (n int, err error) { return len(p), nil } +func (h *ClientHandler) Auth(user string, token string) error { + msg := AuthMessage{User: user, Token: token} + return h.ask(&msg) +} + +func (h *ClientHandler) Subscribe(key string, val string) error { + msg := SubscribeMessage{Key: key, Val: val} + return h.ask(&msg) +} + +func (h *ClientHandler) ask(v interface{}) error { + err := h.c.WriteJSON(v) + if err != nil { + return err + } + ticker := time.NewTicker(3 * time.Second) + defer func() { + ticker.Stop() + }() + + send := make(chan []byte) + go func() { + defer close(send) + _, message, _ := h.c.ReadMessage() + send <- message + }() + select { + case message := <-send: + if string(message) != "ok" { + return errors.New("fail, " + string(message)) + } + case <-ticker.C: + return errors.New("timeout") + } + return nil +} + func conn(addr *string, interrupt chan os.Signal) { u := url.URL{Scheme: "ws", Host: *addr, Path: "/ws"} @@ -121,20 +162,24 @@ func conn(addr *string, interrupt chan os.Signal) { } defer c.Close() - w := newWsHelp(c) + w := newClientHandler(c) + err = w.Auth("111", "test") + if err != nil { + log.Println("auth:", err) + time.Sleep(time.Duration(3) * time.Second) + return + } + err = w.Subscribe("aa", "bb") + if err != nil { + log.Println("subscribe:", err) + time.Sleep(time.Duration(3) * time.Second) + return + } + log.Println("websocket auth and subscribe ok") done := make(chan struct{}) - go func() { defer close(done) - /*for { - _, message, err := c.ReadMessage() - if err != nil { - log.Println("read:", err) - return - } - log.Printf("recv: %s", message) - }*/ for { if proxyBool == false { dial() @@ -148,18 +193,19 @@ func conn(addr *string, interrupt chan os.Signal) { close(done2) }() readSize, err := copyBuffer(proxyConn.(*net.TCPConn), w, "client") - fmt.Println("websocket->client", readSize, err) + log.Println("request body size", readSize) + logCopyErr("websocket->client", err) + proxyConn.(*net.TCPConn).CloseWrite() + proxyBool = false if err != nil { reConn = true } - proxyConn.(*net.TCPConn).CloseWrite() - proxyBool = false }() writeSize, err := copyBuffer(w, tcp.NewReader(proxyConn.(*net.TCPConn)), "websocket") - fmt.Println("client->websocket", writeSize, err) + log.Println("websocket transfer finished, response size", writeSize) + logCopyErr("client->websocket", err) w.Write([]byte("ok")) <-done2 - log.Println("websocket transfer finished, response size", writeSize) if reConn { break } @@ -189,3 +235,14 @@ func conn(addr *string, interrupt chan os.Signal) { } } } + +func logCopyErr(name string, err error) { + if err == nil { + return + } + if config.DebugLevel >= config.LevelLong { + log.Println(name, err.Error()) + } else if err != io.EOF { + log.Println(name, err.Error()) + } +} diff --git a/nat/conn.go b/nat/conn.go index c81b856..20254b6 100644 --- a/nat/conn.go +++ b/nat/conn.go @@ -41,6 +41,9 @@ type Client struct { // Buffered channel of outbound messages. send chan []byte + + User string + Subscribe SubscribeMessage } // writePump pumps messages from the hub to the websocket connection. @@ -88,7 +91,28 @@ func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) { log.Println(err) return } - client := &Client{hub: hub, conn: conn, send: make(chan []byte)} + + var user AuthMessage + err = conn.ReadJSON(&user) + if err != nil { + log.Println(err) + return + } + if user.User == "111" && user.Token == "test" { + conn.WriteMessage(websocket.TextMessage, []byte("ok")) + } else { + conn.WriteMessage(websocket.TextMessage, []byte("token err")) + } + + var subscribe SubscribeMessage + err = conn.ReadJSON(&subscribe) + if err != nil { + log.Println(err) + return + } + conn.WriteMessage(websocket.TextMessage, []byte("ok")) + + client := &Client{hub: hub, conn: conn, send: make(chan []byte), User: user.User, Subscribe: subscribe} client.hub.register <- client // Allow collection of memory referenced by the caller by doing all work in diff --git a/nat/message.go b/nat/message.go new file mode 100644 index 0000000..dcafb58 --- /dev/null +++ b/nat/message.go @@ -0,0 +1,21 @@ +package nat + +type AuthMessage struct { + User string + Token string +} + +type SubscribeMessage struct { + Key string + Val string +} + +type AnswerMessage struct { + State int + Msg string +} + +type Message struct { + User string + Body []byte +} From a400ecf8f992a9e275debf7dfcbe9fa36c631345 Mon Sep 17 00:00:00 2001 From: liminggui Date: Fri, 2 Apr 2021 18:07:44 +0800 Subject: [PATCH 05/32] =?UTF-8?q?=E4=B8=B4=E6=97=B6=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- anyproxy.go | 2 +- nat/bridge.go | 11 ++++++ nat/bridge_hub.go | 71 +++++++++++++++++++++++++++++++++++ nat/client.go | 54 +++++++++++++++++++++++++- nat/{hub.go => client_hub.go} | 0 nat/conn.go | 52 ------------------------- nat/message.go | 5 ++- nat/server.go | 4 ++ proto/websocket.go | 3 ++ 9 files changed, 146 insertions(+), 56 deletions(-) create mode 100644 nat/bridge.go create mode 100644 nat/bridge_hub.go rename nat/{hub.go => client_hub.go} (100%) diff --git a/anyproxy.go b/anyproxy.go index 11699bc..3f05b7b 100644 --- a/anyproxy.go +++ b/anyproxy.go @@ -105,7 +105,7 @@ func main() { } if gWebsocketConn != "" { - go nat.NewClient(&gWebsocketConn) + go nat.ConnectServer(&gWebsocketConn) } server := grace.NewServer(gListenAddrPort, proto.ClientHandler) server.ListenAndServe() diff --git a/nat/bridge.go b/nat/bridge.go new file mode 100644 index 0000000..07da429 --- /dev/null +++ b/nat/bridge.go @@ -0,0 +1,11 @@ +package nat + +import "net" + +type Bridge struct { + reqID uint + conn *net.TCPConn + + // Buffered channel of outbound messages. + send chan []byte +} diff --git a/nat/bridge_hub.go b/nat/bridge_hub.go new file mode 100644 index 0000000..6f15440 --- /dev/null +++ b/nat/bridge_hub.go @@ -0,0 +1,71 @@ +package nat + +import ( + "errors" + "net" +) + +type BridgeHub struct { + // Registered clients. + bridges map[*Bridge]bool + + // Inbound messages from the clients. + broadcast chan []byte + + // Register requests from the clients. + register chan *Bridge + + // Unregister requests from clients. + unregister chan *Bridge +} + +func newBridgeHub() *BridgeHub { + return &BridgeHub{ + broadcast: make(chan []byte), + register: make(chan *Bridge), + unregister: make(chan *Bridge), + bridges: make(map[*Bridge]bool), + } +} + +func (h *BridgeHub) run() { + for { + select { + case bridge := <-h.register: + h.bridges[bridge] = true + case bridge := <-h.unregister: + if _, ok := h.bridges[bridge]; ok { + delete(h.bridges, bridge) + close(bridge.send) + } + case message := <-h.broadcast: + for bridge := range h.bridges { + select { + case bridge.send <- message: + default: + close(bridge.send) + delete(h.bridges, bridge) + } + } + } + } +} + +func (h *BridgeHub) Write(p []byte) (n int, err error) { + h.broadcast <- p + return len(p), nil +} + +func (h *BridgeHub) Read(p []byte) (n int, err error) { + for bridge := range h.bridges { + n, err := bridge.conn.Read(p) + return n, err + } + return 0, errors.New("fail") +} + +func (h *BridgeHub) Register(ID uint, conn *net.TCPConn) { + b := &Bridge{reqID: ID, conn: conn, send: make(chan []byte)} + h.register <- b + //go b.writePump() +} diff --git a/nat/client.go b/nat/client.go index 91bf7bf..f460982 100644 --- a/nat/client.go +++ b/nat/client.go @@ -21,7 +21,59 @@ var interruptClose bool var proxyConn net.Conn var proxyBool bool -func NewClient(addr *string) { +// Client is a middleman between the websocket connection and the hub. +type Client struct { + hub *Hub + + // The websocket connection. + conn *websocket.Conn + + // Buffered channel of outbound messages. + send chan []byte + + User string + Subscribe SubscribeMessage +} + +// writePump pumps messages from the hub to the websocket connection. +// +// A goroutine running writePump is started for each connection. The +// application ensures that there is at most one writer to a connection by +// executing all writes from this goroutine. +func (c *Client) writePump() { + ticker := time.NewTicker(pingPeriod) + defer func() { + ticker.Stop() + c.conn.Close() + }() + for { + select { + case message, ok := <-c.send: + c.conn.SetWriteDeadline(time.Now().Add(writeWait)) + if !ok { + // The hub closed the channel. + c.conn.WriteMessage(websocket.CloseMessage, []byte{}) + return + } + + w, err := c.conn.NextWriter(websocket.BinaryMessage) + if err != nil { + return + } + w.Write(message) + if err := w.Close(); err != nil { + return + } + case <-ticker.C: + c.conn.SetWriteDeadline(time.Now().Add(writeWait)) + if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil { + return + } + } + } +} + +func ConnectServer(addr *string) { interruptClose = false interrupt := make(chan os.Signal, 1) signal.Notify(interrupt, os.Interrupt) diff --git a/nat/hub.go b/nat/client_hub.go similarity index 100% rename from nat/hub.go rename to nat/client_hub.go diff --git a/nat/conn.go b/nat/conn.go index 20254b6..f568b71 100644 --- a/nat/conn.go +++ b/nat/conn.go @@ -32,58 +32,6 @@ var upgrader = websocket.Upgrader{ WriteBufferSize: 1024, } -// Client is a middleman between the websocket connection and the hub. -type Client struct { - hub *Hub - - // The websocket connection. - conn *websocket.Conn - - // Buffered channel of outbound messages. - send chan []byte - - User string - Subscribe SubscribeMessage -} - -// writePump pumps messages from the hub to the websocket connection. -// -// A goroutine running writePump is started for each connection. The -// application ensures that there is at most one writer to a connection by -// executing all writes from this goroutine. -func (c *Client) writePump() { - ticker := time.NewTicker(pingPeriod) - defer func() { - ticker.Stop() - c.conn.Close() - }() - for { - select { - case message, ok := <-c.send: - c.conn.SetWriteDeadline(time.Now().Add(writeWait)) - if !ok { - // The hub closed the channel. - c.conn.WriteMessage(websocket.CloseMessage, []byte{}) - return - } - - w, err := c.conn.NextWriter(websocket.BinaryMessage) - if err != nil { - return - } - w.Write(message) - if err := w.Close(); err != nil { - return - } - case <-ticker.C: - c.conn.SetWriteDeadline(time.Now().Add(writeWait)) - if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil { - return - } - } - } -} - // serveWs handles websocket requests from the peer. func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) diff --git a/nat/message.go b/nat/message.go index dcafb58..4ef1464 100644 --- a/nat/message.go +++ b/nat/message.go @@ -16,6 +16,7 @@ type AnswerMessage struct { } type Message struct { - User string - Body []byte + ID string + Method string + Body []byte } diff --git a/nat/server.go b/nat/server.go index 2de49ef..7c2367a 100644 --- a/nat/server.go +++ b/nat/server.go @@ -6,10 +6,14 @@ import ( ) var WsHub *Hub +var Bridges *BridgeHub func NewServer(addr *string) { WsHub = newHub() go WsHub.run() + Bridges = newBridgeHub() + go Bridges.run() + http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { serveWs(WsHub, w, r) }) diff --git a/proto/websocket.go b/proto/websocket.go index ac0a2ed..db1fdca 100644 --- a/proto/websocket.go +++ b/proto/websocket.go @@ -86,6 +86,9 @@ func (s *wsTunnel) transfer() { if config.DebugLevel >= config.LevelLong { log.Println(trace.ID(s.req.ID), "transfer start") } + + Bridges.Register(s.req.ID, s.req.conn) + var err error done := make(chan int, 1) From f6ebeb5d638a1930fab0bfcaf6711e75a8dd6a01 Mon Sep 17 00:00:00 2001 From: liminggui Date: Sun, 4 Apr 2021 15:02:01 +0800 Subject: [PATCH 06/32] =?UTF-8?q?=E4=B8=B4=E6=97=B6=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nat/bridge.go | 50 ++++++++++++++++++++- nat/bridge_hub.go | 35 +++++++-------- nat/client.go | 108 +++++++++++++++------------------------------ nat/client_hub.go | 23 +++------- nat/conn.go | 38 ---------------- nat/message.go | 2 +- nat/server.go | 45 +++++++++++++++++-- proto/websocket.go | 20 +++------ 8 files changed, 153 insertions(+), 168 deletions(-) diff --git a/nat/bridge.go b/nat/bridge.go index 07da429..9cc2a60 100644 --- a/nat/bridge.go +++ b/nat/bridge.go @@ -1,11 +1,59 @@ package nat -import "net" +import ( + "net" + "time" +) type Bridge struct { + hub *BridgeHub + reqID uint conn *net.TCPConn // Buffered channel of outbound messages. send chan []byte } + +// 向websocket hub写数据 +func (b *Bridge) Write(p []byte) (n int, err error) { + msg := &Message{ID: b.reqID, Body: p} + WsHub.broadcast <- msg + return len(p), nil +} + +// 通知websocket 创建连接 +func (b *Bridge) Open() { + msg := &Message{ID: b.reqID, Method: "create"} + WsHub.broadcast <- msg +} + +// 通知tcp关闭连接 +func (b *Bridge) CloseWrite() { + msg := &Message{ID: b.reqID, Method: "close"} + WsHub.broadcast <- msg +} + +// 从websocket hub读数据 +func (b *Bridge) ReadPump() { + ticker := time.NewTicker(pingPeriod) + defer func() { + ticker.Stop() + b.conn.Close() + }() + for { + select { + case message, ok := <-b.send: //ok为判断channel是否关闭 + if !ok { + return + } + + _, err := b.conn.Write(message) + if err != nil { + return + } + case <-ticker.C: + return + } + } +} diff --git a/nat/bridge_hub.go b/nat/bridge_hub.go index 6f15440..183ed6a 100644 --- a/nat/bridge_hub.go +++ b/nat/bridge_hub.go @@ -1,7 +1,6 @@ package nat import ( - "errors" "net" ) @@ -10,7 +9,7 @@ type BridgeHub struct { bridges map[*Bridge]bool // Inbound messages from the clients. - broadcast chan []byte + broadcast chan *Message // Register requests from the clients. register chan *Bridge @@ -21,7 +20,7 @@ type BridgeHub struct { func newBridgeHub() *BridgeHub { return &BridgeHub{ - broadcast: make(chan []byte), + broadcast: make(chan *Message), register: make(chan *Bridge), unregister: make(chan *Bridge), bridges: make(map[*Bridge]bool), @@ -40,8 +39,16 @@ func (h *BridgeHub) run() { } case message := <-h.broadcast: for bridge := range h.bridges { + if bridge.reqID != message.ID { + continue + } + if message.Method == "close" { + close(bridge.send) + delete(h.bridges, bridge) + return + } select { - case bridge.send <- message: + case bridge.send <- message.Body: default: close(bridge.send) delete(h.bridges, bridge) @@ -51,21 +58,11 @@ func (h *BridgeHub) run() { } } -func (h *BridgeHub) Write(p []byte) (n int, err error) { - h.broadcast <- p - return len(p), nil -} - -func (h *BridgeHub) Read(p []byte) (n int, err error) { - for bridge := range h.bridges { - n, err := bridge.conn.Read(p) - return n, err - } - return 0, errors.New("fail") -} - -func (h *BridgeHub) Register(ID uint, conn *net.TCPConn) { +func (h *BridgeHub) Register(ID uint, conn *net.TCPConn) *Bridge { b := &Bridge{reqID: ID, conn: conn, send: make(chan []byte)} h.register <- b - //go b.writePump() + + // 发送创建连接请求 + b.Open() + return b } diff --git a/nat/client.go b/nat/client.go index f460982..297315a 100644 --- a/nat/client.go +++ b/nat/client.go @@ -13,14 +13,11 @@ import ( "github.com/gorilla/websocket" "github.com/keminar/anyproxy/config" - "github.com/keminar/anyproxy/proto/tcp" + "github.com/keminar/anyproxy/utils/trace" ) var interruptClose bool -var proxyConn net.Conn -var proxyBool bool - // Client is a middleman between the websocket connection and the hub. type Client struct { hub *Hub @@ -29,17 +26,13 @@ type Client struct { conn *websocket.Conn // Buffered channel of outbound messages. - send chan []byte + send chan *Message User string Subscribe SubscribeMessage } -// writePump pumps messages from the hub to the websocket connection. -// -// A goroutine running writePump is started for each connection. The -// application ensures that there is at most one writer to a connection by -// executing all writes from this goroutine. +// write to pc client func (c *Client) writePump() { ticker := time.NewTicker(pingPeriod) defer func() { @@ -48,7 +41,7 @@ func (c *Client) writePump() { }() for { select { - case message, ok := <-c.send: + case message, ok := <-c.send: //ok为判断channel是否关闭 c.conn.SetWriteDeadline(time.Now().Add(writeWait)) if !ok { // The hub closed the channel. @@ -56,14 +49,7 @@ func (c *Client) writePump() { return } - w, err := c.conn.NextWriter(websocket.BinaryMessage) - if err != nil { - return - } - w.Write(message) - if err := w.Close(); err != nil { - return - } + c.conn.WriteJSON(message) case <-ticker.C: c.conn.SetWriteDeadline(time.Now().Add(writeWait)) if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil { @@ -73,12 +59,16 @@ func (c *Client) writePump() { } } +var LocalBridge *BridgeHub + func ConnectServer(addr *string) { interruptClose = false interrupt := make(chan os.Signal, 1) signal.Notify(interrupt, os.Interrupt) - proxyBool = false + LocalBridge = newBridgeHub() + go LocalBridge.run() + for { conn(addr, interrupt) if interruptClose { @@ -87,16 +77,16 @@ func ConnectServer(addr *string) { } } -func dial() { +func dialProxy() net.Conn { connTimeout := time.Duration(5) * time.Second var err error localProxy := fmt.Sprintf("%s:%d", "127.0.0.1", config.ListenPort) - proxyConn, err = net.DialTimeout("tcp", localProxy, connTimeout) + proxyConn, err := net.DialTimeout("tcp", localProxy, connTimeout) if err != nil { fmt.Println("dial local proxy", err) } log.Printf("websocket connecting to %s", localProxy) - proxyBool = true + return proxyConn } func copyBuffer(dst io.Writer, src io.Reader, srcname string) (written int64, err error) { @@ -108,10 +98,6 @@ func copyBuffer(dst io.Writer, src io.Reader, srcname string) (written int64, er i++ nr, er := src.Read(buf) if nr > 0 { - if srcname == "client" && string(buf[0:nr]) == "ok" { - fmt.Println("recv ok") - break - } fmt.Println("test", string(buf[0:nr])) nw, ew := dst.Write(buf[0:nr]) if nw > 0 { @@ -144,26 +130,6 @@ func newClientHandler(c *websocket.Conn) *ClientHandler { return &ClientHandler{c: c} } -func (h *ClientHandler) Read(p []byte) (n int, err error) { - _, message, err := h.c.ReadMessage() - n = copy(p, message) - return n, err -} - -func (h *ClientHandler) Write(p []byte) (n int, err error) { - h.c.SetWriteDeadline(time.Now().Add(writeWait)) - - w, err := h.c.NextWriter(websocket.BinaryMessage) - if err != nil { - return 0, err - } - w.Write(p) - if err := w.Close(); err != nil { - return 0, err - } - return len(p), nil -} - func (h *ClientHandler) Auth(user string, token string) error { msg := AuthMessage{User: user, Token: token} return h.ask(&msg) @@ -191,7 +157,10 @@ func (h *ClientHandler) ask(v interface{}) error { send <- message }() select { - case message := <-send: + case message, ok := <-send: //ok为判断channel是否关闭 + if !ok { + return errors.New("fail") + } if string(message) != "ok" { return errors.New("fail, " + string(message)) } @@ -233,33 +202,26 @@ func conn(addr *string, interrupt chan os.Signal) { go func() { defer close(done) for { - if proxyBool == false { - dial() + msg := &Message{} + err := w.c.ReadJSON(msg) + if err != nil { + break } - // 如果与服务器断开,需要重连 - reConn := false - done2 := make(chan int, 1) - go func() { - defer func() { - done2 <- 1 - close(done2) + if msg.Method == "create" { + proxConn := dialProxy() + b := LocalBridge.Register(msg.ID, proxConn.(*net.TCPConn)) + go b.ReadPump() + + // 从tcp返回数据到ws + go func() { + readSize, err := copyBuffer(b, proxConn, "server") + logCopyErr("local->websocket", err) + log.Println(trace.ID(msg.ID), "request body size", readSize) + // close + b.CloseWrite() }() - readSize, err := copyBuffer(proxyConn.(*net.TCPConn), w, "client") - log.Println("request body size", readSize) - logCopyErr("websocket->client", err) - proxyConn.(*net.TCPConn).CloseWrite() - proxyBool = false - if err != nil { - reConn = true - } - }() - writeSize, err := copyBuffer(w, tcp.NewReader(proxyConn.(*net.TCPConn)), "websocket") - log.Println("websocket transfer finished, response size", writeSize) - logCopyErr("client->websocket", err) - w.Write([]byte("ok")) - <-done2 - if reConn { - break + } else { + LocalBridge.broadcast <- msg } } }() diff --git a/nat/client_hub.go b/nat/client_hub.go index 458a442..916014f 100644 --- a/nat/client_hub.go +++ b/nat/client_hub.go @@ -1,7 +1,5 @@ package nat -import "errors" - // Hub maintains the set of active clients and broadcasts messages to the // clients. type Hub struct { @@ -9,7 +7,7 @@ type Hub struct { clients map[*Client]bool // Inbound messages from the clients. - broadcast chan []byte + broadcast chan *Message // Register requests from the clients. register chan *Client @@ -20,7 +18,7 @@ type Hub struct { func newHub() *Hub { return &Hub{ - broadcast: make(chan []byte), + broadcast: make(chan *Message), register: make(chan *Client), unregister: make(chan *Client), clients: make(map[*Client]bool), @@ -39,8 +37,11 @@ func (h *Hub) run() { } case message := <-h.broadcast: for client := range h.clients { + // todo check client subscribe select { case client.send <- message: + //发送给一个订阅者就要返回,不然变成多个并发请求了,而且接收数据也会出错。 + break default: close(client.send) delete(h.clients, client) @@ -49,17 +50,3 @@ func (h *Hub) run() { } } } - -func (h *Hub) Write(p []byte) (n int, err error) { - h.broadcast <- p - return len(p), nil -} - -func (h *Hub) Read(p []byte) (n int, err error) { - for client := range h.clients { - _, message, err := client.conn.ReadMessage() - n = copy(p, message) - return n, err - } - return 0, errors.New("fail") -} diff --git a/nat/conn.go b/nat/conn.go index f568b71..41b4280 100644 --- a/nat/conn.go +++ b/nat/conn.go @@ -1,8 +1,6 @@ package nat import ( - "log" - "net/http" "time" "github.com/gorilla/websocket" @@ -31,39 +29,3 @@ var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, } - -// serveWs handles websocket requests from the peer. -func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) { - conn, err := upgrader.Upgrade(w, r, nil) - if err != nil { - log.Println(err) - return - } - - var user AuthMessage - err = conn.ReadJSON(&user) - if err != nil { - log.Println(err) - return - } - if user.User == "111" && user.Token == "test" { - conn.WriteMessage(websocket.TextMessage, []byte("ok")) - } else { - conn.WriteMessage(websocket.TextMessage, []byte("token err")) - } - - var subscribe SubscribeMessage - err = conn.ReadJSON(&subscribe) - if err != nil { - log.Println(err) - return - } - conn.WriteMessage(websocket.TextMessage, []byte("ok")) - - client := &Client{hub: hub, conn: conn, send: make(chan []byte), User: user.User, Subscribe: subscribe} - client.hub.register <- client - - // Allow collection of memory referenced by the caller by doing all work in - // new goroutines. - go client.writePump() -} diff --git a/nat/message.go b/nat/message.go index 4ef1464..ce8477e 100644 --- a/nat/message.go +++ b/nat/message.go @@ -16,7 +16,7 @@ type AnswerMessage struct { } type Message struct { - ID string + ID uint Method string Body []byte } diff --git a/nat/server.go b/nat/server.go index 7c2367a..d91d678 100644 --- a/nat/server.go +++ b/nat/server.go @@ -3,16 +3,18 @@ package nat import ( "log" "net/http" + + "github.com/gorilla/websocket" ) var WsHub *Hub -var Bridges *BridgeHub +var ServerBridge *BridgeHub func NewServer(addr *string) { WsHub = newHub() go WsHub.run() - Bridges = newBridgeHub() - go Bridges.run() + ServerBridge = newBridgeHub() + go ServerBridge.run() http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { serveWs(WsHub, w, r) @@ -22,3 +24,40 @@ func NewServer(addr *string) { log.Fatal("ListenAndServe: ", err) } } + +// serveWs handles websocket requests from the peer. +func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Println(err) + return + } + + // 认证 + var user AuthMessage + err = conn.ReadJSON(&user) + if err != nil { + log.Println(err) + return + } + if user.User == "111" && user.Token == "test" { + conn.WriteMessage(websocket.TextMessage, []byte("ok")) + } else { + conn.WriteMessage(websocket.TextMessage, []byte("token err")) + } + + // 订阅 + var subscribe SubscribeMessage + err = conn.ReadJSON(&subscribe) + if err != nil { + log.Println(err) + return + } + conn.WriteMessage(websocket.TextMessage, []byte("ok")) + + // 注册连接 + client := &Client{hub: hub, conn: conn, send: make(chan *Message), User: user.User, Subscribe: subscribe} + client.hub.register <- client + + go client.writePump() +} diff --git a/proto/websocket.go b/proto/websocket.go index db1fdca..743cbe8 100644 --- a/proto/websocket.go +++ b/proto/websocket.go @@ -43,10 +43,6 @@ func (s *wsTunnel) copyBuffer(dst io.Writer, src io.Reader, srcname string) (wri } nr, er := src.Read(buf) if nr > 0 { - if srcname == "websocket" && string(buf[0:nr]) == "ok" { - fmt.Println("recv ok") - break - } if config.DebugLevel >= config.LevelDebugBody { log.Printf("%s receive from %s, n=%d, data len: %d\n", trace.ID(s.req.ID), srcname, i, nr) fmt.Println(trace.ID(s.req.ID), string(buf[0:nr])) @@ -68,12 +64,6 @@ func (s *wsTunnel) copyBuffer(dst io.Writer, src io.Reader, srcname string) (wri if er != io.EOF { err = er } - - if srcname == "client" { - fmt.Println("send ok") - // 当客户端断开或出错了,服务端也不用再读了,可以关闭,解决读Server卡住不能到EOF的问题 - dst.Write([]byte("ok")) - } break } @@ -87,7 +77,7 @@ func (s *wsTunnel) transfer() { log.Println(trace.ID(s.req.ID), "transfer start") } - Bridges.Register(s.req.ID, s.req.conn) + b := nat.ServerBridge.Register(s.req.ID, s.req.conn) var err error done := make(chan int, 1) @@ -98,14 +88,14 @@ func (s *wsTunnel) transfer() { done <- 1 close(done) }() - nat.WsHub.Write([]byte(s.buffer.String())) - s.readSize, err = s.copyBuffer(nat.WsHub, s.req.reader, "client") + b.Write([]byte(s.buffer.String())) + s.readSize, err = s.copyBuffer(b, s.req.reader, "client") + b.CloseWrite() s.logCopyErr("client->websocket", err) log.Println(trace.ID(s.req.ID), "request body size", s.readSize) }() //取返回结果 - s.writeSize, err = s.copyBuffer(s.req.conn, nat.WsHub, "websocket") - s.logCopyErr("websocket->client", err) + b.ReadPump() <-done // 不管是不是正常结束,只要server结束了,函数就会返回,然后底层会自动断开与client的连接 From afed48b5be21538d067b50417af771371e4fee7d Mon Sep 17 00:00:00 2001 From: liminggui Date: Mon, 5 Apr 2021 16:13:00 +0800 Subject: [PATCH 07/32] =?UTF-8?q?=E4=B8=B4=E6=97=B6=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nat/bridge.go | 65 +++++++++++++++++++++++++--- nat/bridge_hub.go | 20 ++++++--- nat/client.go | 105 +++++++++++++++++++++++++-------------------- nat/client_hub.go | 10 ++++- nat/conn.go | 68 ++++++++++++++++++++++++++--- nat/server.go | 63 --------------------------- proto/websocket.go | 58 +++++-------------------- 7 files changed, 213 insertions(+), 176 deletions(-) delete mode 100644 nat/server.go diff --git a/nat/bridge.go b/nat/bridge.go index 9cc2a60..2f21a6f 100644 --- a/nat/bridge.go +++ b/nat/bridge.go @@ -1,12 +1,19 @@ package nat import ( + "fmt" + "io" + "log" "net" "time" + + "github.com/keminar/anyproxy/config" + "github.com/keminar/anyproxy/utils/trace" ) type Bridge struct { - hub *BridgeHub + hub *BridgeHub + wsHub *Hub reqID uint conn *net.TCPConn @@ -18,36 +25,38 @@ type Bridge struct { // 向websocket hub写数据 func (b *Bridge) Write(p []byte) (n int, err error) { msg := &Message{ID: b.reqID, Body: p} - WsHub.broadcast <- msg + b.wsHub.broadcast <- msg return len(p), nil } // 通知websocket 创建连接 func (b *Bridge) Open() { msg := &Message{ID: b.reqID, Method: "create"} - WsHub.broadcast <- msg + b.wsHub.broadcast <- msg } // 通知tcp关闭连接 func (b *Bridge) CloseWrite() { msg := &Message{ID: b.reqID, Method: "close"} - WsHub.broadcast <- msg + b.wsHub.broadcast <- msg } // 从websocket hub读数据 -func (b *Bridge) ReadPump() { +func (b *Bridge) WritePump() { ticker := time.NewTicker(pingPeriod) defer func() { ticker.Stop() - b.conn.Close() + b.conn.CloseWrite() + log.Println("net_debug_close_proxy_write") }() for { select { case message, ok := <-b.send: //ok为判断channel是否关闭 if !ok { + log.Println("nat_debug_bridge_send_close") return } - + log.Println("nat_debug_write_proxy", string(message)) _, err := b.conn.Write(message) if err != nil { return @@ -57,3 +66,45 @@ func (b *Bridge) ReadPump() { } } } + +// CopyBuffer 传输数据 +func (b *Bridge) CopyBuffer(dst io.Writer, src io.Reader, srcname string) (written int64, err error) { + //如果设置过大会耗内存高,4k比较合理 + size := 4 * 1024 + buf := make([]byte, size) + i := 0 + for { + i++ + if config.DebugLevel >= config.LevelDebug { + log.Printf("%s bridge of %s proxy, n=%d\n", trace.ID(b.reqID), srcname, i) + } + nr, er := src.Read(buf) + if nr > 0 { + if config.DebugLevel >= config.LevelDebugBody { + log.Printf("%s bridge of %s proxy, n=%d, data len: %d\n", trace.ID(b.reqID), srcname, i, nr) + fmt.Println(trace.ID(b.reqID), string(buf[0:nr])) + } + nw, ew := dst.Write(buf[0:nr]) + if nw > 0 { + written += int64(nw) + } + if ew != nil { + err = ew + break + } + if nr != nw { + err = io.ErrShortWrite + break + } + } + if er != nil { + if er != io.EOF { + err = er + } + log.Println("nat_debug_read_error", er) + break + } + + } + return written, err +} diff --git a/nat/bridge_hub.go b/nat/bridge_hub.go index 183ed6a..be810f1 100644 --- a/nat/bridge_hub.go +++ b/nat/bridge_hub.go @@ -1,6 +1,7 @@ package nat import ( + "log" "net" ) @@ -31,14 +32,18 @@ func (h *BridgeHub) run() { for { select { case bridge := <-h.register: + log.Println("register read") h.bridges[bridge] = true case bridge := <-h.unregister: + log.Println("unregister read") if _, ok := h.bridges[bridge]; ok { delete(h.bridges, bridge) close(bridge.send) } case message := <-h.broadcast: + log.Println("bridge nums", len(h.bridges)) for bridge := range h.bridges { + log.Println("nat_debug_write_bridge_hub", bridge.reqID, message.ID, message.Method, string(message.Body)) if bridge.reqID != message.ID { continue } @@ -49,7 +54,7 @@ func (h *BridgeHub) run() { } select { case bridge.send <- message.Body: - default: + default: // 当send chan满时也会走进default close(bridge.send) delete(h.bridges, bridge) } @@ -58,11 +63,14 @@ func (h *BridgeHub) run() { } } -func (h *BridgeHub) Register(ID uint, conn *net.TCPConn) *Bridge { - b := &Bridge{reqID: ID, conn: conn, send: make(chan []byte)} +func (h *BridgeHub) Register(wsHub *Hub, ID uint, conn *net.TCPConn) *Bridge { + b := &Bridge{reqID: ID, conn: conn, send: make(chan []byte, 100), wsHub: wsHub} + log.Println("register") h.register <- b - - // 发送创建连接请求 - b.Open() + log.Println("register 2") return b } + +func (h *BridgeHub) Unregister(b *Bridge) { + h.unregister <- b +} diff --git a/nat/client.go b/nat/client.go index 297315a..dcf0c9c 100644 --- a/nat/client.go +++ b/nat/client.go @@ -1,6 +1,7 @@ package nat import ( + "encoding/json" "errors" "fmt" "io" @@ -44,12 +45,14 @@ func (c *Client) writePump() { case message, ok := <-c.send: //ok为判断channel是否关闭 c.conn.SetWriteDeadline(time.Now().Add(writeWait)) if !ok { + log.Println("nat client send chan close") // The hub closed the channel. c.conn.WriteMessage(websocket.CloseMessage, []byte{}) return } - c.conn.WriteJSON(message) + err := c.conn.WriteJSON(message) + log.Println("nat websocket writeJson", message.ID, message.Method, string(message.Body), err) case <-ticker.C: c.conn.SetWriteDeadline(time.Now().Add(writeWait)) if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil { @@ -59,6 +62,28 @@ func (c *Client) writePump() { } } +func (c *Client) readPump() { + defer func() { + c.hub.unregister <- c + c.conn.Close() + }() + c.conn.SetReadDeadline(time.Now().Add(pongWait)) + c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil }) + for { + msg := &Message{} + err := c.conn.ReadJSON(msg) + if err != nil { + if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { + log.Printf("error: %v", err) + } + break + } + log.Println("nat_debug_read_from_client", msg.ID, msg.Method, string(msg.Body)) + ServerBridge.broadcast <- msg + } +} + +var ClientHub *Hub var LocalBridge *BridgeHub func ConnectServer(addr *string) { @@ -66,11 +91,13 @@ func ConnectServer(addr *string) { interrupt := make(chan os.Signal, 1) signal.Notify(interrupt, os.Interrupt) + ClientHub = newHub() + go ClientHub.run() LocalBridge = newBridgeHub() go LocalBridge.run() for { - conn(addr, interrupt) + connect(addr, interrupt) if interruptClose { break } @@ -83,45 +110,12 @@ func dialProxy() net.Conn { localProxy := fmt.Sprintf("%s:%d", "127.0.0.1", config.ListenPort) proxyConn, err := net.DialTimeout("tcp", localProxy, connTimeout) if err != nil { - fmt.Println("dial local proxy", err) + log.Println("dial local proxy", err) } - log.Printf("websocket connecting to %s", localProxy) + log.Printf("local websocket connecting to %s", localProxy) return proxyConn } -func copyBuffer(dst io.Writer, src io.Reader, srcname string) (written int64, err error) { - //如果设置过大会耗内存高,4k比较合理 - size := 4 * 1024 - buf := make([]byte, size) - i := 0 - for { - i++ - nr, er := src.Read(buf) - if nr > 0 { - fmt.Println("test", string(buf[0:nr])) - nw, ew := dst.Write(buf[0:nr]) - if nw > 0 { - written += int64(nw) - } - if ew != nil { - err = ew - break - } - if nr != nw { - err = io.ErrShortWrite - break - } - } - if er != nil { - if er != io.EOF { - err = er - } - break - } - } - return written, err -} - type ClientHandler struct { c *websocket.Conn } @@ -170,7 +164,7 @@ func (h *ClientHandler) ask(v interface{}) error { return nil } -func conn(addr *string, interrupt chan os.Signal) { +func connect(addr *string, interrupt chan os.Signal) { u := url.URL{Scheme: "ws", Host: *addr, Path: "/ws"} log.Printf("connecting to %s", u.String()) @@ -198,25 +192,44 @@ func conn(addr *string, interrupt chan os.Signal) { } log.Println("websocket auth and subscribe ok") + client := &Client{hub: ClientHub, conn: c, send: make(chan *Message, 100), User: "111", Subscribe: SubscribeMessage{Key: "aa", Val: "bb"}} + client.hub.register <- client + defer func() { + client.hub.unregister <- client + log.Println("nat_debug_unregister_local_client") + }() + go client.writePump() done := make(chan struct{}) - go func() { + go func() { //client.readRump defer close(done) for { msg := &Message{} - err := w.c.ReadJSON(msg) + _, message, err := c.ReadMessage() if err != nil { - break + log.Println("nat_local_debug_read_error", err.Error()) + return } + + err = json.Unmarshal(message, &msg) + if err != nil { + log.Println("nat_local_debug_json_error", err.Error()) + return + } + log.Println("nat_local_read_from_websocket_message", msg.ID, msg.Method, string(msg.Body)) + if msg.Method == "create" { proxConn := dialProxy() - b := LocalBridge.Register(msg.ID, proxConn.(*net.TCPConn)) - go b.ReadPump() + b := LocalBridge.Register(ClientHub, msg.ID, proxConn.(*net.TCPConn)) + defer func() { + LocalBridge.Unregister(b) + }() + go b.WritePump() // 从tcp返回数据到ws go func() { - readSize, err := copyBuffer(b, proxConn, "server") - logCopyErr("local->websocket", err) - log.Println(trace.ID(msg.ID), "request body size", readSize) + readSize, err := b.CopyBuffer(b, proxConn, "local") + logCopyErr("nat_debug local->websocket", err) + log.Println(trace.ID(msg.ID), "nat debug request body size", readSize) // close b.CloseWrite() }() diff --git a/nat/client_hub.go b/nat/client_hub.go index 916014f..5df0c73 100644 --- a/nat/client_hub.go +++ b/nat/client_hub.go @@ -1,5 +1,7 @@ package nat +import "log" + // Hub maintains the set of active clients and broadcasts messages to the // clients. type Hub struct { @@ -36,13 +38,17 @@ func (h *Hub) run() { close(client.send) } case message := <-h.broadcast: + log.Println("clients nums", len(h.clients)) + Exit: for client := range h.clients { // todo check client subscribe + log.Println("nat_debug_write_client_hub", message.ID, message.Method, string(message.Body)) select { case client.send <- message: //发送给一个订阅者就要返回,不然变成多个并发请求了,而且接收数据也会出错。 - break - default: + break Exit + default: // 当send chan满时也会走进default + log.Println("why ?????") close(client.send) delete(h.clients, client) } diff --git a/nat/conn.go b/nat/conn.go index 41b4280..ba93edd 100644 --- a/nat/conn.go +++ b/nat/conn.go @@ -1,6 +1,9 @@ package nat import ( + "fmt" + "log" + "net/http" "time" "github.com/gorilla/websocket" @@ -15,9 +18,6 @@ const ( // Send pings to peer with this period. Must be less than pongWait. pingPeriod = (pongWait * 9) / 10 - - // Maximum message size allowed from peer. - maxMessageSize = 512 ) var ( @@ -26,6 +26,64 @@ var ( ) var upgrader = websocket.Upgrader{ - ReadBufferSize: 1024, - WriteBufferSize: 1024, + ReadBufferSize: 1024 * 1024 * 1024, + WriteBufferSize: 1024 * 1024 * 1024, +} + +var ServerHub *Hub +var ServerBridge *BridgeHub + +func NewServer(addr *string) { + ServerHub = newHub() + go ServerHub.run() + ServerBridge = newBridgeHub() + go ServerBridge.run() + + http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { + serveWs(ServerHub, w, r) + }) + + log.Println(fmt.Sprintf("Listening for websocket connections on %s", *addr)) + err := http.ListenAndServe(*addr, nil) + if err != nil { + log.Fatal("ListenAndServe: ", err) + } +} + +// serveWs handles websocket requests from the peer. +func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Println(err) + return + } + + // 认证 + var user AuthMessage + err = conn.ReadJSON(&user) + if err != nil { + log.Println(err) + return + } + if user.User == "111" && user.Token == "test" { + conn.WriteMessage(websocket.TextMessage, []byte("ok")) + } else { + conn.WriteMessage(websocket.TextMessage, []byte("token err")) + } + + // 订阅 + var subscribe SubscribeMessage + err = conn.ReadJSON(&subscribe) + if err != nil { + log.Println(err) + return + } + conn.WriteMessage(websocket.TextMessage, []byte("ok")) + + // 注册连接 + client := &Client{hub: hub, conn: conn, send: make(chan *Message, 100), User: user.User, Subscribe: subscribe} + client.hub.register <- client + + go client.writePump() + go client.readPump() } diff --git a/nat/server.go b/nat/server.go deleted file mode 100644 index d91d678..0000000 --- a/nat/server.go +++ /dev/null @@ -1,63 +0,0 @@ -package nat - -import ( - "log" - "net/http" - - "github.com/gorilla/websocket" -) - -var WsHub *Hub -var ServerBridge *BridgeHub - -func NewServer(addr *string) { - WsHub = newHub() - go WsHub.run() - ServerBridge = newBridgeHub() - go ServerBridge.run() - - http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { - serveWs(WsHub, w, r) - }) - err := http.ListenAndServe(*addr, nil) - if err != nil { - log.Fatal("ListenAndServe: ", err) - } -} - -// serveWs handles websocket requests from the peer. -func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) { - conn, err := upgrader.Upgrade(w, r, nil) - if err != nil { - log.Println(err) - return - } - - // 认证 - var user AuthMessage - err = conn.ReadJSON(&user) - if err != nil { - log.Println(err) - return - } - if user.User == "111" && user.Token == "test" { - conn.WriteMessage(websocket.TextMessage, []byte("ok")) - } else { - conn.WriteMessage(websocket.TextMessage, []byte("token err")) - } - - // 订阅 - var subscribe SubscribeMessage - err = conn.ReadJSON(&subscribe) - if err != nil { - log.Println(err) - return - } - conn.WriteMessage(websocket.TextMessage, []byte("ok")) - - // 注册连接 - client := &Client{hub: hub, conn: conn, send: make(chan *Message), User: user.User, Subscribe: subscribe} - client.hub.register <- client - - go client.writePump() -} diff --git a/proto/websocket.go b/proto/websocket.go index 743cbe8..1132204 100644 --- a/proto/websocket.go +++ b/proto/websocket.go @@ -2,7 +2,6 @@ package proto import ( "bytes" - "fmt" "io" "log" @@ -30,55 +29,19 @@ func newWsTunnel(req *Request) *wsTunnel { return s } -// copyBuffer 传输数据 -func (s *wsTunnel) copyBuffer(dst io.Writer, src io.Reader, srcname string) (written int64, err error) { - //如果设置过大会耗内存高,4k比较合理 - size := 4 * 1024 - buf := make([]byte, size) - i := 0 - for { - i++ - if config.DebugLevel >= config.LevelDebug { - log.Printf("%s receive from %s, n=%d\n", trace.ID(s.req.ID), srcname, i) - } - nr, er := src.Read(buf) - if nr > 0 { - if config.DebugLevel >= config.LevelDebugBody { - log.Printf("%s receive from %s, n=%d, data len: %d\n", trace.ID(s.req.ID), srcname, i, nr) - fmt.Println(trace.ID(s.req.ID), string(buf[0:nr])) - } - nw, ew := dst.Write(buf[0:nr]) - if nw > 0 { - written += int64(nw) - } - if ew != nil { - err = ew - break - } - if nr != nw { - err = io.ErrShortWrite - break - } - } - if er != nil { - if er != io.EOF { - err = er - } - break - } - - } - return written, err -} - // transfer 交换数据 func (s *wsTunnel) transfer() { if config.DebugLevel >= config.LevelLong { - log.Println(trace.ID(s.req.ID), "transfer start") + log.Println(trace.ID(s.req.ID), "websocket transfer start") } - b := nat.ServerBridge.Register(s.req.ID, s.req.conn) + b := nat.ServerBridge.Register(nat.ServerHub, s.req.ID, s.req.conn) + defer func() { + nat.ServerBridge.Unregister(b) + }() + // 发送创建连接请求 + b.Open() var err error done := make(chan int, 1) @@ -89,13 +52,14 @@ func (s *wsTunnel) transfer() { close(done) }() b.Write([]byte(s.buffer.String())) - s.readSize, err = s.copyBuffer(b, s.req.reader, "client") - b.CloseWrite() + s.readSize, err = b.CopyBuffer(b, s.req.reader, "client") s.logCopyErr("client->websocket", err) log.Println(trace.ID(s.req.ID), "request body size", s.readSize) + log.Println("debug send close ?") + b.CloseWrite() }() //取返回结果 - b.ReadPump() + b.WritePump() <-done // 不管是不是正常结束,只要server结束了,函数就会返回,然后底层会自动断开与client的连接 From 69b546850c5eb1a54127bb057914e5672f2073c1 Mon Sep 17 00:00:00 2001 From: liminggui Date: Mon, 5 Apr 2021 22:25:57 +0800 Subject: [PATCH 08/32] =?UTF-8?q?=E5=8F=AF=E4=BB=A5=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E5=95=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nat/bridge.go | 6 ++++-- nat/bridge_hub.go | 13 +++++++------ nat/client.go | 8 ++++++-- nat/client_hub.go | 10 ++++++++-- proto/websocket.go | 1 - 5 files changed, 25 insertions(+), 13 deletions(-) diff --git a/nat/bridge.go b/nat/bridge.go index 2f21a6f..6982b2a 100644 --- a/nat/bridge.go +++ b/nat/bridge.go @@ -56,7 +56,9 @@ func (b *Bridge) WritePump() { log.Println("nat_debug_bridge_send_close") return } - log.Println("nat_debug_write_proxy", string(message)) + if config.DebugLevel >= config.LevelDebugBody { + log.Println("nat_debug_write_proxy", string(message)) + } _, err := b.conn.Write(message) if err != nil { return @@ -101,7 +103,7 @@ func (b *Bridge) CopyBuffer(dst io.Writer, src io.Reader, srcname string) (writt if er != io.EOF { err = er } - log.Println("nat_debug_read_error", er) + log.Println("nat_debug_read_error", srcname, er) break } diff --git a/nat/bridge_hub.go b/nat/bridge_hub.go index be810f1..aa55058 100644 --- a/nat/bridge_hub.go +++ b/nat/bridge_hub.go @@ -3,6 +3,8 @@ package nat import ( "log" "net" + + "github.com/keminar/anyproxy/config" ) type BridgeHub struct { @@ -32,25 +34,26 @@ func (h *BridgeHub) run() { for { select { case bridge := <-h.register: - log.Println("register read") h.bridges[bridge] = true case bridge := <-h.unregister: - log.Println("unregister read") if _, ok := h.bridges[bridge]; ok { delete(h.bridges, bridge) close(bridge.send) } case message := <-h.broadcast: log.Println("bridge nums", len(h.bridges)) + Exit: for bridge := range h.bridges { - log.Println("nat_debug_write_bridge_hub", bridge.reqID, message.ID, message.Method, string(message.Body)) + if config.DebugLevel >= config.LevelDebugBody { + log.Println("nat_debug_write_bridge_hub", bridge.reqID, message.ID, message.Method, string(message.Body)) + } if bridge.reqID != message.ID { continue } if message.Method == "close" { close(bridge.send) delete(h.bridges, bridge) - return + break Exit } select { case bridge.send <- message.Body: @@ -65,9 +68,7 @@ func (h *BridgeHub) run() { func (h *BridgeHub) Register(wsHub *Hub, ID uint, conn *net.TCPConn) *Bridge { b := &Bridge{reqID: ID, conn: conn, send: make(chan []byte, 100), wsHub: wsHub} - log.Println("register") h.register <- b - log.Println("register 2") return b } diff --git a/nat/client.go b/nat/client.go index dcf0c9c..e7e65c7 100644 --- a/nat/client.go +++ b/nat/client.go @@ -52,7 +52,9 @@ func (c *Client) writePump() { } err := c.conn.WriteJSON(message) - log.Println("nat websocket writeJson", message.ID, message.Method, string(message.Body), err) + if config.DebugLevel >= config.LevelDebugBody { + log.Println("nat websocket writeJson", message.ID, message.Method, string(message.Body), err) + } case <-ticker.C: c.conn.SetWriteDeadline(time.Now().Add(writeWait)) if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil { @@ -78,7 +80,9 @@ func (c *Client) readPump() { } break } - log.Println("nat_debug_read_from_client", msg.ID, msg.Method, string(msg.Body)) + if config.DebugLevel >= config.LevelDebugBody { + log.Println("nat_debug_read_from_client", msg.ID, msg.Method, string(msg.Body)) + } ServerBridge.broadcast <- msg } } diff --git a/nat/client_hub.go b/nat/client_hub.go index 5df0c73..62cc43f 100644 --- a/nat/client_hub.go +++ b/nat/client_hub.go @@ -1,6 +1,10 @@ package nat -import "log" +import ( + "log" + + "github.com/keminar/anyproxy/config" +) // Hub maintains the set of active clients and broadcasts messages to the // clients. @@ -42,7 +46,9 @@ func (h *Hub) run() { Exit: for client := range h.clients { // todo check client subscribe - log.Println("nat_debug_write_client_hub", message.ID, message.Method, string(message.Body)) + if config.DebugLevel >= config.LevelDebugBody { + log.Println("nat_debug_write_client_hub", message.ID, message.Method, string(message.Body)) + } select { case client.send <- message: //发送给一个订阅者就要返回,不然变成多个并发请求了,而且接收数据也会出错。 diff --git a/proto/websocket.go b/proto/websocket.go index 1132204..95b3d5d 100644 --- a/proto/websocket.go +++ b/proto/websocket.go @@ -55,7 +55,6 @@ func (s *wsTunnel) transfer() { s.readSize, err = b.CopyBuffer(b, s.req.reader, "client") s.logCopyErr("client->websocket", err) log.Println(trace.ID(s.req.ID), "request body size", s.readSize) - log.Println("debug send close ?") b.CloseWrite() }() //取返回结果 From 8d78ea6fe96cc10fd3b67be85789a3cbb01af6de Mon Sep 17 00:00:00 2001 From: liminggui Date: Tue, 6 Apr 2021 00:13:56 +0800 Subject: [PATCH 09/32] =?UTF-8?q?=E7=AE=80=E5=8D=95=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nat/bridge.go | 40 +++++---- nat/bridge_hub.go | 16 ++-- nat/client.go | 211 ++----------------------------------------- nat/client_hub.go | 10 ++- nat/handler.go | 217 +++++++++++++++++++++++++++++++++++++++++++++ nat/message.go | 10 +++ proto/websocket.go | 13 +-- 7 files changed, 283 insertions(+), 234 deletions(-) create mode 100644 nat/handler.go diff --git a/nat/bridge.go b/nat/bridge.go index 6982b2a..5f0c912 100644 --- a/nat/bridge.go +++ b/nat/bridge.go @@ -5,23 +5,27 @@ import ( "io" "log" "net" - "time" "github.com/keminar/anyproxy/config" "github.com/keminar/anyproxy/utils/trace" ) type Bridge struct { - hub *BridgeHub - wsHub *Hub + bridgeHub *BridgeHub + wsHub *Hub - reqID uint + reqID uint //请求id conn *net.TCPConn // Buffered channel of outbound messages. send chan []byte } +// 包外面调用取消注册 +func (h *Bridge) Unregister(b *Bridge) { + h.bridgeHub.unregister <- b +} + // 向websocket hub写数据 func (b *Bridge) Write(p []byte) (n int, err error) { msg := &Message{ID: b.reqID, Body: p} @@ -31,40 +35,42 @@ func (b *Bridge) Write(p []byte) (n int, err error) { // 通知websocket 创建连接 func (b *Bridge) Open() { - msg := &Message{ID: b.reqID, Method: "create"} + msg := &Message{ID: b.reqID, Method: METHOD_CREATE} b.wsHub.broadcast <- msg } // 通知tcp关闭连接 func (b *Bridge) CloseWrite() { - msg := &Message{ID: b.reqID, Method: "close"} + msg := &Message{ID: b.reqID, Method: METHOD_CLOSE} b.wsHub.broadcast <- msg } -// 从websocket hub读数据 -func (b *Bridge) WritePump() { - ticker := time.NewTicker(pingPeriod) +// 从websocket hub读数据写到请求http端 +func (b *Bridge) WritePump() (written int64, err error) { defer func() { - ticker.Stop() b.conn.CloseWrite() - log.Println("net_debug_close_proxy_write") + if config.DebugLevel >= config.LevelDebug { + log.Println("net_debug_write_proxy_close") + } }() for { select { case message, ok := <-b.send: //ok为判断channel是否关闭 if !ok { - log.Println("nat_debug_bridge_send_close") + if config.DebugLevel >= config.LevelDebug { + log.Println("nat_debug_bridge_send_chan_closed") + } return } if config.DebugLevel >= config.LevelDebugBody { log.Println("nat_debug_write_proxy", string(message)) } - _, err := b.conn.Write(message) + var nw int + nw, err = b.conn.Write(message) if err != nil { return } - case <-ticker.C: - return + written += int64(nw) } } } @@ -103,7 +109,9 @@ func (b *Bridge) CopyBuffer(dst io.Writer, src io.Reader, srcname string) (writt if er != io.EOF { err = er } - log.Println("nat_debug_read_error", srcname, er) + if config.DebugLevel >= config.LevelDebug { + log.Println("nat_debug_read_error", srcname, er) + } break } diff --git a/nat/bridge_hub.go b/nat/bridge_hub.go index aa55058..15af85c 100644 --- a/nat/bridge_hub.go +++ b/nat/bridge_hub.go @@ -41,7 +41,9 @@ func (h *BridgeHub) run() { close(bridge.send) } case message := <-h.broadcast: - log.Println("bridge nums", len(h.bridges)) + if config.DebugLevel >= config.LevelDebug { + log.Println("bridge nums", len(h.bridges)) + } Exit: for bridge := range h.bridges { if config.DebugLevel >= config.LevelDebugBody { @@ -50,14 +52,18 @@ func (h *BridgeHub) run() { if bridge.reqID != message.ID { continue } - if message.Method == "close" { + if message.Method == METHOD_CLOSE { close(bridge.send) delete(h.bridges, bridge) break Exit } select { case bridge.send <- message.Body: + break Exit default: // 当send chan满时也会走进default + if config.DebugLevel >= config.LevelDebug { + log.Println("why go here ?????") + } close(bridge.send) delete(h.bridges, bridge) } @@ -67,11 +73,7 @@ func (h *BridgeHub) run() { } func (h *BridgeHub) Register(wsHub *Hub, ID uint, conn *net.TCPConn) *Bridge { - b := &Bridge{reqID: ID, conn: conn, send: make(chan []byte, 100), wsHub: wsHub} + b := &Bridge{bridgeHub: h, reqID: ID, conn: conn, send: make(chan []byte, 100), wsHub: wsHub} h.register <- b return b } - -func (h *BridgeHub) Unregister(b *Bridge) { - h.unregister <- b -} diff --git a/nat/client.go b/nat/client.go index e7e65c7..a7f6ac9 100644 --- a/nat/client.go +++ b/nat/client.go @@ -1,20 +1,11 @@ package nat import ( - "encoding/json" - "errors" - "fmt" - "io" "log" - "net" - "net/url" - "os" - "os/signal" "time" "github.com/gorilla/websocket" "github.com/keminar/anyproxy/config" - "github.com/keminar/anyproxy/utils/trace" ) var interruptClose bool @@ -29,11 +20,14 @@ type Client struct { // Buffered channel of outbound messages. send chan *Message - User string + // 用户 + User string + + // 订阅特征 Subscribe SubscribeMessage } -// write to pc client +// 写数据到websocket的对端 func (c *Client) writePump() { ticker := time.NewTicker(pingPeriod) defer func() { @@ -55,6 +49,9 @@ func (c *Client) writePump() { if config.DebugLevel >= config.LevelDebugBody { log.Println("nat websocket writeJson", message.ID, message.Method, string(message.Body), err) } + if err != nil { + return + } case <-ticker.C: c.conn.SetWriteDeadline(time.Now().Add(writeWait)) if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil { @@ -64,6 +61,7 @@ func (c *Client) writePump() { } } +// 从websocket的客户端读取数据 func (c *Client) readPump() { defer func() { c.hub.unregister <- c @@ -86,194 +84,3 @@ func (c *Client) readPump() { ServerBridge.broadcast <- msg } } - -var ClientHub *Hub -var LocalBridge *BridgeHub - -func ConnectServer(addr *string) { - interruptClose = false - interrupt := make(chan os.Signal, 1) - signal.Notify(interrupt, os.Interrupt) - - ClientHub = newHub() - go ClientHub.run() - LocalBridge = newBridgeHub() - go LocalBridge.run() - - for { - connect(addr, interrupt) - if interruptClose { - break - } - } -} - -func dialProxy() net.Conn { - connTimeout := time.Duration(5) * time.Second - var err error - localProxy := fmt.Sprintf("%s:%d", "127.0.0.1", config.ListenPort) - proxyConn, err := net.DialTimeout("tcp", localProxy, connTimeout) - if err != nil { - log.Println("dial local proxy", err) - } - log.Printf("local websocket connecting to %s", localProxy) - return proxyConn -} - -type ClientHandler struct { - c *websocket.Conn -} - -func newClientHandler(c *websocket.Conn) *ClientHandler { - return &ClientHandler{c: c} -} - -func (h *ClientHandler) Auth(user string, token string) error { - msg := AuthMessage{User: user, Token: token} - return h.ask(&msg) -} - -func (h *ClientHandler) Subscribe(key string, val string) error { - msg := SubscribeMessage{Key: key, Val: val} - return h.ask(&msg) -} - -func (h *ClientHandler) ask(v interface{}) error { - err := h.c.WriteJSON(v) - if err != nil { - return err - } - ticker := time.NewTicker(3 * time.Second) - defer func() { - ticker.Stop() - }() - - send := make(chan []byte) - go func() { - defer close(send) - _, message, _ := h.c.ReadMessage() - send <- message - }() - select { - case message, ok := <-send: //ok为判断channel是否关闭 - if !ok { - return errors.New("fail") - } - if string(message) != "ok" { - return errors.New("fail, " + string(message)) - } - case <-ticker.C: - return errors.New("timeout") - } - return nil -} - -func connect(addr *string, interrupt chan os.Signal) { - - u := url.URL{Scheme: "ws", Host: *addr, Path: "/ws"} - log.Printf("connecting to %s", u.String()) - - c, _, err := websocket.DefaultDialer.Dial(u.String(), nil) - if err != nil { - log.Println("dial:", err) - time.Sleep(time.Duration(3) * time.Second) - return - } - defer c.Close() - - w := newClientHandler(c) - err = w.Auth("111", "test") - if err != nil { - log.Println("auth:", err) - time.Sleep(time.Duration(3) * time.Second) - return - } - err = w.Subscribe("aa", "bb") - if err != nil { - log.Println("subscribe:", err) - time.Sleep(time.Duration(3) * time.Second) - return - } - log.Println("websocket auth and subscribe ok") - - client := &Client{hub: ClientHub, conn: c, send: make(chan *Message, 100), User: "111", Subscribe: SubscribeMessage{Key: "aa", Val: "bb"}} - client.hub.register <- client - defer func() { - client.hub.unregister <- client - log.Println("nat_debug_unregister_local_client") - }() - go client.writePump() - done := make(chan struct{}) - go func() { //client.readRump - defer close(done) - for { - msg := &Message{} - _, message, err := c.ReadMessage() - if err != nil { - log.Println("nat_local_debug_read_error", err.Error()) - return - } - - err = json.Unmarshal(message, &msg) - if err != nil { - log.Println("nat_local_debug_json_error", err.Error()) - return - } - log.Println("nat_local_read_from_websocket_message", msg.ID, msg.Method, string(msg.Body)) - - if msg.Method == "create" { - proxConn := dialProxy() - b := LocalBridge.Register(ClientHub, msg.ID, proxConn.(*net.TCPConn)) - defer func() { - LocalBridge.Unregister(b) - }() - go b.WritePump() - - // 从tcp返回数据到ws - go func() { - readSize, err := b.CopyBuffer(b, proxConn, "local") - logCopyErr("nat_debug local->websocket", err) - log.Println(trace.ID(msg.ID), "nat debug request body size", readSize) - // close - b.CloseWrite() - }() - } else { - LocalBridge.broadcast <- msg - } - } - }() - - for { - select { - case <-done: - return - case <-interrupt: - log.Println("interrupt") - interruptClose = true - - // Cleanly close the connection by sending a close message and then - // waiting (with timeout) for the server to close the connection. - err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) - if err != nil { - log.Println("write close:", err) - return - } - select { - case <-done: - case <-time.After(time.Second): - } - return - } - } -} - -func logCopyErr(name string, err error) { - if err == nil { - return - } - if config.DebugLevel >= config.LevelLong { - log.Println(name, err.Error()) - } else if err != io.EOF { - log.Println(name, err.Error()) - } -} diff --git a/nat/client_hub.go b/nat/client_hub.go index 62cc43f..484c710 100644 --- a/nat/client_hub.go +++ b/nat/client_hub.go @@ -42,19 +42,23 @@ func (h *Hub) run() { close(client.send) } case message := <-h.broadcast: - log.Println("clients nums", len(h.clients)) + if config.DebugLevel >= config.LevelDebug { + log.Println("clients nums", len(h.clients)) + } Exit: for client := range h.clients { - // todo check client subscribe if config.DebugLevel >= config.LevelDebugBody { log.Println("nat_debug_write_client_hub", message.ID, message.Method, string(message.Body)) } + // todo 检查 client subscribe 来判断发送给谁 select { case client.send <- message: //发送给一个订阅者就要返回,不然变成多个并发请求了,而且接收数据也会出错。 break Exit default: // 当send chan满时也会走进default - log.Println("why ?????") + if config.DebugLevel >= config.LevelDebug { + log.Println("why go here ?????") + } close(client.send) delete(h.clients, client) } diff --git a/nat/handler.go b/nat/handler.go new file mode 100644 index 0000000..1d9ebd5 --- /dev/null +++ b/nat/handler.go @@ -0,0 +1,217 @@ +package nat + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "log" + "net" + "net/url" + "os" + "os/signal" + "time" + + "github.com/gorilla/websocket" + "github.com/keminar/anyproxy/config" + "github.com/keminar/anyproxy/utils/trace" +) + +var ClientHub *Hub +var LocalBridge *BridgeHub + +// 连接到websocket服务 +func ConnectServer(addr *string) { + interruptClose = false + interrupt := make(chan os.Signal, 1) + signal.Notify(interrupt, os.Interrupt) + + ClientHub = newHub() + go ClientHub.run() + LocalBridge = newBridgeHub() + go LocalBridge.run() + + for { + connect(addr, interrupt) + if interruptClose { + break + } + } +} + +// 连接本地Proxy服务 +func dialProxy() net.Conn { + connTimeout := time.Duration(5) * time.Second + var err error + localProxy := fmt.Sprintf("%s:%d", "127.0.0.1", config.ListenPort) + proxyConn, err := net.DialTimeout("tcp", localProxy, connTimeout) + if err != nil { + log.Println("dial local proxy", err) + } + log.Printf("local websocket connecting to %s", localProxy) + return proxyConn +} + +// 认证连接并交换数据 +func connect(addr *string, interrupt chan os.Signal) { + u := url.URL{Scheme: "ws", Host: *addr, Path: "/ws"} + log.Printf("connecting to %s", u.String()) + + c, _, err := websocket.DefaultDialer.Dial(u.String(), nil) + if err != nil { + log.Println("dial:", err) + time.Sleep(time.Duration(3) * time.Second) + return + } + defer c.Close() + + w := newClientHandler(c) + err = w.Auth("111", "test") + if err != nil { + log.Println("auth:", err) + time.Sleep(time.Duration(3) * time.Second) + return + } + err = w.Subscribe("aa", "bb") + if err != nil { + log.Println("subscribe:", err) + time.Sleep(time.Duration(3) * time.Second) + return + } + log.Println("websocket auth and subscribe ok") + + client := &Client{hub: ClientHub, conn: c, send: make(chan *Message, 100), User: "111", Subscribe: SubscribeMessage{Key: "aa", Val: "bb"}} + client.hub.register <- client + defer func() { + client.hub.unregister <- client + }() + + go client.writePump() + done := make(chan struct{}) + go func() { //客户端的client.readRump + defer close(done) + for { + // 使用普通形式读,可以读到类似连接已关闭等的错误 + _, message, err := c.ReadMessage() + if err != nil { + log.Println("nat_local_debug_read_error", err.Error()) + return + } + + msg := &Message{} + err = json.Unmarshal(message, &msg) + if err != nil { + log.Println("nat_local_debug_json_error", err.Error()) + return + } + if config.DebugLevel >= config.LevelDebugBody { + log.Println("nat_local_read_from_websocket_message", msg.ID, msg.Method, string(msg.Body)) + } + + if msg.Method == METHOD_CREATE { + proxConn := dialProxy() + b := LocalBridge.Register(ClientHub, msg.ID, proxConn.(*net.TCPConn)) + go func() { + written, err := b.WritePump() + logCopyErr("nat_local_debug websocket->local", err) + log.Println(trace.ID(msg.ID), "nat debug response size", written) + }() + + // 从tcp返回数据到ws + go func() { + defer func() { + b.bridgeHub.unregister <- b + }() + readSize, err := b.CopyBuffer(b, proxConn, "local") + logCopyErr("nat_local_debug local->websocket", err) + log.Println(trace.ID(msg.ID), "nat debug request body size", readSize) + b.CloseWrite() + }() + } else { + LocalBridge.broadcast <- msg + } + } + }() + + for { + select { + case <-done: + return + case <-interrupt: + log.Println("interrupt") + interruptClose = true + + // Cleanly close the connection by sending a close message and then + // waiting (with timeout) for the server to close the connection. + err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) + if err != nil { + log.Println("write close:", err) + return + } + select { + case <-done: + case <-time.After(time.Second): + } + return + } + } +} + +type ClientHandler struct { + c *websocket.Conn +} + +func newClientHandler(c *websocket.Conn) *ClientHandler { + return &ClientHandler{c: c} +} + +func (h *ClientHandler) Auth(user string, token string) error { + msg := AuthMessage{User: user, Token: token} + return h.ask(&msg) +} + +func (h *ClientHandler) Subscribe(key string, val string) error { + msg := SubscribeMessage{Key: key, Val: val} + return h.ask(&msg) +} + +func (h *ClientHandler) ask(v interface{}) error { + err := h.c.WriteJSON(v) + if err != nil { + return err + } + ticker := time.NewTicker(3 * time.Second) + defer func() { + ticker.Stop() + }() + + send := make(chan []byte) + go func() { + defer close(send) + _, message, _ := h.c.ReadMessage() + send <- message + }() + select { + case message, ok := <-send: //ok为判断channel是否关闭 + if !ok { + return errors.New("fail") + } + if string(message) != "ok" { + return errors.New("fail, " + string(message)) + } + case <-ticker.C: + return errors.New("timeout") + } + return nil +} + +func logCopyErr(name string, err error) { + if err == nil { + return + } + if config.DebugLevel >= config.LevelLong { + log.Println(name, err.Error()) + } else if err != io.EOF { + log.Println(name, err.Error()) + } +} diff --git a/nat/message.go b/nat/message.go index ce8477e..aec84bc 100644 --- a/nat/message.go +++ b/nat/message.go @@ -1,20 +1,30 @@ package nat +// 创建连接命令 +const METHOD_CREATE = "create" + +// 关闭连接命令 +const METHOD_CLOSE = "close" + +// 认证 type AuthMessage struct { User string Token string } +// 订阅 type SubscribeMessage struct { Key string Val string } +// 回答 type AnswerMessage struct { State int Msg string } +// 普通消息体 type Message struct { ID uint Method string diff --git a/proto/websocket.go b/proto/websocket.go index 95b3d5d..651280a 100644 --- a/proto/websocket.go +++ b/proto/websocket.go @@ -37,7 +37,7 @@ func (s *wsTunnel) transfer() { b := nat.ServerBridge.Register(nat.ServerHub, s.req.ID, s.req.conn) defer func() { - nat.ServerBridge.Unregister(b) + b.Unregister(b) }() // 发送创建连接请求 @@ -45,20 +45,21 @@ func (s *wsTunnel) transfer() { var err error done := make(chan int, 1) - //发送请求 + //发送请求给websocket go func() { defer func() { done <- 1 close(done) }() b.Write([]byte(s.buffer.String())) - s.readSize, err = b.CopyBuffer(b, s.req.reader, "client") - s.logCopyErr("client->websocket", err) + s.readSize, err = b.CopyBuffer(b, s.req.reader, "request") + s.logCopyErr("request->websocket", err) log.Println(trace.ID(s.req.ID), "request body size", s.readSize) b.CloseWrite() }() - //取返回结果 - b.WritePump() + //取返回结果写入请求端 + s.writeSize, err = b.WritePump() + s.logCopyErr("websocket->request", err) <-done // 不管是不是正常结束,只要server结束了,函数就会返回,然后底层会自动断开与client的连接 From c65e3d5c2a21e3bc6cd4394591c16af00ed3aeb3 Mon Sep 17 00:00:00 2001 From: liminggui Date: Tue, 6 Apr 2021 11:19:03 +0800 Subject: [PATCH 10/32] =?UTF-8?q?=E8=AE=A4=E8=AF=81=E6=94=B9=E6=88=90?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- conf/router.yaml | 6 + nat/client.go | 2 +- nat/conn.go | 25 ++- nat/handler.go | 447 ++++++++++++++++++++++-------------------- nat/message.go | 1 + utils/conf/router.go | 34 +++- utils/tools/string.go | 12 ++ 7 files changed, 295 insertions(+), 232 deletions(-) diff --git a/conf/router.yaml b/conf/router.yaml index 754390a..f366474 100644 --- a/conf/router.yaml +++ b/conf/router.yaml @@ -40,3 +40,9 @@ hosts: #token: anyproxyproxyany # 可访问的客户端IP,为空不限制 #allowIP: +websocket: + user: 111 + pass: 111 + subscribe: + - key: devId + val: xxx \ No newline at end of file diff --git a/nat/client.go b/nat/client.go index a7f6ac9..251ef5f 100644 --- a/nat/client.go +++ b/nat/client.go @@ -24,7 +24,7 @@ type Client struct { User string // 订阅特征 - Subscribe SubscribeMessage + Subscribe []SubscribeMessage } // 写数据到websocket的对端 diff --git a/nat/conn.go b/nat/conn.go index ba93edd..456dd5d 100644 --- a/nat/conn.go +++ b/nat/conn.go @@ -7,6 +7,8 @@ import ( "time" "github.com/gorilla/websocket" + "github.com/keminar/anyproxy/utils/conf" + "github.com/keminar/anyproxy/utils/tools" ) const ( @@ -65,19 +67,34 @@ func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) { log.Println(err) return } - if user.User == "111" && user.Token == "test" { - conn.WriteMessage(websocket.TextMessage, []byte("ok")) - } else { + xtime := time.Now().Unix() + if xtime-user.Xtime > 60 { + conn.WriteMessage(websocket.TextMessage, []byte("xtime err")) + return + } + if user.User != conf.RouterConfig.Websocket.User { + conn.WriteMessage(websocket.TextMessage, []byte("user err")) + return + } + + token, err := tools.Md5Str(fmt.Sprintf("%s|%s|%d", user.User, conf.RouterConfig.Websocket.Pass, user.Xtime)) + if err != nil || user.Token != token { conn.WriteMessage(websocket.TextMessage, []byte("token err")) + return } + conn.WriteMessage(websocket.TextMessage, []byte("ok")) // 订阅 - var subscribe SubscribeMessage + var subscribe []SubscribeMessage err = conn.ReadJSON(&subscribe) if err != nil { log.Println(err) return } + if len(subscribe) == 0 { + conn.WriteMessage(websocket.TextMessage, []byte("subscribe empty err")) + return + } conn.WriteMessage(websocket.TextMessage, []byte("ok")) // 注册连接 diff --git a/nat/handler.go b/nat/handler.go index 1d9ebd5..55f1288 100644 --- a/nat/handler.go +++ b/nat/handler.go @@ -1,217 +1,230 @@ -package nat - -import ( - "encoding/json" - "errors" - "fmt" - "io" - "log" - "net" - "net/url" - "os" - "os/signal" - "time" - - "github.com/gorilla/websocket" - "github.com/keminar/anyproxy/config" - "github.com/keminar/anyproxy/utils/trace" -) - -var ClientHub *Hub -var LocalBridge *BridgeHub - -// 连接到websocket服务 -func ConnectServer(addr *string) { - interruptClose = false - interrupt := make(chan os.Signal, 1) - signal.Notify(interrupt, os.Interrupt) - - ClientHub = newHub() - go ClientHub.run() - LocalBridge = newBridgeHub() - go LocalBridge.run() - - for { - connect(addr, interrupt) - if interruptClose { - break - } - } -} - -// 连接本地Proxy服务 -func dialProxy() net.Conn { - connTimeout := time.Duration(5) * time.Second - var err error - localProxy := fmt.Sprintf("%s:%d", "127.0.0.1", config.ListenPort) - proxyConn, err := net.DialTimeout("tcp", localProxy, connTimeout) - if err != nil { - log.Println("dial local proxy", err) - } - log.Printf("local websocket connecting to %s", localProxy) - return proxyConn -} - -// 认证连接并交换数据 -func connect(addr *string, interrupt chan os.Signal) { - u := url.URL{Scheme: "ws", Host: *addr, Path: "/ws"} - log.Printf("connecting to %s", u.String()) - - c, _, err := websocket.DefaultDialer.Dial(u.String(), nil) - if err != nil { - log.Println("dial:", err) - time.Sleep(time.Duration(3) * time.Second) - return - } - defer c.Close() - - w := newClientHandler(c) - err = w.Auth("111", "test") - if err != nil { - log.Println("auth:", err) - time.Sleep(time.Duration(3) * time.Second) - return - } - err = w.Subscribe("aa", "bb") - if err != nil { - log.Println("subscribe:", err) - time.Sleep(time.Duration(3) * time.Second) - return - } - log.Println("websocket auth and subscribe ok") - - client := &Client{hub: ClientHub, conn: c, send: make(chan *Message, 100), User: "111", Subscribe: SubscribeMessage{Key: "aa", Val: "bb"}} - client.hub.register <- client - defer func() { - client.hub.unregister <- client - }() - - go client.writePump() - done := make(chan struct{}) - go func() { //客户端的client.readRump - defer close(done) - for { - // 使用普通形式读,可以读到类似连接已关闭等的错误 - _, message, err := c.ReadMessage() - if err != nil { - log.Println("nat_local_debug_read_error", err.Error()) - return - } - - msg := &Message{} - err = json.Unmarshal(message, &msg) - if err != nil { - log.Println("nat_local_debug_json_error", err.Error()) - return - } - if config.DebugLevel >= config.LevelDebugBody { - log.Println("nat_local_read_from_websocket_message", msg.ID, msg.Method, string(msg.Body)) - } - - if msg.Method == METHOD_CREATE { - proxConn := dialProxy() - b := LocalBridge.Register(ClientHub, msg.ID, proxConn.(*net.TCPConn)) - go func() { - written, err := b.WritePump() - logCopyErr("nat_local_debug websocket->local", err) - log.Println(trace.ID(msg.ID), "nat debug response size", written) - }() - - // 从tcp返回数据到ws - go func() { - defer func() { - b.bridgeHub.unregister <- b - }() - readSize, err := b.CopyBuffer(b, proxConn, "local") - logCopyErr("nat_local_debug local->websocket", err) - log.Println(trace.ID(msg.ID), "nat debug request body size", readSize) - b.CloseWrite() - }() - } else { - LocalBridge.broadcast <- msg - } - } - }() - - for { - select { - case <-done: - return - case <-interrupt: - log.Println("interrupt") - interruptClose = true - - // Cleanly close the connection by sending a close message and then - // waiting (with timeout) for the server to close the connection. - err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) - if err != nil { - log.Println("write close:", err) - return - } - select { - case <-done: - case <-time.After(time.Second): - } - return - } - } -} - -type ClientHandler struct { - c *websocket.Conn -} - -func newClientHandler(c *websocket.Conn) *ClientHandler { - return &ClientHandler{c: c} -} - -func (h *ClientHandler) Auth(user string, token string) error { - msg := AuthMessage{User: user, Token: token} - return h.ask(&msg) -} - -func (h *ClientHandler) Subscribe(key string, val string) error { - msg := SubscribeMessage{Key: key, Val: val} - return h.ask(&msg) -} - -func (h *ClientHandler) ask(v interface{}) error { - err := h.c.WriteJSON(v) - if err != nil { - return err - } - ticker := time.NewTicker(3 * time.Second) - defer func() { - ticker.Stop() - }() - - send := make(chan []byte) - go func() { - defer close(send) - _, message, _ := h.c.ReadMessage() - send <- message - }() - select { - case message, ok := <-send: //ok为判断channel是否关闭 - if !ok { - return errors.New("fail") - } - if string(message) != "ok" { - return errors.New("fail, " + string(message)) - } - case <-ticker.C: - return errors.New("timeout") - } - return nil -} - -func logCopyErr(name string, err error) { - if err == nil { - return - } - if config.DebugLevel >= config.LevelLong { - log.Println(name, err.Error()) - } else if err != io.EOF { - log.Println(name, err.Error()) - } -} +package nat + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "log" + "net" + "net/url" + "os" + "os/signal" + "time" + + "github.com/keminar/anyproxy/utils/conf" + "github.com/keminar/anyproxy/utils/tools" + + "github.com/gorilla/websocket" + "github.com/keminar/anyproxy/config" + "github.com/keminar/anyproxy/utils/trace" +) + +var ClientHub *Hub +var LocalBridge *BridgeHub + +// ConnectServer 连接到websocket服务 +func ConnectServer(addr *string) { + interruptClose = false + interrupt := make(chan os.Signal, 1) + signal.Notify(interrupt, os.Interrupt) + + ClientHub = newHub() + go ClientHub.run() + LocalBridge = newBridgeHub() + go LocalBridge.run() + + for { + connect(addr, interrupt) + if interruptClose { + break + } + } +} + +// 连接本地Proxy服务 +func dialProxy() net.Conn { + connTimeout := time.Duration(5) * time.Second + var err error + localProxy := fmt.Sprintf("%s:%d", "127.0.0.1", config.ListenPort) + proxyConn, err := net.DialTimeout("tcp", localProxy, connTimeout) + if err != nil { + log.Println("dial local proxy", err) + } + log.Printf("local websocket connecting to %s", localProxy) + return proxyConn +} + +// 认证连接并交换数据 +func connect(addr *string, interrupt chan os.Signal) { + u := url.URL{Scheme: "ws", Host: *addr, Path: "/ws"} + log.Printf("connecting to %s", u.String()) + + c, _, err := websocket.DefaultDialer.Dial(u.String(), nil) + if err != nil { + log.Println("dial:", err) + time.Sleep(time.Duration(3) * time.Second) + return + } + defer c.Close() + + w := newClientHandler(c) + err = w.auth(conf.RouterConfig.Websocket.User, conf.RouterConfig.Websocket.Pass) + if err != nil { + log.Println("auth:", err) + time.Sleep(time.Duration(3) * time.Second) + return + } + err = w.subscribe(conf.RouterConfig.Websocket.Subscribe) + if err != nil { + log.Println("subscribe:", err) + time.Sleep(time.Duration(3) * time.Second) + return + } + log.Println("websocket auth and subscribe ok") + + client := &Client{hub: ClientHub, conn: c, send: make(chan *Message, 100)} + client.hub.register <- client + defer func() { + client.hub.unregister <- client + }() + + go client.writePump() + done := make(chan struct{}) + go func() { //客户端的client.readRump + defer close(done) + for { + // 使用普通形式读,可以读到类似连接已关闭等的错误 + _, message, err := c.ReadMessage() + if err != nil { + log.Println("nat_local_debug_read_error", err.Error()) + return + } + + msg := &Message{} + err = json.Unmarshal(message, &msg) + if err != nil { + log.Println("nat_local_debug_json_error", err.Error()) + return + } + if config.DebugLevel >= config.LevelDebugBody { + log.Println("nat_local_read_from_websocket_message", msg.ID, msg.Method, string(msg.Body)) + } + + if msg.Method == METHOD_CREATE { + proxConn := dialProxy() + b := LocalBridge.Register(ClientHub, msg.ID, proxConn.(*net.TCPConn)) + go func() { + written, err := b.WritePump() + logCopyErr("nat_local_debug websocket->local", err) + log.Println(trace.ID(msg.ID), "nat debug response size", written) + }() + + // 从tcp返回数据到ws + go func() { + defer func() { + b.bridgeHub.unregister <- b + }() + readSize, err := b.CopyBuffer(b, proxConn, "local") + logCopyErr("nat_local_debug local->websocket", err) + log.Println(trace.ID(msg.ID), "nat debug request body size", readSize) + b.CloseWrite() + }() + } else { + LocalBridge.broadcast <- msg + } + } + }() + + for { + select { + case <-done: + return + case <-interrupt: + log.Println("interrupt") + interruptClose = true + + // Cleanly close the connection by sending a close message and then + // waiting (with timeout) for the server to close the connection. + err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) + if err != nil { + log.Println("write close:", err) + return + } + select { + case <-done: + case <-time.After(time.Second): + } + return + } + } +} + +type ClientHandler struct { + c *websocket.Conn +} + +func newClientHandler(c *websocket.Conn) *ClientHandler { + return &ClientHandler{c: c} +} + +// auth 认证 +func (h *ClientHandler) auth(user string, pass string) error { + xtime := time.Now().Unix() + token, err := tools.Md5Str(fmt.Sprintf("%s|%s|%d", user, pass, xtime)) + if err != nil { + return err + } + msg := AuthMessage{User: user, Token: token, Xtime: xtime} + return h.ask(&msg) +} + +// subscribe 订阅 +func (h *ClientHandler) subscribe(sub []conf.Subscribe) error { + msg := []SubscribeMessage{} + for _, s := range sub { + msg = append(msg, SubscribeMessage{Key: s.Key, Val: s.Val}) + } + return h.ask(&msg) +} + +func (h *ClientHandler) ask(v interface{}) error { + err := h.c.WriteJSON(v) + if err != nil { + return err + } + ticker := time.NewTicker(3 * time.Second) + defer func() { + ticker.Stop() + }() + + send := make(chan []byte) + go func() { + defer close(send) + _, message, _ := h.c.ReadMessage() + send <- message + }() + select { + case message, ok := <-send: //ok为判断channel是否关闭 + if !ok { + return errors.New("fail") + } + if string(message) != "ok" { + return errors.New("fail, " + string(message)) + } + case <-ticker.C: + return errors.New("timeout") + } + return nil +} + +func logCopyErr(name string, err error) { + if err == nil { + return + } + if config.DebugLevel >= config.LevelLong { + log.Println(name, err.Error()) + } else if err != io.EOF { + log.Println(name, err.Error()) + } +} diff --git a/nat/message.go b/nat/message.go index aec84bc..bfcfa50 100644 --- a/nat/message.go +++ b/nat/message.go @@ -10,6 +10,7 @@ const METHOD_CLOSE = "close" type AuthMessage struct { User string Token string + Xtime int64 } // 订阅 diff --git a/utils/conf/router.go b/utils/conf/router.go index 31c12df..73198d3 100644 --- a/utils/conf/router.go +++ b/utils/conf/router.go @@ -24,18 +24,32 @@ type Log struct { Dir string `yaml:"dir"` } +// Subscribe 订阅标志 +type Subscribe struct { + Key string `yaml:"key"` + Val string `yaml:"val"` +} + +// Websocket 与服务端websocket通信 +type Websocket struct { + User string `yaml:"user"` + Pass string `yaml:"pass"` + Subscribe []Subscribe `yaml:"subscribe"` +} + // Router 配置文件模型 type Router struct { - Listen string `yaml:"listen"` //监听端口 - Log Log `yaml:"log"` //日志目录 - Token string `yaml:"token"` //加密值 - DNS string `yaml:"dns"` //默认的DNS服务器 - Target string `yaml:"target"` //http默认访问策略 - TCPTarget string `yaml:"tcpTarget"` //tcp默认访问策略 - Match string `yaml:"match"` //默认域名比对 - Proxy string `yaml:"proxy"` //全局代理服务器 - Hosts []Host `yaml:"hosts"` //域名列表 - AllowIP []string `yaml:"allowIP"` //可以访问的客户端IP + Listen string `yaml:"listen"` //监听端口 + Log Log `yaml:"log"` //日志目录 + Token string `yaml:"token"` //加密值 + DNS string `yaml:"dns"` //默认的DNS服务器 + Target string `yaml:"target"` //http默认访问策略 + TCPTarget string `yaml:"tcpTarget"` //tcp默认访问策略 + Match string `yaml:"match"` //默认域名比对 + Proxy string `yaml:"proxy"` //全局代理服务器 + Hosts []Host `yaml:"hosts"` //域名列表 + AllowIP []string `yaml:"allowIP"` //可以访问的客户端IP + Websocket Websocket `yaml:"websocket"` //会话订阅请求信息 } // LoadRouterConfig 加载配置 diff --git a/utils/tools/string.go b/utils/tools/string.go index 1f95606..58d75ac 100644 --- a/utils/tools/string.go +++ b/utils/tools/string.go @@ -1,5 +1,10 @@ package tools +import ( + "crypto/md5" + "encoding/hex" +) + // GetPort 从 127.0.0.1:3000 结构中取出3000 func GetPort(addr string) string { for i := len(addr) - 1; i >= 0; i-- { @@ -9,3 +14,10 @@ func GetPort(addr string) string { } return "" } + +func Md5Str(str string) (string, error) { + h := md5.New() + h.Write([]byte(str)) + cipherStr := h.Sum(nil) + return hex.EncodeToString(cipherStr), nil +} From a3ab17d30a6d7887216326276e51e7e8c1a08083 Mon Sep 17 00:00:00 2001 From: liminggui Date: Tue, 6 Apr 2021 16:18:10 +0800 Subject: [PATCH 11/32] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=AE=A2=E9=98=85?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nat/bridge.go | 8 ++++---- nat/bridge_hub.go | 4 ++-- nat/client_hub.go | 40 ++++++++++++---------------------------- nat/conn.go | 16 ++++++++++++++++ nat/handler.go | 6 +----- proto/http.go | 27 ++++++++++++++++----------- proto/websocket.go | 17 +++++++++++++---- 7 files changed, 64 insertions(+), 54 deletions(-) diff --git a/nat/bridge.go b/nat/bridge.go index 5f0c912..c814fba 100644 --- a/nat/bridge.go +++ b/nat/bridge.go @@ -12,7 +12,7 @@ import ( type Bridge struct { bridgeHub *BridgeHub - wsHub *Hub + client *Client reqID uint //请求id conn *net.TCPConn @@ -29,20 +29,20 @@ func (h *Bridge) Unregister(b *Bridge) { // 向websocket hub写数据 func (b *Bridge) Write(p []byte) (n int, err error) { msg := &Message{ID: b.reqID, Body: p} - b.wsHub.broadcast <- msg + b.client.send <- msg return len(p), nil } // 通知websocket 创建连接 func (b *Bridge) Open() { msg := &Message{ID: b.reqID, Method: METHOD_CREATE} - b.wsHub.broadcast <- msg + b.client.send <- msg } // 通知tcp关闭连接 func (b *Bridge) CloseWrite() { msg := &Message{ID: b.reqID, Method: METHOD_CLOSE} - b.wsHub.broadcast <- msg + b.client.send <- msg } // 从websocket hub读数据写到请求http端 diff --git a/nat/bridge_hub.go b/nat/bridge_hub.go index 15af85c..0008a69 100644 --- a/nat/bridge_hub.go +++ b/nat/bridge_hub.go @@ -72,8 +72,8 @@ func (h *BridgeHub) run() { } } -func (h *BridgeHub) Register(wsHub *Hub, ID uint, conn *net.TCPConn) *Bridge { - b := &Bridge{bridgeHub: h, reqID: ID, conn: conn, send: make(chan []byte, 100), wsHub: wsHub} +func (h *BridgeHub) Register(c *Client, ID uint, conn *net.TCPConn) *Bridge { + b := &Bridge{bridgeHub: h, reqID: ID, conn: conn, send: make(chan []byte, 100), client: c} h.register <- b return b } diff --git a/nat/client_hub.go b/nat/client_hub.go index 484c710..814ddcd 100644 --- a/nat/client_hub.go +++ b/nat/client_hub.go @@ -1,9 +1,7 @@ package nat import ( - "log" - - "github.com/keminar/anyproxy/config" + "github.com/keminar/anyproxy/proto/http" ) // Hub maintains the set of active clients and broadcasts messages to the @@ -12,9 +10,6 @@ type Hub struct { // Registered clients. clients map[*Client]bool - // Inbound messages from the clients. - broadcast chan *Message - // Register requests from the clients. register chan *Client @@ -24,7 +19,6 @@ type Hub struct { func newHub() *Hub { return &Hub{ - broadcast: make(chan *Message), register: make(chan *Client), unregister: make(chan *Client), clients: make(map[*Client]bool), @@ -41,28 +35,18 @@ func (h *Hub) run() { delete(h.clients, client) close(client.send) } - case message := <-h.broadcast: - if config.DebugLevel >= config.LevelDebug { - log.Println("clients nums", len(h.clients)) - } - Exit: - for client := range h.clients { - if config.DebugLevel >= config.LevelDebugBody { - log.Println("nat_debug_write_client_hub", message.ID, message.Method, string(message.Body)) - } - // todo 检查 client subscribe 来判断发送给谁 - select { - case client.send <- message: - //发送给一个订阅者就要返回,不然变成多个并发请求了,而且接收数据也会出错。 - break Exit - default: // 当send chan满时也会走进default - if config.DebugLevel >= config.LevelDebug { - log.Println("why go here ?????") - } - close(client.send) - delete(h.clients, client) - } + } + } +} + +func (h *Hub) GetClient(header http.Header) *Client { + for client := range h.clients { + for _, s := range client.Subscribe { + val := header.Get(s.Key) + if val != "" && val == s.Val { + return client } } } + return nil } diff --git a/nat/conn.go b/nat/conn.go index 456dd5d..7a70332 100644 --- a/nat/conn.go +++ b/nat/conn.go @@ -35,11 +35,27 @@ var upgrader = websocket.Upgrader{ var ServerHub *Hub var ServerBridge *BridgeHub +// serverStart 是否开启服务 +var serverStart = false + +// Eable 检查是否可以发送nat请求 +func Eable() bool { + if !serverStart { + return false + } + if len(ServerHub.clients) == 0 { + return false + } + return true +} + +// NewServer 开启服务 func NewServer(addr *string) { ServerHub = newHub() go ServerHub.run() ServerBridge = newBridgeHub() go ServerBridge.run() + serverStart = true http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { serveWs(ServerHub, w, r) diff --git a/nat/handler.go b/nat/handler.go index 55f1288..a6b060d 100644 --- a/nat/handler.go +++ b/nat/handler.go @@ -84,10 +84,6 @@ func connect(addr *string, interrupt chan os.Signal) { log.Println("websocket auth and subscribe ok") client := &Client{hub: ClientHub, conn: c, send: make(chan *Message, 100)} - client.hub.register <- client - defer func() { - client.hub.unregister <- client - }() go client.writePump() done := make(chan struct{}) @@ -113,7 +109,7 @@ func connect(addr *string, interrupt chan os.Signal) { if msg.Method == METHOD_CREATE { proxConn := dialProxy() - b := LocalBridge.Register(ClientHub, msg.ID, proxConn.(*net.TCPConn)) + b := LocalBridge.Register(client, msg.ID, proxConn.(*net.TCPConn)) go func() { written, err := b.WritePump() logCopyErr("nat_local_debug websocket->local", err) diff --git a/proto/http.go b/proto/http.go index 97cc5c8..084c554 100644 --- a/proto/http.go +++ b/proto/http.go @@ -12,6 +12,7 @@ import ( "github.com/keminar/anyproxy/config" "github.com/keminar/anyproxy/crypto" + "github.com/keminar/anyproxy/nat" "github.com/keminar/anyproxy/proto/http" "github.com/keminar/anyproxy/proto/text" "github.com/keminar/anyproxy/utils/trace" @@ -258,17 +259,21 @@ func (that *httpStream) badRequest(err error) { } func (that *httpStream) response() error { - if test, ok := that.Header["Anyproxy-Action"]; ok && test[0] == "websocket" { - that.Header.Del("Anyproxy-Action") - tunnel := newWsTunnel(that.req) - // 先将请求头部发出 - tunnel.buffer.Write([]byte(fmt.Sprintf("%s\r\n", that.FirstLine))) - that.Header.Write(tunnel.buffer) - tunnel.buffer.Write([]byte("\r\n")) - // 多读取的body部分 - tunnel.buffer.Write(that.BodyBuf) - tunnel.transfer() - return nil + if nat.Eable() { + if test, ok := that.Header["Anyproxy-Action"]; ok && test[0] == "websocket" { + that.Header.Del("Anyproxy-Action") + tunnel := newWsTunnel(that.req, that.Header) + // 先将请求头部发出 + tunnel.buffer.Write([]byte(fmt.Sprintf("%s\r\n", that.FirstLine))) + that.Header.Write(tunnel.buffer) + tunnel.buffer.Write([]byte("\r\n")) + // 多读取的body部分 + tunnel.buffer.Write(that.BodyBuf) + ok := tunnel.transfer() + if ok == true { + return nil + } + } } tunnel := newTunnel(that.req) if ip, ok := tunnel.isAllowed(); !ok { diff --git a/proto/websocket.go b/proto/websocket.go index 651280a..6e19ec6 100644 --- a/proto/websocket.go +++ b/proto/websocket.go @@ -7,12 +7,14 @@ import ( "github.com/keminar/anyproxy/config" "github.com/keminar/anyproxy/nat" + "github.com/keminar/anyproxy/proto/http" "github.com/keminar/anyproxy/utils/trace" ) // 转发实体 type wsTunnel struct { - req *Request + req *Request + header http.Header readSize int64 writeSize int64 @@ -21,21 +23,27 @@ type wsTunnel struct { } // newTunnel 实例 -func newWsTunnel(req *Request) *wsTunnel { +func newWsTunnel(req *Request, header http.Header) *wsTunnel { s := &wsTunnel{ req: req, + header: header, buffer: new(bytes.Buffer), } return s } // transfer 交换数据 -func (s *wsTunnel) transfer() { +func (s *wsTunnel) transfer() bool { if config.DebugLevel >= config.LevelLong { log.Println(trace.ID(s.req.ID), "websocket transfer start") } - b := nat.ServerBridge.Register(nat.ServerHub, s.req.ID, s.req.conn) + c := nat.ServerHub.GetClient(s.header) + if c == nil { + // 走旧转发 + return false + } + b := nat.ServerBridge.Register(c, s.req.ID, s.req.conn) defer func() { b.Unregister(b) }() @@ -64,6 +72,7 @@ func (s *wsTunnel) transfer() { <-done // 不管是不是正常结束,只要server结束了,函数就会返回,然后底层会自动断开与client的连接 log.Println(trace.ID(s.req.ID), "websocket transfer finished, response size", s.writeSize) + return true } func (s *wsTunnel) logCopyErr(name string, err error) { From 155d1c7f10e5ee521f7335eeb8f420e5a6b5357d Mon Sep 17 00:00:00 2001 From: liminggui Date: Wed, 7 Apr 2021 01:28:57 +0800 Subject: [PATCH 12/32] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 13 +++++-------- anyproxy.go | 15 +++++---------- conf/router.yaml | 2 +- nat/conn.go | 4 ++++ proto/http.go | 9 +++++++-- proto/websocket.go | 1 + tunnel/tunneld.go | 19 ++++++++++++------- utils/help/help.go | 2 ++ utils/tools/string.go | 9 +++++++++ 9 files changed, 46 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 53a9fb3..a2e17bc 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,6 @@ sudo iptables -t nat -D OUTPUT 2 > ~~划线~~ 部分为已实现功能 * ~~可将请求转发到Tunnel服务~~ -* 根据CIDR做不同出口请求 * ~~对域名支持加Host绑定~~ * ~~对域名配置请求出口~~ * ~~增加全局默认出口配置~~ @@ -161,17 +160,15 @@ sudo iptables -t nat -D OUTPUT 2 * ~~可以自定义代理server,如果不可用则用全局的~~ * ~~server多级转发~~ * ~~加域名黑名单功能,不给请求~~ -* 请求Body内容体记录, 涉及安全,可能不会实现 -* 服务间通信http请求完全加密(header+body) -* HTTPS的SNI的支持? * ~~支持转发到socket5服务~~ -* TCP 增加更多协议解析支持,如rtmp,ftp等 -* 与Tunnel的多账户认证,账户可设置有效期 * ~~支持HTTP/1.1 keep-alive 一外链接多次请求不同域名~~ -* HTTP/1.1 keep-alive后端也能复用tcp * ~~修复iptables转发后百度贴吧无法访问的问题~~ -* 转发的mysql的连接请求会一直卡住 * ~~支持windows平台使用~~ +* ~~通过websocket实现内网穿透(必须为http的非CONNECT请求)~~ +* TCP 增加更多协议解析支持,如rtmp,ftp, socks5, https(SNI)等 +* TCP 转发的mysql的连接请求会一直卡住 +* 与Tunnel的多账户认证,账户可设置有效期 +* HTTP/1.1 keep-alive后端也能复用tcp # 感谢 diff --git a/anyproxy.go b/anyproxy.go index 3f05b7b..07dff1d 100644 --- a/anyproxy.go +++ b/anyproxy.go @@ -8,7 +8,6 @@ import ( "net/http" _ "net/http/pprof" "os" - "strings" "github.com/keminar/anyproxy/config" "github.com/keminar/anyproxy/grace" @@ -18,6 +17,7 @@ import ( "github.com/keminar/anyproxy/utils/conf" "github.com/keminar/anyproxy/utils/daemon" "github.com/keminar/anyproxy/utils/help" + "github.com/keminar/anyproxy/utils/tools" ) var ( @@ -35,7 +35,7 @@ func init() { flag.StringVar(&gListenAddrPort, "l", "", "Address and port to listen on") flag.StringVar(&gProxyServerSpec, "p", "", "Proxy servers to use") flag.StringVar(&gWebsocketListen, "ws-listen", "", "Websocket address and port to listen on") - flag.StringVar(&gWebsocketConn, "ws-connect", "", "websocket Address and port to connect") + flag.StringVar(&gWebsocketConn, "ws-connect", "", "Websocket Address and port to connect") flag.IntVar(&gDebug, "debug", 0, "debug mode (0, 1, 2)") flag.StringVar(&gPprof, "pprof", "", "pprof port, disable if empty") flag.BoolVar(&gHelp, "h", false, "This usage message") @@ -67,10 +67,7 @@ func main() { daemon.Daemonize(envRunMode, fd) gListenAddrPort = config.IfEmptyThen(gListenAddrPort, conf.RouterConfig.Listen, ":3000") - // 支持只输入端口的形式 - if !strings.Contains(gListenAddrPort, ":") { - gListenAddrPort = ":" + gListenAddrPort - } + gListenAddrPort = tools.FillPort(gListenAddrPort) config.SetListenPort(gListenAddrPort) var writer io.Writer @@ -88,10 +85,7 @@ func main() { // 调试模式 if len(gPprof) > 0 { go func() { - // 支持只输入端口的形式 - if !strings.Contains(gPprof, ":") { - gPprof = ":" + gPprof - } + gPprof = tools.FillPort(gPprof) //浏览器访问: http://:5001/debug/pprof/ log.Println("Starting pprof debug server ...") // 这里不要使用log.Fatal会在平滑重启时导致进程退出 @@ -101,6 +95,7 @@ func main() { } if gWebsocketListen != "" { + gWebsocketListen = tools.FillPort(gWebsocketListen) go nat.NewServer(&gWebsocketListen) } diff --git a/conf/router.yaml b/conf/router.yaml index f366474..505e950 100644 --- a/conf/router.yaml +++ b/conf/router.yaml @@ -21,7 +21,7 @@ hosts: # 如果有用proxy自定义代理可用,target强制当remote使用,proxy代理不可用,target按原逻辑处理 target: remote # 参考全局localDns - dns: local + dns: remote # 支持 http:// , tunnel:// , socks5:// 三种协议,默认 tunnel:// proxy: http://127.0.0.1:8888 - name: golang.org diff --git a/nat/conn.go b/nat/conn.go index 7a70332..6f689e9 100644 --- a/nat/conn.go +++ b/nat/conn.go @@ -113,9 +113,13 @@ func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) { } conn.WriteMessage(websocket.TextMessage, []byte("ok")) + clientNum := len(hub.clients) // 注册连接 client := &Client{hub: hub, conn: conn, send: make(chan *Message, 100), User: user.User, Subscribe: subscribe} client.hub.register <- client + clientNum++ //这里不用len计算是因为chan异步不确认谁先执行 + + log.Printf("client ip %s connected, total client nums %d\n", r.RemoteAddr, clientNum) go client.writePump() go client.readPump() diff --git a/proto/http.go b/proto/http.go index 084c554..ffb0770 100644 --- a/proto/http.go +++ b/proto/http.go @@ -259,9 +259,14 @@ func (that *httpStream) badRequest(err error) { } func (that *httpStream) response() error { + specialHeader := "Anyproxy-Action" + if config.DebugLevel >= config.LevelDebug { + fmt.Println(that.Header) + log.Println(trace.ID(that.req.ID), "nat server status", nat.Eable(), "special header", that.Header.Get(specialHeader)) + } if nat.Eable() { - if test, ok := that.Header["Anyproxy-Action"]; ok && test[0] == "websocket" { - that.Header.Del("Anyproxy-Action") + if that.Header.Get(specialHeader) == "websocket" { + that.Header.Del(specialHeader) tunnel := newWsTunnel(that.req, that.Header) // 先将请求头部发出 tunnel.buffer.Write([]byte(fmt.Sprintf("%s\r\n", that.FirstLine))) diff --git a/proto/websocket.go b/proto/websocket.go index 6e19ec6..6946d35 100644 --- a/proto/websocket.go +++ b/proto/websocket.go @@ -41,6 +41,7 @@ func (s *wsTunnel) transfer() bool { c := nat.ServerHub.GetClient(s.header) if c == nil { // 走旧转发 + log.Println(trace.ID(s.req.ID), "websocket transfer fail") return false } b := nat.ServerBridge.Register(c, s.req.ID, s.req.conn) diff --git a/tunnel/tunneld.go b/tunnel/tunneld.go index 03d1deb..55e4df6 100644 --- a/tunnel/tunneld.go +++ b/tunnel/tunneld.go @@ -5,20 +5,22 @@ import ( "fmt" "io" "os" - "strings" "github.com/keminar/anyproxy/config" "github.com/keminar/anyproxy/grace" "github.com/keminar/anyproxy/logging" + "github.com/keminar/anyproxy/nat" "github.com/keminar/anyproxy/proto" "github.com/keminar/anyproxy/utils/conf" "github.com/keminar/anyproxy/utils/daemon" "github.com/keminar/anyproxy/utils/help" + "github.com/keminar/anyproxy/utils/tools" ) var ( gListenAddrPort string gProxyServerSpec string + gWebsocketListen string gHelp bool gDebug int ) @@ -27,9 +29,10 @@ func init() { flag.Usage = help.Usage flag.StringVar(&gListenAddrPort, "l", "", "Address and port to listen on") flag.StringVar(&gProxyServerSpec, "p", "", "Proxy servers to use") - flag.IntVar(&gDebug, "d", 0, "debug mode") + flag.StringVar(&gWebsocketListen, "ws-listen", "", "Websocket address and port to listen on") + //不支持ws-connect + flag.IntVar(&gDebug, "debug", 0, "debug mode (0, 1, 2)") flag.BoolVar(&gHelp, "h", false, "This usage message") - } func main() { @@ -57,10 +60,7 @@ func main() { daemon.Daemonize(envRunMode, fd) gListenAddrPort = config.IfEmptyThen(gListenAddrPort, conf.RouterConfig.Listen, ":3001") - // 支持只输入端口的形式 - if !strings.Contains(gListenAddrPort, ":") { - gListenAddrPort = ":" + gListenAddrPort - } + gListenAddrPort = tools.FillPort(gListenAddrPort) var writer io.Writer // 前台执行,daemon运行根据环境变量识别 @@ -74,6 +74,11 @@ func main() { gProxyServerSpec = config.IfEmptyThen(gProxyServerSpec, conf.RouterConfig.Proxy, "") config.SetProxyServer(gProxyServerSpec) + if gWebsocketListen != "" { + gWebsocketListen = tools.FillPort(gWebsocketListen) + go nat.NewServer(&gWebsocketListen) + } + server := grace.NewServer(gListenAddrPort, proto.ServerHandler) server.ListenAndServe() } diff --git a/utils/help/help.go b/utils/help/help.go index 4838e32..9ee68ab 100644 --- a/utils/help/help.go +++ b/utils/help/help.go @@ -21,6 +21,8 @@ func Usage() { fmt.Fprintf(os.Stdout, "Optional\n") fmt.Fprintf(os.Stdout, " -p=PROXIES Address and ports of upstream proxy servers to use\n") fmt.Fprintf(os.Stdout, " (e.g., 10.1.1.1:80 will use http proxy, socks5://10.2.2.2:3128 use socks5 proxy\n") + fmt.Fprintf(os.Stdout, " -ws-listen Websocket address and port to listen on\n") + fmt.Fprintf(os.Stdout, " -ws-connect Websocket Address and port to connect\n") fmt.Fprintf(os.Stdout, " -daemon Run as a Unix daemon\n") fmt.Fprintf(os.Stdout, " -debug Debug mode (0, 1, 2, 3)\n") fmt.Fprintf(os.Stdout, " -pprof Pprof port, disable if empty\n") diff --git a/utils/tools/string.go b/utils/tools/string.go index 58d75ac..434425a 100644 --- a/utils/tools/string.go +++ b/utils/tools/string.go @@ -3,6 +3,7 @@ package tools import ( "crypto/md5" "encoding/hex" + "strings" ) // GetPort 从 127.0.0.1:3000 结构中取出3000 @@ -21,3 +22,11 @@ func Md5Str(str string) (string, error) { cipherStr := h.Sum(nil) return hex.EncodeToString(cipherStr), nil } + +// 支持只输入端口的形式 +func FillPort(port string) string { + if !strings.Contains(port, ":") { + port = ":" + port + } + return port +} From 2224bc940461d034389d9ecd8335ccb050a6cc81 Mon Sep 17 00:00:00 2001 From: liminggui Date: Wed, 7 Apr 2021 01:41:12 +0800 Subject: [PATCH 13/32] buffer size --- nat/conn.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nat/conn.go b/nat/conn.go index 6f689e9..2e88251 100644 --- a/nat/conn.go +++ b/nat/conn.go @@ -28,8 +28,8 @@ var ( ) var upgrader = websocket.Upgrader{ - ReadBufferSize: 1024 * 1024 * 1024, - WriteBufferSize: 1024 * 1024 * 1024, + ReadBufferSize: 1024, + WriteBufferSize: 1024, } var ServerHub *Hub From e59d1182447a2f5036c5a472b6cc8062c88f1aa3 Mon Sep 17 00:00:00 2001 From: liminggui Date: Wed, 7 Apr 2021 01:43:35 +0800 Subject: [PATCH 14/32] debug --- proto/http.go | 1 - 1 file changed, 1 deletion(-) diff --git a/proto/http.go b/proto/http.go index ffb0770..a902750 100644 --- a/proto/http.go +++ b/proto/http.go @@ -261,7 +261,6 @@ func (that *httpStream) badRequest(err error) { func (that *httpStream) response() error { specialHeader := "Anyproxy-Action" if config.DebugLevel >= config.LevelDebug { - fmt.Println(that.Header) log.Println(trace.ID(that.req.ID), "nat server status", nat.Eable(), "special header", that.Header.Get(specialHeader)) } if nat.Eable() { From c5589357aff0e7367d88c00c967164e493de83d9 Mon Sep 17 00:00:00 2001 From: liminggui Date: Wed, 7 Apr 2021 10:10:05 +0800 Subject: [PATCH 15/32] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 7 +++---- README.md | 14 +++++++++----- anyproxy.go | 1 + proto/http.go | 4 ++-- scripts/build.sh | 13 +++++++------ tunnel/tunneld.go | 13 +++---------- utils/help/help.go | 30 +++++++++++++++++++++++++----- 7 files changed, 50 insertions(+), 32 deletions(-) diff --git a/.gitignore b/.gitignore index 254ace2..cb06193 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,11 @@ .history .vscode +dist/ +logs/ anyproxy anyproxy-alpine anyproxy-darwin anyproxy-windows.exe tunneld tunneld-alpine -tunnel/tunneld -tunnel/tunneld-alpine -logs/ -tunnel/logs/ \ No newline at end of file +tunnel/logs/ diff --git a/README.md b/README.md index a2e17bc..a134e99 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ anyproxy 是一个部署在Linux系统上的tcp流转发器,可以直接将本 提醒:请使用浏览器右键的“链接另存为”下载文件 -tunneld 是一个anyproxy的服务端,部署在服务器上接收anyproxy的请求,并代理发出请求或是转到下一个tunneld。用于跨内网访问资源使用 +tunneld 是一个anyproxy的服务端,部署在服务器上接收anyproxy的请求,并代理发出请求或是转到下一个tunneld。用于跨内网访问资源使用。非anyproxy请求一概拒绝处理 # 路由支持 @@ -28,13 +28,13 @@ tunneld 是一个anyproxy的服务端,部署在服务器上接收anyproxy的 # or +----------+ +----------+ +---------+ +---------+ +----------+ -| Computer | <==> | anyproxy | <==> | tunneld | <==> | tunneld | <==> | Internet | +| Computer | <==> | anyproxy | <==> | tunneld | <==> | socks5 | <==> | Internet | +----------+ +----------+ +---------+ +---------+ +----------+ # or -+----------+ +----------+ +---------+ +---------+ +----------+ -| Computer | <==> | anyproxy | <==> | tunneld | <==> | socks5 | <==> | Internet | -+----------+ +----------+ +---------+ +---------+ +----------+ ++----------+ +---------+ +-----------+ ws +-----------+ +---------+ +| Computer | <==> | Nginx A | <==> | anyproxy S| <==> | anyproxy C| <==> | Nginx B | ++----------+ +---------+ +-----------+ +-----------+ +---------+ ``` # 使用案例 @@ -48,6 +48,10 @@ tunneld 是一个anyproxy的服务端,部署在服务器上接收anyproxy的 `本地通过内网 anyproxy 代理上网,遇到测试服务器域名则跳到外网tunneld转发,网站的nginx根据来源IP进行转发到特定测试环境(有几个环境就需要有几个tunneld服务且IP要不同)` +> 案例3: 解决HTTPS抓包问题 + +`本地将https请求到服务器,服务器解证书后增加特定头部转到anyproxy websocket服务端,本地另起一个anyproxy的websocket客户端接收并将http请求转发到Charles` + # 源码编译 > 安装Go环境并设置GOPROXY diff --git a/anyproxy.go b/anyproxy.go index 07dff1d..0a64f1e 100644 --- a/anyproxy.go +++ b/anyproxy.go @@ -100,6 +100,7 @@ func main() { } if gWebsocketConn != "" { + gWebsocketConn = tools.FillPort(gWebsocketConn) go nat.ConnectServer(&gWebsocketConn) } server := grace.NewServer(gListenAddrPort, proto.ClientHandler) diff --git a/proto/http.go b/proto/http.go index a902750..8f9bff2 100644 --- a/proto/http.go +++ b/proto/http.go @@ -261,9 +261,9 @@ func (that *httpStream) badRequest(err error) { func (that *httpStream) response() error { specialHeader := "Anyproxy-Action" if config.DebugLevel >= config.LevelDebug { - log.Println(trace.ID(that.req.ID), "nat server status", nat.Eable(), "special header", that.Header.Get(specialHeader)) + log.Println(trace.ID(that.req.ID), "nat server status:", nat.Eable(), ",special header:", that.Header.Get(specialHeader)) } - if nat.Eable() { + if that.Method != "CONNECT" && nat.Eable() { //CONNECT 请求不支持ws转发 if that.Header.Get(specialHeader) == "websocket" { that.Header.Del(specialHeader) tunnel := newWsTunnel(that.req, that.Header) diff --git a/scripts/build.sh b/scripts/build.sh index 1c1580d..836a4b7 100644 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -3,30 +3,31 @@ SCRIPT=$(readlink -f $0) ROOT_DIR=$(dirname $SCRIPT)/../ cd $ROOT_DIR +mkdir dist/ # anyproxy echo "build anyproxy" # for linux echo " for linux" -go build -o anyproxy anyproxy.go +go build -o dist/anyproxy anyproxy.go # for mac echo " for mac" -CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o anyproxy-darwin anyproxy.go +CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o dist/anyproxy-darwin anyproxy.go # for windows echo " for windows" -CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o anyproxy-windows.exe anyproxy.go +CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o dist/anyproxy-windows.exe anyproxy.go # for alpine echo " for alpine" -go build -tags netgo -o anyproxy-alpine anyproxy.go +go build -tags netgo -o dist/anyproxy-alpine anyproxy.go # tunneld echo "build tunneld" echo " for linux" -go build -o tunnel/tunneld tunnel/tunneld.go +go build -o dist/tunneld tunnel/tunneld.go # for alpine echo " for alpine" -go build -tags netgo -o tunnel/tunneld-alpine tunnel/tunneld.go \ No newline at end of file +go build -tags netgo -o dist/tunneld-alpine tunnel/tunneld.go \ No newline at end of file diff --git a/tunnel/tunneld.go b/tunnel/tunneld.go index 55e4df6..59c6a27 100644 --- a/tunnel/tunneld.go +++ b/tunnel/tunneld.go @@ -9,7 +9,6 @@ import ( "github.com/keminar/anyproxy/config" "github.com/keminar/anyproxy/grace" "github.com/keminar/anyproxy/logging" - "github.com/keminar/anyproxy/nat" "github.com/keminar/anyproxy/proto" "github.com/keminar/anyproxy/utils/conf" "github.com/keminar/anyproxy/utils/daemon" @@ -20,17 +19,15 @@ import ( var ( gListenAddrPort string gProxyServerSpec string - gWebsocketListen string gHelp bool gDebug int ) func init() { - flag.Usage = help.Usage + flag.Usage = help.TunnelUsage flag.StringVar(&gListenAddrPort, "l", "", "Address and port to listen on") flag.StringVar(&gProxyServerSpec, "p", "", "Proxy servers to use") - flag.StringVar(&gWebsocketListen, "ws-listen", "", "Websocket address and port to listen on") - //不支持ws-connect + //CONNECT请求不支持ws-listen 和 ws-connect,且tunnel只做接收anyproxy的安全转发,不需要支持ws flag.IntVar(&gDebug, "debug", 0, "debug mode (0, 1, 2)") flag.BoolVar(&gHelp, "h", false, "This usage message") } @@ -74,11 +71,7 @@ func main() { gProxyServerSpec = config.IfEmptyThen(gProxyServerSpec, conf.RouterConfig.Proxy, "") config.SetProxyServer(gProxyServerSpec) - if gWebsocketListen != "" { - gWebsocketListen = tools.FillPort(gWebsocketListen) - go nat.NewServer(&gWebsocketListen) - } - + // 与anyproxy不同之处在ServerHandler server := grace.NewServer(gListenAddrPort, proto.ServerHandler) server.ListenAndServe() } diff --git a/utils/help/help.go b/utils/help/help.go index 9ee68ab..e06a6bd 100644 --- a/utils/help/help.go +++ b/utils/help/help.go @@ -13,14 +13,15 @@ const VERSION = "0.9" // Usage 帮助 func Usage() { - fmt.Fprintf(os.Stdout, "%s\n\n", versionString()) + fmt.Fprintf(os.Stdout, "%s\n\n", versionString("anyproxy")) fmt.Fprintf(os.Stdout, "usage: %s -l listenaddress -p proxies \n", os.Args[0]) fmt.Fprintf(os.Stdout, " Proxies any tcp port transparently using Linux netfilter\n\n") fmt.Fprintf(os.Stdout, "Mandatory\n") - fmt.Fprintf(os.Stdout, " -l=ADDRPORT Address and port to listen on (e.g., :3128 or 127.0.0.1:3128)\n") + fmt.Fprintf(os.Stdout, " -l=ADDRPORT Address and port to listen on (e.g., :3000 or 127.0.0.1:3000)\n") fmt.Fprintf(os.Stdout, "Optional\n") fmt.Fprintf(os.Stdout, " -p=PROXIES Address and ports of upstream proxy servers to use\n") - fmt.Fprintf(os.Stdout, " (e.g., 10.1.1.1:80 will use http proxy, socks5://10.2.2.2:3128 use socks5 proxy\n") + fmt.Fprintf(os.Stdout, " (e.g., 10.1.1.1:80 will use http proxy, socks5://10.2.2.2:3128 use socks5 proxy,\n") + fmt.Fprintf(os.Stdout, " tunnel://10.2.2.2:3001 use tunnel proxy)\n") fmt.Fprintf(os.Stdout, " -ws-listen Websocket address and port to listen on\n") fmt.Fprintf(os.Stdout, " -ws-connect Websocket Address and port to connect\n") fmt.Fprintf(os.Stdout, " -daemon Run as a Unix daemon\n") @@ -53,10 +54,29 @@ func Usage() { fmt.Fprintf(os.Stdout, "Thanks to https://github.com/ryanchapman/go-any-proxy.git\n") } -func versionString() (v string) { +// TunnelUsage 帮助 +func TunnelUsage() { + fmt.Fprintf(os.Stdout, "%s\n\n", versionString("tunneld")) + fmt.Fprintf(os.Stdout, "usage: %s -l listenaddress -p proxies \n", os.Args[0]) + fmt.Fprintf(os.Stdout, " Proxies anyproxy reqest\n\n") + fmt.Fprintf(os.Stdout, "Mandatory\n") + fmt.Fprintf(os.Stdout, " -l=ADDRPORT Address and port to listen on (e.g., :3001 or 127.0.0.1:3001)\n") + fmt.Fprintf(os.Stdout, "Optional\n") + fmt.Fprintf(os.Stdout, " -p=PROXIES Address and ports of upstream proxy servers to use\n") + fmt.Fprintf(os.Stdout, " (e.g., 10.1.1.1:80 will use http proxy, socks5://10.2.2.2:3128 use socks5 proxy,\n") + fmt.Fprintf(os.Stdout, " tunnel://10.2.2.2:3001 use tunnel proxy)\n") + fmt.Fprintf(os.Stdout, " -daemon Run as a Unix daemon\n") + fmt.Fprintf(os.Stdout, " -debug Debug mode (0, 1, 2, 3)\n") + fmt.Fprintf(os.Stdout, " -h This usage message\n\n") + + fmt.Fprintf(os.Stdout, "Report bugs to https://github.com/keminar/anyproxy or .\n") + fmt.Fprintf(os.Stdout, "Thanks to https://github.com/ryanchapman/go-any-proxy.git\n") +} + +func versionString(name string) (v string) { now := time.Now().Unix() buildNum := strings.ToUpper(strconv.FormatInt(now, 36)) buildDate := time.Unix(now, 0).Format(time.UnixDate) - v = fmt.Sprintf("anyproxy %s (build %v, %v)", VERSION, buildNum, buildDate) + v = fmt.Sprintf("%s %s (build %v, %v)", name, VERSION, buildNum, buildDate) return } From b64af067cccfe3f6d6a767ffffb038cf80e761ae Mon Sep 17 00:00:00 2001 From: liminggui Date: Wed, 7 Apr 2021 11:23:12 +0800 Subject: [PATCH 16/32] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- anyproxy.go | 3 +++ conf/router.yaml | 4 ++++ logging/logger.go | 8 +++++++- proto/http.go | 21 ++++++++++++--------- proto/tunnel.go | 4 ++-- proto/websocket.go | 16 ++++++++++++++++ scripts/build.sh | 2 +- utils/conf/router.go | 16 +++++++++------- 8 files changed, 54 insertions(+), 20 deletions(-) diff --git a/anyproxy.go b/anyproxy.go index 0a64f1e..4a891ba 100644 --- a/anyproxy.go +++ b/anyproxy.go @@ -94,11 +94,14 @@ func main() { }() } + // websocket 配置 + gWebsocketListen = config.IfEmptyThen(gWebsocketListen, conf.RouterConfig.Websocket.Listen, "") if gWebsocketListen != "" { gWebsocketListen = tools.FillPort(gWebsocketListen) go nat.NewServer(&gWebsocketListen) } + gWebsocketConn = config.IfEmptyThen(gWebsocketConn, conf.RouterConfig.Websocket.Connect, "") if gWebsocketConn != "" { gWebsocketConn = tools.FillPort(gWebsocketConn) go nat.ConnectServer(&gWebsocketConn) diff --git a/conf/router.yaml b/conf/router.yaml index 505e950..2eb4608 100644 --- a/conf/router.yaml +++ b/conf/router.yaml @@ -40,7 +40,11 @@ hosts: #token: anyproxyproxyany # 可访问的客户端IP,为空不限制 #allowIP: + +#websocket配置 websocket: + listen: + connect: user: 111 pass: 111 subscribe: diff --git a/logging/logger.go b/logging/logger.go index dcfee20..bc143de 100644 --- a/logging/logger.go +++ b/logging/logger.go @@ -38,11 +38,17 @@ func SetDefaultLogger(dir, prefix string, compress bool, reserveDay int, w io.Wr // ErrlogFd 标准输出错误输出文件 func ErrlogFd(logDir string, cmdName string) *os.File { + if _, err := os.Stat(logDir); os.IsNotExist(err) { + err = os.Mkdir(logDir, 0777) + if err != nil { + log.Fatalln("logs dir create error", err.Error()) + } + } errFile := filepath.Join(logDir, fmt.Sprintf("%s.err.log", cmdName)) fd, err := os.OpenFile(errFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0664) if err != nil { //报错并退出 - log.Fatalln(err.Error()) + log.Fatalln("open log file error", err.Error()) } return fd } diff --git a/proto/http.go b/proto/http.go index 8f9bff2..7fa606a 100644 --- a/proto/http.go +++ b/proto/http.go @@ -267,15 +267,18 @@ func (that *httpStream) response() error { if that.Header.Get(specialHeader) == "websocket" { that.Header.Del(specialHeader) tunnel := newWsTunnel(that.req, that.Header) - // 先将请求头部发出 - tunnel.buffer.Write([]byte(fmt.Sprintf("%s\r\n", that.FirstLine))) - that.Header.Write(tunnel.buffer) - tunnel.buffer.Write([]byte("\r\n")) - // 多读取的body部分 - tunnel.buffer.Write(that.BodyBuf) - ok := tunnel.transfer() - if ok == true { - return nil + if tunnel.getTarget(that.req.DstName) { + // 先将请求头部发出 + tunnel.buffer.Write([]byte(fmt.Sprintf("%s\r\n", that.FirstLine))) + that.Header.Write(tunnel.buffer) + tunnel.buffer.Write([]byte("\r\n")) + // 多读取的body部分 + tunnel.buffer.Write(that.BodyBuf) + ok := tunnel.transfer() + if ok == true { + return nil + } + // 请求不成,则走普通转发 } } } diff --git a/proto/tunnel.go b/proto/tunnel.go index 9325b15..10baa34 100644 --- a/proto/tunnel.go +++ b/proto/tunnel.go @@ -212,7 +212,7 @@ func (s *tunnel) lookup(dstName, dstIP string) (string, cache.DialState) { } // 查询配置 -func (s *tunnel) findHost(dstName, dstIP string) conf.Host { +func findHost(dstName, dstIP string) conf.Host { for _, h := range conf.RouterConfig.Hosts { confMatch := getString(h.Match, conf.RouterConfig.Match, "equal") switch confMatch { @@ -249,7 +249,7 @@ func (s *tunnel) handshake(proto string, dstName, dstIP string, dstPort uint16) // http请求,dns解析 dstIP, state = s.lookup(dstName, dstIP) } - host := s.findHost(dstName, dstIP) + host := findHost(dstName, dstIP) var confTarget string if proto == protoTCP { confTarget = getString(host.Target, conf.RouterConfig.TCPTarget, "auto") diff --git a/proto/websocket.go b/proto/websocket.go index 6946d35..1cb2fa7 100644 --- a/proto/websocket.go +++ b/proto/websocket.go @@ -8,6 +8,7 @@ import ( "github.com/keminar/anyproxy/config" "github.com/keminar/anyproxy/nat" "github.com/keminar/anyproxy/proto/http" + "github.com/keminar/anyproxy/utils/conf" "github.com/keminar/anyproxy/utils/trace" ) @@ -32,6 +33,21 @@ func newWsTunnel(req *Request, header http.Header) *wsTunnel { return s } +// 检查ws转发是否允许 +func (s *wsTunnel) getTarget(dstName string) (ok bool) { + if dstName == "" { + return false + } + host := findHost(dstName, dstName) + var confTarget string + confTarget = getString(host.Target, conf.RouterConfig.Target, "auto") + + if confTarget == "deny" { + return false + } + return true +} + // transfer 交换数据 func (s *wsTunnel) transfer() bool { if config.DebugLevel >= config.LevelLong { diff --git a/scripts/build.sh b/scripts/build.sh index 836a4b7..4155992 100644 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -3,7 +3,7 @@ SCRIPT=$(readlink -f $0) ROOT_DIR=$(dirname $SCRIPT)/../ cd $ROOT_DIR -mkdir dist/ +mkdir -p dist/ # anyproxy echo "build anyproxy" diff --git a/utils/conf/router.go b/utils/conf/router.go index 73198d3..e52a5fb 100644 --- a/utils/conf/router.go +++ b/utils/conf/router.go @@ -11,7 +11,7 @@ import ( // Host 域名 type Host struct { - Name string `yaml:"name"` + Name string `yaml:"name"` //域名关键字 Match string `yaml:"match"` //contain 包含, equal 完全相等, preg 正则 Target string `yaml:"target"` //local 当前环境, remote 远程, deny 禁止, auto根据dial选择 DNS string `yaml:"dns"` //local 当前环境, remote 远程, 仅当target使用remote有效 @@ -26,15 +26,17 @@ type Log struct { // Subscribe 订阅标志 type Subscribe struct { - Key string `yaml:"key"` - Val string `yaml:"val"` + Key string `yaml:"key"` //Header的key + Val string `yaml:"val"` //Header的val } // Websocket 与服务端websocket通信 type Websocket struct { - User string `yaml:"user"` - Pass string `yaml:"pass"` - Subscribe []Subscribe `yaml:"subscribe"` + Listen string `yaml:"listen"` //websocket 监听 + Connect string `yaml:"connect"` //websocket 连接 + User string `yaml:"user"` //认证用户 + Pass string `yaml:"pass"` //密码 + Subscribe []Subscribe `yaml:"subscribe"` //订阅信息 } // Router 配置文件模型 @@ -80,7 +82,7 @@ func getPath(filename string) (string, error) { if !fileExists(configPath) { configPath = filepath.Join(AppSrcPath, "conf", filename) if !fileExists(configPath) { - return "", errors.New(filename + " not found") + return "", errors.New("conf/" + filename + " not found") } } } From a25917da91cf76688fce994e4880e1c35bd7b7b1 Mon Sep 17 00:00:00 2001 From: liminggui Date: Wed, 7 Apr 2021 13:09:54 +0800 Subject: [PATCH 17/32] =?UTF-8?q?vhost=20=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + examples/nginx_vhost.conf | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 examples/nginx_vhost.conf diff --git a/README.md b/README.md index a134e99..0166700 100644 --- a/README.md +++ b/README.md @@ -173,6 +173,7 @@ sudo iptables -t nat -D OUTPUT 2 * TCP 转发的mysql的连接请求会一直卡住 * 与Tunnel的多账户认证,账户可设置有效期 * HTTP/1.1 keep-alive后端也能复用tcp +* 启用ws-listen后的平滑重启问题 # 感谢 diff --git a/examples/nginx_vhost.conf b/examples/nginx_vhost.conf new file mode 100644 index 0000000..21163ec --- /dev/null +++ b/examples/nginx_vhost.conf @@ -0,0 +1,32 @@ +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +server { + listen 80; + server_name ws.example.com; + + default_type application/octet-stream; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + gzip on; + gzip_min_length 1000; + gzip_proxied any; + + proxy_next_upstream error; + + location / { + include proxy.conf; + proxy_pass http://127.0.0.1:3002; + keepalive_timeout 65; + proxy_http_version 1.1; + proxy_set_header X-Scheme $scheme; + proxy_set_header Host $http_host; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + } + access_log logs/n_$HOST.log; +} \ No newline at end of file From 0c8bd496751100f6026ad386c98dc0fe7c70c1bb Mon Sep 17 00:00:00 2001 From: liminggui Date: Wed, 7 Apr 2021 18:28:22 +0800 Subject: [PATCH 18/32] fix bug --- logging/logger.go | 2 ++ nat/bridge_hub.go | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/logging/logger.go b/logging/logger.go index bc143de..db2944b 100644 --- a/logging/logger.go +++ b/logging/logger.go @@ -31,6 +31,8 @@ func SetDefaultLogger(dir, prefix string, compress bool, reserveDay int, w io.Wr log.SetFlags(log.Lshortfile | log.Ldate | log.Lmicroseconds) case config.LevelDebug: log.SetFlags(log.Llongfile | log.Ldate | log.Lmicroseconds) + case config.LevelDebugBody: + log.SetFlags(log.Lshortfile | log.Ldate | log.Lmicroseconds) default: log.SetFlags(log.Lshortfile | log.LstdFlags) } diff --git a/nat/bridge_hub.go b/nat/bridge_hub.go index 0008a69..8c68e65 100644 --- a/nat/bridge_hub.go +++ b/nat/bridge_hub.go @@ -44,11 +44,11 @@ func (h *BridgeHub) run() { if config.DebugLevel >= config.LevelDebug { log.Println("bridge nums", len(h.bridges)) } + if config.DebugLevel >= config.LevelDebugBody { + log.Println("nat_debug_write_bridge_hub", message.ID, message.Method, string(message.Body)) + } Exit: for bridge := range h.bridges { - if config.DebugLevel >= config.LevelDebugBody { - log.Println("nat_debug_write_bridge_hub", bridge.reqID, message.ID, message.Method, string(message.Body)) - } if bridge.reqID != message.ID { continue } From 4ae6db540c8438b342dd5aae1d8a12e18660436a Mon Sep 17 00:00:00 2001 From: liminggui Date: Wed, 7 Apr 2021 22:59:42 +0800 Subject: [PATCH 19/32] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E4=BC=A0=E8=BE=93=E4=B8=BAgob=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nat/bridge.go | 6 ++++-- nat/bridge_hub.go | 2 +- nat/client.go | 26 ++++++++++++++++++-------- nat/handler.go | 11 ++++------- nat/message.go | 30 ++++++++++++++++++++++++------ proto/tunnel.go | 10 +++++----- 6 files changed, 56 insertions(+), 29 deletions(-) diff --git a/nat/bridge.go b/nat/bridge.go index c814fba..1f7701e 100644 --- a/nat/bridge.go +++ b/nat/bridge.go @@ -63,7 +63,7 @@ func (b *Bridge) WritePump() (written int64, err error) { return } if config.DebugLevel >= config.LevelDebugBody { - log.Println("nat_debug_write_proxy", string(message)) + log.Println("nat_debug_write_proxy", len(message), string(message)) } var nw int nw, err = b.conn.Write(message) @@ -90,7 +90,9 @@ func (b *Bridge) CopyBuffer(dst io.Writer, src io.Reader, srcname string) (writt if nr > 0 { if config.DebugLevel >= config.LevelDebugBody { log.Printf("%s bridge of %s proxy, n=%d, data len: %d\n", trace.ID(b.reqID), srcname, i, nr) - fmt.Println(trace.ID(b.reqID), string(buf[0:nr])) + if srcname != "local" { //在写入websocket时已有输出 + fmt.Println(trace.ID(b.reqID), string(buf[0:nr])) + } } nw, ew := dst.Write(buf[0:nr]) if nw > 0 { diff --git a/nat/bridge_hub.go b/nat/bridge_hub.go index 8c68e65..b326c25 100644 --- a/nat/bridge_hub.go +++ b/nat/bridge_hub.go @@ -45,7 +45,7 @@ func (h *BridgeHub) run() { log.Println("bridge nums", len(h.bridges)) } if config.DebugLevel >= config.LevelDebugBody { - log.Println("nat_debug_write_bridge_hub", message.ID, message.Method, string(message.Body)) + log.Println("nat_debug_write_bridge_hub", message.ID, message.Method, len(message.Body)) } Exit: for bridge := range h.bridges { diff --git a/nat/client.go b/nat/client.go index 251ef5f..5fef07e 100644 --- a/nat/client.go +++ b/nat/client.go @@ -39,17 +39,23 @@ func (c *Client) writePump() { case message, ok := <-c.send: //ok为判断channel是否关闭 c.conn.SetWriteDeadline(time.Now().Add(writeWait)) if !ok { - log.Println("nat client send chan close") + log.Println("nat_debug_client_send_chan_close") // The hub closed the channel. c.conn.WriteMessage(websocket.CloseMessage, []byte{}) return } - err := c.conn.WriteJSON(message) + w, err := c.conn.NextWriter(websocket.BinaryMessage) + if err != nil { + return + } + if config.DebugLevel >= config.LevelDebugBody { - log.Println("nat websocket writeJson", message.ID, message.Method, string(message.Body), err) + log.Println("nat_debug_write_websocket", message.ID, message.Method, len(message.Body), err, string(message.Body)) } - if err != nil { + msgByte, err := message.encode() + w.Write(msgByte) + if err := w.Close(); err != nil { return } case <-ticker.C: @@ -70,16 +76,20 @@ func (c *Client) readPump() { c.conn.SetReadDeadline(time.Now().Add(pongWait)) c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil }) for { - msg := &Message{} - err := c.conn.ReadJSON(msg) + _, p, err := c.conn.ReadMessage() if err != nil { if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { - log.Printf("error: %v", err) + log.Printf("nat_debug_read_message_error: %v", err) } break } + msg, err := decodeMessage(p) + if err != nil { + log.Printf("nat_debug_decode_message_error: %v", err) + break + } if config.DebugLevel >= config.LevelDebugBody { - log.Println("nat_debug_read_from_client", msg.ID, msg.Method, string(msg.Body)) + log.Println("nat_debug_read_from_websocket", msg.ID, msg.Method, len(msg.Body)) } ServerBridge.broadcast <- msg } diff --git a/nat/handler.go b/nat/handler.go index a6b060d..658901b 100644 --- a/nat/handler.go +++ b/nat/handler.go @@ -1,7 +1,6 @@ package nat import ( - "encoding/json" "errors" "fmt" "io" @@ -90,21 +89,19 @@ func connect(addr *string, interrupt chan os.Signal) { go func() { //客户端的client.readRump defer close(done) for { - // 使用普通形式读,可以读到类似连接已关闭等的错误 - _, message, err := c.ReadMessage() + _, p, err := c.ReadMessage() if err != nil { log.Println("nat_local_debug_read_error", err.Error()) return } - msg := &Message{} - err = json.Unmarshal(message, &msg) + msg, err := decodeMessage(p) if err != nil { - log.Println("nat_local_debug_json_error", err.Error()) + log.Println("nat_local_debug_decode_error", err.Error()) return } if config.DebugLevel >= config.LevelDebugBody { - log.Println("nat_local_read_from_websocket_message", msg.ID, msg.Method, string(msg.Body)) + log.Println("nat_local_read_from_websocket_message", msg.ID, msg.Method, len(msg.Body)) } if msg.Method == METHOD_CREATE { diff --git a/nat/message.go b/nat/message.go index bfcfa50..d8a9438 100644 --- a/nat/message.go +++ b/nat/message.go @@ -1,5 +1,10 @@ package nat +import ( + "bytes" + "encoding/gob" +) + // 创建连接命令 const METHOD_CREATE = "create" @@ -19,15 +24,28 @@ type SubscribeMessage struct { Val string } -// 回答 -type AnswerMessage struct { - State int - Msg string -} - // 普通消息体 type Message struct { ID uint Method string Body []byte } + +func (m *Message) encode() ([]byte, error) { + var buf bytes.Buffer + enc := gob.NewEncoder(&buf) + err := enc.Encode(*m) + return buf.Bytes(), err +} + +func decodeMessage(data []byte) (*Message, error) { + var buf bytes.Buffer + var m Message + _, err := buf.Write(data) + if err != nil { + return &m, err + } + dec := gob.NewDecoder(&buf) + err = dec.Decode(&m) + return &m, err +} diff --git a/proto/tunnel.go b/proto/tunnel.go index 10baa34..e57a33f 100644 --- a/proto/tunnel.go +++ b/proto/tunnel.go @@ -67,7 +67,7 @@ func (s *tunnel) copyBuffer(dst io.Writer, src *tcp.Reader, srcname string) (wri nr, er := src.Read(buf) if nr > 0 { // 如果为HTTP/1.1的Keep-alive情况下 - if srcname == "client" && s.clientUnRead >= 0 { + if srcname == "request" && s.clientUnRead >= 0 { // 之前已读完,说明要建新链接 if s.clientUnRead == 0 { // 关闭与旧的服务器的连接的写 @@ -120,7 +120,7 @@ func (s *tunnel) copyBuffer(dst io.Writer, src *tcp.Reader, srcname string) (wri } } - if srcname == "client" { + if srcname == "request" { // 当客户端断开或出错了,服务端也不用再读了,可以关闭,解决读Server卡住不能到EOF的问题 s.conn.CloseWrite() s.curState = stateClosed @@ -148,15 +148,15 @@ func (s *tunnel) transfer(clientUnRead int) { }() //不能和外层共用err var err error - s.readSize, err = s.copyBuffer(s.conn, s.req.reader, "client") - s.logCopyErr("client->server", err) + s.readSize, err = s.copyBuffer(s.conn, s.req.reader, "request") + s.logCopyErr("request->server", err) log.Println(trace.ID(s.req.ID), "request body size", s.readSize) }() var err error //取返回结果 s.writeSize, err = s.copyBuffer(s.req.conn, tcp.NewReader(s.conn), "server") - s.logCopyErr("server->client", err) + s.logCopyErr("server->request", err) <-done // 不管是不是正常结束,只要server结束了,函数就会返回,然后底层会自动断开与client的连接 From 5985d887edecd4262b6a21a0a9f459b4e9bfe08a Mon Sep 17 00:00:00 2001 From: liminggui Date: Thu, 8 Apr 2021 13:37:08 +0800 Subject: [PATCH 20/32] =?UTF-8?q?copy=20[]byte=20fix=20=E5=86=85=E5=AE=B9b?= =?UTF-8?q?ug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nat/bridge.go | 35 +++++++++++++++++++++-------------- nat/bridge_hub.go | 3 ++- nat/client.go | 8 +++++--- nat/handler.go | 12 +++++++++++- proto/websocket.go | 2 +- 5 files changed, 40 insertions(+), 20 deletions(-) diff --git a/nat/bridge.go b/nat/bridge.go index 1f7701e..be6630e 100644 --- a/nat/bridge.go +++ b/nat/bridge.go @@ -1,7 +1,6 @@ package nat import ( - "fmt" "io" "log" "net" @@ -22,30 +21,39 @@ type Bridge struct { } // 包外面调用取消注册 -func (h *Bridge) Unregister(b *Bridge) { - h.bridgeHub.unregister <- b +func (b *Bridge) Unregister() { + b.bridgeHub.unregister <- b } // 向websocket hub写数据 func (b *Bridge) Write(p []byte) (n int, err error) { - msg := &Message{ID: b.reqID, Body: p} + // 先把p拷贝一份,否则会被外面的CopyBuffer再次修改,因为是引入传递 + body := make([]byte, len(p)) + copy(body, p) + msg := &Message{ID: b.reqID, Body: body} + + if config.DebugLevel >= config.LevelDebugBody { + md5Val, _ := Md5Byte(msg.Body) + log.Println("nat_debug_write_chan", msg.ID, md5Val) + } + b.client.send <- msg return len(p), nil } -// 通知websocket 创建连接 +// Open 通知websocket 创建连接 func (b *Bridge) Open() { msg := &Message{ID: b.reqID, Method: METHOD_CREATE} b.client.send <- msg } -// 通知tcp关闭连接 +// CloseWrite 通知tcp关闭连接 func (b *Bridge) CloseWrite() { msg := &Message{ID: b.reqID, Method: METHOD_CLOSE} b.client.send <- msg } -// 从websocket hub读数据写到请求http端 +// WritePump 从websocket hub读数据写到请求http端 func (b *Bridge) WritePump() (written int64, err error) { defer func() { b.conn.CloseWrite() @@ -62,11 +70,12 @@ func (b *Bridge) WritePump() (written int64, err error) { } return } - if config.DebugLevel >= config.LevelDebugBody { - log.Println("nat_debug_write_proxy", len(message), string(message)) - } var nw int nw, err = b.conn.Write(message) + if config.DebugLevel >= config.LevelDebugBody { + md5Val, _ := Md5Byte(message) + log.Println("nat_debug_write_proxy", md5Val, err, "\n", string(message)) + } if err != nil { return } @@ -89,10 +98,8 @@ func (b *Bridge) CopyBuffer(dst io.Writer, src io.Reader, srcname string) (writt nr, er := src.Read(buf) if nr > 0 { if config.DebugLevel >= config.LevelDebugBody { - log.Printf("%s bridge of %s proxy, n=%d, data len: %d\n", trace.ID(b.reqID), srcname, i, nr) - if srcname != "local" { //在写入websocket时已有输出 - fmt.Println(trace.ID(b.reqID), string(buf[0:nr])) - } + md5Val, _ := Md5Byte(buf[0:nr]) + log.Println("net_debug_copy_buffer", trace.ID(b.reqID), srcname, i, nr, md5Val) } nw, ew := dst.Write(buf[0:nr]) if nw > 0 { diff --git a/nat/bridge_hub.go b/nat/bridge_hub.go index b326c25..e692871 100644 --- a/nat/bridge_hub.go +++ b/nat/bridge_hub.go @@ -45,7 +45,8 @@ func (h *BridgeHub) run() { log.Println("bridge nums", len(h.bridges)) } if config.DebugLevel >= config.LevelDebugBody { - log.Println("nat_debug_write_bridge_hub", message.ID, message.Method, len(message.Body)) + md5Val, _ := Md5Byte(message.Body) + log.Println("nat_debug_write_bridge_hub", message.ID, message.Method, md5Val) } Exit: for bridge := range h.bridges { diff --git a/nat/client.go b/nat/client.go index 5fef07e..19c64a8 100644 --- a/nat/client.go +++ b/nat/client.go @@ -51,9 +51,10 @@ func (c *Client) writePump() { } if config.DebugLevel >= config.LevelDebugBody { - log.Println("nat_debug_write_websocket", message.ID, message.Method, len(message.Body), err, string(message.Body)) + md5Val, _ := Md5Byte(message.Body) + log.Println("nat_debug_write_websocket", message.ID, message.Method, md5Val, "\n", string(message.Body)) } - msgByte, err := message.encode() + msgByte, _ := message.encode() w.Write(msgByte) if err := w.Close(); err != nil { return @@ -89,7 +90,8 @@ func (c *Client) readPump() { break } if config.DebugLevel >= config.LevelDebugBody { - log.Println("nat_debug_read_from_websocket", msg.ID, msg.Method, len(msg.Body)) + md5Val, _ := Md5Byte(msg.Body) + log.Println("nat_debug_read_from_websocket", msg.ID, msg.Method, md5Val) } ServerBridge.broadcast <- msg } diff --git a/nat/handler.go b/nat/handler.go index 658901b..903c2cc 100644 --- a/nat/handler.go +++ b/nat/handler.go @@ -1,6 +1,8 @@ package nat import ( + "crypto/md5" + "encoding/hex" "errors" "fmt" "io" @@ -101,7 +103,8 @@ func connect(addr *string, interrupt chan os.Signal) { return } if config.DebugLevel >= config.LevelDebugBody { - log.Println("nat_local_read_from_websocket_message", msg.ID, msg.Method, len(msg.Body)) + md5Val, _ := Md5Byte(msg.Body) + log.Println("nat_local_read_from_websocket_message", msg.ID, msg.Method, md5Val) } if msg.Method == METHOD_CREATE { @@ -221,3 +224,10 @@ func logCopyErr(name string, err error) { log.Println(name, err.Error()) } } + +func Md5Byte(data []byte) (string, error) { + h := md5.New() + h.Write(data) + cipherStr := h.Sum(nil) + return hex.EncodeToString(cipherStr), nil +} diff --git a/proto/websocket.go b/proto/websocket.go index 1cb2fa7..b840d4c 100644 --- a/proto/websocket.go +++ b/proto/websocket.go @@ -62,7 +62,7 @@ func (s *wsTunnel) transfer() bool { } b := nat.ServerBridge.Register(c, s.req.ID, s.req.conn) defer func() { - b.Unregister(b) + b.Unregister() }() // 发送创建连接请求 From e849cf11a6a337b8c3816a4a812332bd6b01c20a Mon Sep 17 00:00:00 2001 From: liminggui Date: Thu, 8 Apr 2021 14:13:27 +0800 Subject: [PATCH 21/32] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E5=92=8C=E5=AE=A2=E6=88=B7=E7=AB=AFIP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nat/conn.go | 27 ++++++++++++++++++++++++++- nat/handler.go | 5 +++++ utils/tools/string.go | 6 +++++- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/nat/conn.go b/nat/conn.go index 2e88251..89d81b1 100644 --- a/nat/conn.go +++ b/nat/conn.go @@ -3,7 +3,9 @@ package nat import ( "fmt" "log" + "net" "net/http" + "strings" "time" "github.com/gorilla/websocket" @@ -119,8 +121,31 @@ func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) { client.hub.register <- client clientNum++ //这里不用len计算是因为chan异步不确认谁先执行 - log.Printf("client ip %s connected, total client nums %d\n", r.RemoteAddr, clientNum) + remote := getIPAdress(r, []string{"X-Real-IP"}) + log.Printf("client ip %s connected, total client nums %d\n", remote, clientNum) go client.writePump() go client.readPump() } + +// getIPAdress 客户端IP +func getIPAdress(req *http.Request, head []string) string { + var ipAddress string + // X-Forwarded-For容易被伪造,最好不用 + if len(head) == 0 { + head = []string{"X-Real-IP"} + } + for _, h := range head { + for _, ip := range strings.Split(req.Header.Get(h), ",") { + ip = strings.TrimSpace(ip) + realIP := net.ParseIP(ip) + if realIP != nil { + ipAddress = ip + } + } + } + if len(ipAddress) == 0 { + ipAddress, _, _ = net.SplitHostPort(req.RemoteAddr) + } + return ipAddress +} diff --git a/nat/handler.go b/nat/handler.go index 903c2cc..99d0661 100644 --- a/nat/handler.go +++ b/nat/handler.go @@ -11,6 +11,7 @@ import ( "net/url" "os" "os/signal" + "strings" "time" "github.com/keminar/anyproxy/utils/conf" @@ -35,6 +36,10 @@ func ConnectServer(addr *string) { LocalBridge = newBridgeHub() go LocalBridge.run() + addrs := strings.Split(*addr, "://") + if addrs[0] == "ws" && len(addrs) == 2 { + *addr = addrs[1] + } for { connect(addr, interrupt) if interruptClose { diff --git a/utils/tools/string.go b/utils/tools/string.go index 434425a..2c2962d 100644 --- a/utils/tools/string.go +++ b/utils/tools/string.go @@ -3,6 +3,7 @@ package tools import ( "crypto/md5" "encoding/hex" + "strconv" "strings" ) @@ -26,7 +27,10 @@ func Md5Str(str string) (string, error) { // 支持只输入端口的形式 func FillPort(port string) string { if !strings.Contains(port, ":") { - port = ":" + port + d, err := strconv.Atoi(port) + if err == nil && strconv.Itoa(d) == port { //说明输入为纯数字 + port = ":" + port + } } return port } From de749ba0b75a936fa8915d732ff4d3991025aba3 Mon Sep 17 00:00:00 2001 From: liminggui Date: Fri, 9 Apr 2021 00:09:39 +0800 Subject: [PATCH 22/32] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dchan=20close=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/chan/send.go | 101 ++++++++++++++++++++++++++++++++++++++++++ nat/bridge.go | 10 +++-- nat/bridge_hub.go | 7 ++- nat/client.go | 60 ++++++++++++++++++++++++- nat/client_hub.go | 25 +++++++++++ nat/conn.go | 4 +- nat/handler.go | 56 +---------------------- nat/message.go | 9 ++++ proto/tunnel.go | 3 +- proto/websocket.go | 7 +-- 10 files changed, 210 insertions(+), 72 deletions(-) create mode 100644 examples/chan/send.go diff --git a/examples/chan/send.go b/examples/chan/send.go new file mode 100644 index 0000000..bf85da4 --- /dev/null +++ b/examples/chan/send.go @@ -0,0 +1,101 @@ +package main + +import "log" + +func main() { + log.Println("test1============") + test1() + log.Println("test2============") + test2() + log.Println("test3============") + test3() + log.Println("test4============") + test4() + log.Println("test5============") + test5() + log.Println("test6============") + test6() + + //结论 ok 为判断通道是否关闭, default为判断通道是否放满或者无数据时都会调用 +} + +func test1() { + send := make(chan int) + close(send) + + select { + case t := <-send: //不用ok + log.Println(t) //被执行 + default: + log.Println("default") + } +} + +func test2() { + send := make(chan int) + close(send) + + select { + case t, ok := <-send: // 使用ok + log.Println(t, ok) //被执行,且ok为false + default: + log.Println("default") + } +} + +func test3() { + send := make(chan int) + close(send) + + select { + case t, ok := <-send: // 使用ok + log.Println(t, ok) //被执行,且ok为false + } +} + +func test4() { + send := make(chan int) + go func() { + // 无close + for i := 0; i < 10; i++ { + send <- i + } + }() + + for i := 0; i < 20; i++ { + select { + case t, ok := <-send: + log.Println(t, ok) + default: + log.Println("send is full or send is empty") //部分被执行 + } + } +} + +func test5() { + send := make(chan int) + go func() { + for i := 0; i < 10; i++ { + send <- i + } + }() + + for i := 0; i < 10; i++ { + select { + case t, ok := <-send: + log.Println(t, ok) //全部执行 + } + } +} + +func test6() { + send := make(chan int, 1) + for i := 0; i < 5; i++ { + select { + case send <- i: + log.Println(i) + default: + log.Println("send is full") //部分被执行 + } + } +} diff --git a/nat/bridge.go b/nat/bridge.go index be6630e..81cb14c 100644 --- a/nat/bridge.go +++ b/nat/bridge.go @@ -37,20 +37,24 @@ func (b *Bridge) Write(p []byte) (n int, err error) { log.Println("nat_debug_write_chan", msg.ID, md5Val) } - b.client.send <- msg + cmsg := &CMessage{Client: b.client, Message: msg} + b.client.hub.broadcast <- cmsg return len(p), nil } // Open 通知websocket 创建连接 func (b *Bridge) Open() { msg := &Message{ID: b.reqID, Method: METHOD_CREATE} - b.client.send <- msg + //b.client.send <- msg //注意:不能直接写send会与close有并发安全冲突 + cmsg := &CMessage{Client: b.client, Message: msg} + b.client.hub.broadcast <- cmsg } // CloseWrite 通知tcp关闭连接 func (b *Bridge) CloseWrite() { msg := &Message{ID: b.reqID, Method: METHOD_CLOSE} - b.client.send <- msg + cmsg := &CMessage{Client: b.client, Message: msg} + b.client.hub.broadcast <- cmsg } // WritePump 从websocket hub读数据写到请求http端 diff --git a/nat/bridge_hub.go b/nat/bridge_hub.go index e692871..6725846 100644 --- a/nat/bridge_hub.go +++ b/nat/bridge_hub.go @@ -22,6 +22,7 @@ type BridgeHub struct { } func newBridgeHub() *BridgeHub { + // 无缓冲通道,保证并发安全 return &BridgeHub{ broadcast: make(chan *Message), register: make(chan *Bridge), @@ -61,10 +62,8 @@ func (h *BridgeHub) run() { select { case bridge.send <- message.Body: break Exit - default: // 当send chan满时也会走进default - if config.DebugLevel >= config.LevelDebug { - log.Println("why go here ?????") - } + default: // 当send chan写不进时会走进default,防止某一个send卡着影响整个系统 + log.Println("net_bridge_send_chan_full", message.ID) close(bridge.send) delete(h.bridges, bridge) } diff --git a/nat/client.go b/nat/client.go index 19c64a8..2dbcc75 100644 --- a/nat/client.go +++ b/nat/client.go @@ -1,11 +1,14 @@ package nat import ( + "io" "log" + "net" "time" "github.com/gorilla/websocket" "github.com/keminar/anyproxy/config" + "github.com/keminar/anyproxy/utils/trace" ) var interruptClose bool @@ -68,8 +71,8 @@ func (c *Client) writePump() { } } -// 从websocket的客户端读取数据 -func (c *Client) readPump() { +// 服务器从websocket的客户端读取数据 +func (c *Client) serverReadPump() { defer func() { c.hub.unregister <- c c.conn.Close() @@ -96,3 +99,56 @@ func (c *Client) readPump() { ServerBridge.broadcast <- msg } } + +// 本地从websocket服务端取数据 +func (c *Client) localReadPump() { + for { + _, p, err := c.conn.ReadMessage() + if err != nil { + log.Println("nat_local_debug_read_error", err.Error()) + return + } + + msg, err := decodeMessage(p) + if err != nil { + log.Println("nat_local_debug_decode_error", err.Error()) + return + } + if config.DebugLevel >= config.LevelDebugBody { + md5Val, _ := Md5Byte(msg.Body) + log.Println("nat_local_read_from_websocket_message", msg.ID, msg.Method, md5Val) + } + + if msg.Method == METHOD_CREATE { + proxConn := dialProxy() //创建本地与本地代理端口之间的连接 + b := LocalBridge.Register(c, msg.ID, proxConn.(*net.TCPConn)) + go func() { + written, err := b.WritePump() + logCopyErr(trace.ID(msg.ID), "nat_local_debug websocket->local", err) + log.Println(trace.ID(msg.ID), "nat debug response size", written) + }() + + // 从tcp返回数据到ws + go func() { + defer b.Unregister() + readSize, err := b.CopyBuffer(b, proxConn, "local") + logCopyErr(trace.ID(msg.ID), "nat_local_debug local->websocket", err) + log.Println(trace.ID(msg.ID), "nat debug request body size", readSize) + b.CloseWrite() + }() + } else { + LocalBridge.broadcast <- msg + } + } +} + +func logCopyErr(traceID, name string, err error) { + if err == nil { + return + } + if config.DebugLevel >= config.LevelLong { + log.Println(traceID, name, err.Error()) + } else if err != io.EOF { + log.Println(traceID, name, err.Error()) + } +} diff --git a/nat/client_hub.go b/nat/client_hub.go index 814ddcd..fae00cc 100644 --- a/nat/client_hub.go +++ b/nat/client_hub.go @@ -1,6 +1,8 @@ package nat import ( + "log" + "github.com/keminar/anyproxy/proto/http" ) @@ -10,6 +12,9 @@ type Hub struct { // Registered clients. clients map[*Client]bool + // Inbound messages from the clients. + broadcast chan *CMessage + // Register requests from the clients. register chan *Client @@ -18,7 +23,9 @@ type Hub struct { } func newHub() *Hub { + // 无缓冲通道,保证并发安全 return &Hub{ + broadcast: make(chan *CMessage), register: make(chan *Client), unregister: make(chan *Client), clients: make(map[*Client]bool), @@ -35,10 +42,28 @@ func (h *Hub) run() { delete(h.clients, client) close(client.send) } + case cmessage := <-h.broadcast: + // 使用broadcast 无缓冲且不会关闭解决并发问题 + // 如果在外部直接写client.send,会与close()有并发安全冲突 + Exit: + for client := range h.clients { + if client != cmessage.Client { + continue + } + select { + case client.send <- cmessage.Message: + break Exit + default: // 当send chan写不进时会走进default,防止某一个send卡着影响整个系统 + log.Println("net_client_send_chan_full") + close(client.send) + delete(h.clients, client) + } + } } } } +// GetClient 获取某一个订阅者 func (h *Hub) GetClient(header http.Header) *Client { for client := range h.clients { for _, s := range client.Subscribe { diff --git a/nat/conn.go b/nat/conn.go index 89d81b1..4b736d5 100644 --- a/nat/conn.go +++ b/nat/conn.go @@ -117,7 +117,7 @@ func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) { clientNum := len(hub.clients) // 注册连接 - client := &Client{hub: hub, conn: conn, send: make(chan *Message, 100), User: user.User, Subscribe: subscribe} + client := &Client{hub: hub, conn: conn, send: make(chan *Message, SEND_CHAN_LEN), User: user.User, Subscribe: subscribe} client.hub.register <- client clientNum++ //这里不用len计算是因为chan异步不确认谁先执行 @@ -125,7 +125,7 @@ func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) { log.Printf("client ip %s connected, total client nums %d\n", remote, clientNum) go client.writePump() - go client.readPump() + go client.serverReadPump() } // getIPAdress 客户端IP diff --git a/nat/handler.go b/nat/handler.go index 99d0661..43c7217 100644 --- a/nat/handler.go +++ b/nat/handler.go @@ -5,7 +5,6 @@ import ( "encoding/hex" "errors" "fmt" - "io" "log" "net" "net/url" @@ -19,7 +18,6 @@ import ( "github.com/gorilla/websocket" "github.com/keminar/anyproxy/config" - "github.com/keminar/anyproxy/utils/trace" ) var ClientHub *Hub @@ -89,52 +87,13 @@ func connect(addr *string, interrupt chan os.Signal) { } log.Println("websocket auth and subscribe ok") - client := &Client{hub: ClientHub, conn: c, send: make(chan *Message, 100)} + client := &Client{hub: ClientHub, conn: c, send: make(chan *Message, SEND_CHAN_LEN)} go client.writePump() done := make(chan struct{}) go func() { //客户端的client.readRump defer close(done) - for { - _, p, err := c.ReadMessage() - if err != nil { - log.Println("nat_local_debug_read_error", err.Error()) - return - } - - msg, err := decodeMessage(p) - if err != nil { - log.Println("nat_local_debug_decode_error", err.Error()) - return - } - if config.DebugLevel >= config.LevelDebugBody { - md5Val, _ := Md5Byte(msg.Body) - log.Println("nat_local_read_from_websocket_message", msg.ID, msg.Method, md5Val) - } - - if msg.Method == METHOD_CREATE { - proxConn := dialProxy() - b := LocalBridge.Register(client, msg.ID, proxConn.(*net.TCPConn)) - go func() { - written, err := b.WritePump() - logCopyErr("nat_local_debug websocket->local", err) - log.Println(trace.ID(msg.ID), "nat debug response size", written) - }() - - // 从tcp返回数据到ws - go func() { - defer func() { - b.bridgeHub.unregister <- b - }() - readSize, err := b.CopyBuffer(b, proxConn, "local") - logCopyErr("nat_local_debug local->websocket", err) - log.Println(trace.ID(msg.ID), "nat debug request body size", readSize) - b.CloseWrite() - }() - } else { - LocalBridge.broadcast <- msg - } - } + client.localReadPump() }() for { @@ -219,17 +178,6 @@ func (h *ClientHandler) ask(v interface{}) error { return nil } -func logCopyErr(name string, err error) { - if err == nil { - return - } - if config.DebugLevel >= config.LevelLong { - log.Println(name, err.Error()) - } else if err != io.EOF { - log.Println(name, err.Error()) - } -} - func Md5Byte(data []byte) (string, error) { h := md5.New() h.Write(data) diff --git a/nat/message.go b/nat/message.go index d8a9438..f3ad184 100644 --- a/nat/message.go +++ b/nat/message.go @@ -11,6 +11,9 @@ const METHOD_CREATE = "create" // 关闭连接命令 const METHOD_CLOSE = "close" +// 发送通道长度 +const SEND_CHAN_LEN = 200 + // 认证 type AuthMessage struct { User string @@ -31,6 +34,12 @@ type Message struct { Body []byte } +// 普通消息体的复合类型,标记要向哪个Client发送 +type CMessage struct { + Client *Client + Message *Message +} + func (m *Message) encode() ([]byte, error) { var buf bytes.Buffer enc := gob.NewEncoder(&buf) diff --git a/proto/tunnel.go b/proto/tunnel.go index e57a33f..0a91b61 100644 --- a/proto/tunnel.go +++ b/proto/tunnel.go @@ -138,12 +138,11 @@ func (s *tunnel) transfer(clientUnRead int) { } s.curState = stateActive s.clientUnRead = clientUnRead - done := make(chan int, 1) + done := make(chan struct{}) //发送请求 go func() { defer func() { - done <- 1 close(done) }() //不能和外层共用err diff --git a/proto/websocket.go b/proto/websocket.go index b840d4c..2bbd4d5 100644 --- a/proto/websocket.go +++ b/proto/websocket.go @@ -68,14 +68,11 @@ func (s *wsTunnel) transfer() bool { // 发送创建连接请求 b.Open() var err error - done := make(chan int, 1) + done := make(chan struct{}) //发送请求给websocket go func() { - defer func() { - done <- 1 - close(done) - }() + defer close(done) b.Write([]byte(s.buffer.String())) s.readSize, err = b.CopyBuffer(b, s.req.reader, "request") s.logCopyErr("request->websocket", err) From 1d75a4e01086537bcb9d94af1132f898526c111f Mon Sep 17 00:00:00 2001 From: liminggui Date: Fri, 9 Apr 2021 09:22:15 +0800 Subject: [PATCH 23/32] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dclient=E6=9C=AA?= =?UTF-8?q?=E6=B3=A8=E5=86=8C=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nat/bridge.go | 15 ++++++++------- nat/bridge_hub.go | 4 +++- nat/client.go | 6 +++--- nat/client_hub.go | 12 ++++++++++-- nat/conn.go | 3 +++ nat/handler.go | 11 ++++++++++- nat/message.go | 20 +++++++++++--------- 7 files changed, 48 insertions(+), 23 deletions(-) diff --git a/nat/bridge.go b/nat/bridge.go index 81cb14c..da0cf21 100644 --- a/nat/bridge.go +++ b/nat/bridge.go @@ -9,6 +9,7 @@ import ( "github.com/keminar/anyproxy/utils/trace" ) +// Bridge 桥接 type Bridge struct { bridgeHub *BridgeHub client *Client @@ -20,7 +21,7 @@ type Bridge struct { send chan []byte } -// 包外面调用取消注册 +// Unregister 包外面调用取消注册 func (b *Bridge) Unregister() { b.bridgeHub.unregister <- b } @@ -33,11 +34,11 @@ func (b *Bridge) Write(p []byte) (n int, err error) { msg := &Message{ID: b.reqID, Body: body} if config.DebugLevel >= config.LevelDebugBody { - md5Val, _ := Md5Byte(msg.Body) + md5Val, _ := md5Byte(msg.Body) log.Println("nat_debug_write_chan", msg.ID, md5Val) } - cmsg := &CMessage{Client: b.client, Message: msg} + cmsg := &CMessage{client: b.client, message: msg} b.client.hub.broadcast <- cmsg return len(p), nil } @@ -46,14 +47,14 @@ func (b *Bridge) Write(p []byte) (n int, err error) { func (b *Bridge) Open() { msg := &Message{ID: b.reqID, Method: METHOD_CREATE} //b.client.send <- msg //注意:不能直接写send会与close有并发安全冲突 - cmsg := &CMessage{Client: b.client, Message: msg} + cmsg := &CMessage{client: b.client, message: msg} b.client.hub.broadcast <- cmsg } // CloseWrite 通知tcp关闭连接 func (b *Bridge) CloseWrite() { msg := &Message{ID: b.reqID, Method: METHOD_CLOSE} - cmsg := &CMessage{Client: b.client, Message: msg} + cmsg := &CMessage{client: b.client, message: msg} b.client.hub.broadcast <- cmsg } @@ -77,7 +78,7 @@ func (b *Bridge) WritePump() (written int64, err error) { var nw int nw, err = b.conn.Write(message) if config.DebugLevel >= config.LevelDebugBody { - md5Val, _ := Md5Byte(message) + md5Val, _ := md5Byte(message) log.Println("nat_debug_write_proxy", md5Val, err, "\n", string(message)) } if err != nil { @@ -102,7 +103,7 @@ func (b *Bridge) CopyBuffer(dst io.Writer, src io.Reader, srcname string) (writt nr, er := src.Read(buf) if nr > 0 { if config.DebugLevel >= config.LevelDebugBody { - md5Val, _ := Md5Byte(buf[0:nr]) + md5Val, _ := md5Byte(buf[0:nr]) log.Println("net_debug_copy_buffer", trace.ID(b.reqID), srcname, i, nr, md5Val) } nw, ew := dst.Write(buf[0:nr]) diff --git a/nat/bridge_hub.go b/nat/bridge_hub.go index 6725846..9e3b3d5 100644 --- a/nat/bridge_hub.go +++ b/nat/bridge_hub.go @@ -7,6 +7,7 @@ import ( "github.com/keminar/anyproxy/config" ) +// BridgeHub 桥接组 type BridgeHub struct { // Registered clients. bridges map[*Bridge]bool @@ -46,7 +47,7 @@ func (h *BridgeHub) run() { log.Println("bridge nums", len(h.bridges)) } if config.DebugLevel >= config.LevelDebugBody { - md5Val, _ := Md5Byte(message.Body) + md5Val, _ := md5Byte(message.Body) log.Println("nat_debug_write_bridge_hub", message.ID, message.Method, md5Val) } Exit: @@ -72,6 +73,7 @@ func (h *BridgeHub) run() { } } +// Register 注册 func (h *BridgeHub) Register(c *Client, ID uint, conn *net.TCPConn) *Bridge { b := &Bridge{bridgeHub: h, reqID: ID, conn: conn, send: make(chan []byte, 100), client: c} h.register <- b diff --git a/nat/client.go b/nat/client.go index 2dbcc75..6a35cb3 100644 --- a/nat/client.go +++ b/nat/client.go @@ -54,7 +54,7 @@ func (c *Client) writePump() { } if config.DebugLevel >= config.LevelDebugBody { - md5Val, _ := Md5Byte(message.Body) + md5Val, _ := md5Byte(message.Body) log.Println("nat_debug_write_websocket", message.ID, message.Method, md5Val, "\n", string(message.Body)) } msgByte, _ := message.encode() @@ -93,7 +93,7 @@ func (c *Client) serverReadPump() { break } if config.DebugLevel >= config.LevelDebugBody { - md5Val, _ := Md5Byte(msg.Body) + md5Val, _ := md5Byte(msg.Body) log.Println("nat_debug_read_from_websocket", msg.ID, msg.Method, md5Val) } ServerBridge.broadcast <- msg @@ -115,7 +115,7 @@ func (c *Client) localReadPump() { return } if config.DebugLevel >= config.LevelDebugBody { - md5Val, _ := Md5Byte(msg.Body) + md5Val, _ := md5Byte(msg.Body) log.Println("nat_local_read_from_websocket_message", msg.ID, msg.Method, md5Val) } diff --git a/nat/client_hub.go b/nat/client_hub.go index fae00cc..31fc51f 100644 --- a/nat/client_hub.go +++ b/nat/client_hub.go @@ -3,6 +3,7 @@ package nat import ( "log" + "github.com/keminar/anyproxy/config" "github.com/keminar/anyproxy/proto/http" ) @@ -43,15 +44,22 @@ func (h *Hub) run() { close(client.send) } case cmessage := <-h.broadcast: + if config.DebugLevel >= config.LevelDebug { + log.Println("client nums", len(h.clients)) + } + if config.DebugLevel >= config.LevelDebugBody { + md5Val, _ := md5Byte(cmessage.message.Body) + log.Println("nat_debug_write_client_hub", cmessage.message.ID, cmessage.message.Method, md5Val) + } // 使用broadcast 无缓冲且不会关闭解决并发问题 // 如果在外部直接写client.send,会与close()有并发安全冲突 Exit: for client := range h.clients { - if client != cmessage.Client { + if client != cmessage.client { continue } select { - case client.send <- cmessage.Message: + case client.send <- cmessage.message: break Exit default: // 当send chan写不进时会走进default,防止某一个send卡着影响整个系统 log.Println("net_client_send_chan_full") diff --git a/nat/conn.go b/nat/conn.go index 4b736d5..6ed9a48 100644 --- a/nat/conn.go +++ b/nat/conn.go @@ -34,7 +34,10 @@ var upgrader = websocket.Upgrader{ WriteBufferSize: 1024, } +// ServerHub 服务端的ws链接信息 var ServerHub *Hub + +// ServerBridge 服务端的http与ws链接 var ServerBridge *BridgeHub // serverStart 是否开启服务 diff --git a/nat/handler.go b/nat/handler.go index 43c7217..02f559c 100644 --- a/nat/handler.go +++ b/nat/handler.go @@ -20,7 +20,10 @@ import ( "github.com/keminar/anyproxy/config" ) +// ClientHub 客户端的ws信息 var ClientHub *Hub + +// LocalBridge 客户端的ws与http关系 var LocalBridge *BridgeHub // ConnectServer 连接到websocket服务 @@ -88,6 +91,10 @@ func connect(addr *string, interrupt chan os.Signal) { log.Println("websocket auth and subscribe ok") client := &Client{hub: ClientHub, conn: c, send: make(chan *Message, SEND_CHAN_LEN)} + client.hub.register <- client + defer func() { + client.hub.unregister <- client + }() go client.writePump() done := make(chan struct{}) @@ -120,6 +127,7 @@ func connect(addr *string, interrupt chan os.Signal) { } } +// ClientHandler 认证助手 type ClientHandler struct { c *websocket.Conn } @@ -178,7 +186,8 @@ func (h *ClientHandler) ask(v interface{}) error { return nil } -func Md5Byte(data []byte) (string, error) { +// md5 +func md5Byte(data []byte) (string, error) { h := md5.New() h.Write(data) cipherStr := h.Sum(nil) diff --git a/nat/message.go b/nat/message.go index f3ad184..91cc7d5 100644 --- a/nat/message.go +++ b/nat/message.go @@ -5,41 +5,42 @@ import ( "encoding/gob" ) -// 创建连接命令 +// METHOD_CREATE 创建连接命令 const METHOD_CREATE = "create" -// 关闭连接命令 +// METHOD_CLOSE 关闭连接命令 const METHOD_CLOSE = "close" -// 发送通道长度 +// SEND_CHAN_LEN 发送通道长度 const SEND_CHAN_LEN = 200 -// 认证 +// AuthMessage 认证 type AuthMessage struct { User string Token string Xtime int64 } -// 订阅 +// SubscribeMessage 订阅 type SubscribeMessage struct { Key string Val string } -// 普通消息体 +// Message 普通消息体 type Message struct { ID uint Method string Body []byte } -// 普通消息体的复合类型,标记要向哪个Client发送 +// CMessage 普通消息体的复合类型,标记要向哪个Client发送 type CMessage struct { - Client *Client - Message *Message + client *Client + message *Message } +// 转成二进制 func (m *Message) encode() ([]byte, error) { var buf bytes.Buffer enc := gob.NewEncoder(&buf) @@ -47,6 +48,7 @@ func (m *Message) encode() ([]byte, error) { return buf.Bytes(), err } +// 转成struct func decodeMessage(data []byte) (*Message, error) { var buf bytes.Buffer var m Message From d73243cb97620cd6283ba68ebc509167e3cec853 Mon Sep 17 00:00:00 2001 From: liminggui Date: Sat, 10 Apr 2021 16:27:15 +0800 Subject: [PATCH 24/32] build --- scripts/build.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/build.sh b/scripts/build.sh index 4155992..85d9215 100644 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -9,7 +9,7 @@ mkdir -p dist/ echo "build anyproxy" # for linux echo " for linux" -go build -o dist/anyproxy anyproxy.go +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o dist/anyproxy anyproxy.go # for mac echo " for mac" @@ -21,13 +21,13 @@ CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o dist/anyproxy-windows.exe an # for alpine echo " for alpine" -go build -tags netgo -o dist/anyproxy-alpine anyproxy.go +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -tags netgo -o dist/anyproxy-alpine anyproxy.go # tunneld echo "build tunneld" echo " for linux" -go build -o dist/tunneld tunnel/tunneld.go +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o dist/tunneld tunnel/tunneld.go # for alpine echo " for alpine" -go build -tags netgo -o dist/tunneld-alpine tunnel/tunneld.go \ No newline at end of file +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -tags netgo -o dist/tunneld-alpine tunnel/tunneld.go \ No newline at end of file From 9538e24648991e29b3ddf79980fa130b639d0259 Mon Sep 17 00:00:00 2001 From: liminggui Date: Sat, 10 Apr 2021 23:55:05 +0800 Subject: [PATCH 25/32] =?UTF-8?q?=E5=A2=9E=E5=8A=A0ws=E7=9A=84host?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- conf/router.yaml | 15 +++++++++++---- nat/client.go | 8 ++++++-- nat/handler.go | 7 ++++++- proto/tunnel.go | 10 +++++++--- proto/websocket.go | 8 ++++++-- utils/cache/cache.go | 13 ++++++++++--- utils/conf/router.go | 1 + 7 files changed, 47 insertions(+), 15 deletions(-) diff --git a/conf/router.yaml b/conf/router.yaml index 2eb4608..e8270e1 100644 --- a/conf/router.yaml +++ b/conf/router.yaml @@ -43,10 +43,17 @@ hosts: #websocket配置 websocket: + # 监听端口 listen: + # ip 端口 connect: - user: 111 - pass: 111 + # connect 域名 + host: + # 用户名 + user: + # 密码 + pass: + # 订阅头部信息 subscribe: - - key: devId - val: xxx \ No newline at end of file + - key: + val: \ No newline at end of file diff --git a/nat/client.go b/nat/client.go index 6a35cb3..02f275b 100644 --- a/nat/client.go +++ b/nat/client.go @@ -125,7 +125,9 @@ func (c *Client) localReadPump() { go func() { written, err := b.WritePump() logCopyErr(trace.ID(msg.ID), "nat_local_debug websocket->local", err) - log.Println(trace.ID(msg.ID), "nat debug response size", written) + if config.DebugLevel >= config.LevelDebug { + log.Println(trace.ID(msg.ID), "nat debug response size", written) + } }() // 从tcp返回数据到ws @@ -133,7 +135,9 @@ func (c *Client) localReadPump() { defer b.Unregister() readSize, err := b.CopyBuffer(b, proxConn, "local") logCopyErr(trace.ID(msg.ID), "nat_local_debug local->websocket", err) - log.Println(trace.ID(msg.ID), "nat debug request body size", readSize) + if config.DebugLevel >= config.LevelDebug { + log.Println(trace.ID(msg.ID), "nat debug request body size", readSize) + } b.CloseWrite() }() } else { diff --git a/nat/handler.go b/nat/handler.go index 02f559c..d6cc149 100644 --- a/nat/handler.go +++ b/nat/handler.go @@ -7,6 +7,7 @@ import ( "fmt" "log" "net" + "net/http" "net/url" "os" "os/signal" @@ -67,7 +68,11 @@ func connect(addr *string, interrupt chan os.Signal) { u := url.URL{Scheme: "ws", Host: *addr, Path: "/ws"} log.Printf("connecting to %s", u.String()) - c, _, err := websocket.DefaultDialer.Dial(u.String(), nil) + h := http.Header{} + if conf.RouterConfig.Websocket.Host != "" { + h.Add("Host", conf.RouterConfig.Websocket.Host) + } + c, _, err := websocket.DefaultDialer.Dial(u.String(), h) if err != nil { log.Println("dial:", err) time.Sleep(time.Duration(3) * time.Second) diff --git a/proto/tunnel.go b/proto/tunnel.go index 0a91b61..bd8f36c 100644 --- a/proto/tunnel.go +++ b/proto/tunnel.go @@ -149,7 +149,9 @@ func (s *tunnel) transfer(clientUnRead int) { var err error s.readSize, err = s.copyBuffer(s.conn, s.req.reader, "request") s.logCopyErr("request->server", err) - log.Println(trace.ID(s.req.ID), "request body size", s.readSize) + if config.DebugLevel >= config.LevelDebug { + log.Println(trace.ID(s.req.ID), "request body size", s.readSize) + } }() var err error @@ -159,7 +161,9 @@ func (s *tunnel) transfer(clientUnRead int) { <-done // 不管是不是正常结束,只要server结束了,函数就会返回,然后底层会自动断开与client的连接 - log.Println(trace.ID(s.req.ID), "transfer finished, response size", s.writeSize) + if config.DebugLevel >= config.LevelDebug { + log.Println(trace.ID(s.req.ID), "transfer finished, response size", s.writeSize) + } } func (s *tunnel) logCopyErr(name string, err error) { @@ -274,7 +278,7 @@ func (s *tunnel) handshake(proto string, dstName, dstIP string, dstPort uint16) proxyScheme2, proxyServer2, proxyPort2, err := getProxyServer(host.Proxy) if err != nil { // 如果自定义代理不可用,confTarget走原来逻辑 - log.Println(trace.ID(s.req.ID), "host.proxy err", host.Proxy, err) + log.Println(trace.ID(s.req.ID), "host.proxy err", err) } else { proxyScheme = proxyScheme2 proxyServer = proxyServer2 diff --git a/proto/websocket.go b/proto/websocket.go index 2bbd4d5..3cf1fa1 100644 --- a/proto/websocket.go +++ b/proto/websocket.go @@ -76,7 +76,9 @@ func (s *wsTunnel) transfer() bool { b.Write([]byte(s.buffer.String())) s.readSize, err = b.CopyBuffer(b, s.req.reader, "request") s.logCopyErr("request->websocket", err) - log.Println(trace.ID(s.req.ID), "request body size", s.readSize) + if config.DebugLevel >= config.LevelDebug { + log.Println(trace.ID(s.req.ID), "request body size", s.readSize) + } b.CloseWrite() }() //取返回结果写入请求端 @@ -85,7 +87,9 @@ func (s *wsTunnel) transfer() bool { <-done // 不管是不是正常结束,只要server结束了,函数就会返回,然后底层会自动断开与client的连接 - log.Println(trace.ID(s.req.ID), "websocket transfer finished, response size", s.writeSize) + if config.DebugLevel >= config.LevelDebug { + log.Println(trace.ID(s.req.ID), "websocket transfer finished, response size", s.writeSize) + } return true } diff --git a/utils/cache/cache.go b/utils/cache/cache.go index e0efb52..0c75fc1 100644 --- a/utils/cache/cache.go +++ b/utils/cache/cache.go @@ -5,6 +5,7 @@ import ( "sync" "time" + "github.com/keminar/anyproxy/config" "github.com/keminar/anyproxy/utils/trace" ) @@ -54,13 +55,19 @@ func (c *resolveLookupCache) Lookup(logID uint, host string) (string, DialState) hit := c.ips[host] if hit != nil { if hit.expires.After(time.Now()) { - log.Println(trace.ID(logID), "lookup(): CACHE_HIT", hit.state) + if config.DebugLevel >= config.LevelDebug { + log.Println(trace.ID(logID), "lookup(): CACHE_HIT", hit.state) + } return hit.ipv4, hit.state } - log.Println(trace.ID(logID), "lookup(): CACHE_EXPIRED") + if config.DebugLevel >= config.LevelDebug { + log.Println(trace.ID(logID), "lookup(): CACHE_EXPIRED") + } delete(c.ips, host) } else { - log.Println(trace.ID(logID), "lookup(): CACHE_MISS") + if config.DebugLevel >= config.LevelDebug { + log.Println(trace.ID(logID), "lookup(): CACHE_MISS") + } } return "", StateNone } diff --git a/utils/conf/router.go b/utils/conf/router.go index e52a5fb..9febbeb 100644 --- a/utils/conf/router.go +++ b/utils/conf/router.go @@ -34,6 +34,7 @@ type Subscribe struct { type Websocket struct { Listen string `yaml:"listen"` //websocket 监听 Connect string `yaml:"connect"` //websocket 连接 + Host string `yaml:"host"` //connect的域名 User string `yaml:"user"` //认证用户 Pass string `yaml:"pass"` //密码 Subscribe []Subscribe `yaml:"subscribe"` //订阅信息 From 592fdd6953bda1bc5f23819355bd4919c133e8d8 Mon Sep 17 00:00:00 2001 From: liminggui Date: Sun, 11 Apr 2021 00:04:32 +0800 Subject: [PATCH 26/32] =?UTF-8?q?https=E6=8A=93=E5=8C=85=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/https_capture.png | Bin 0 -> 283964 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 examples/https_capture.png diff --git a/examples/https_capture.png b/examples/https_capture.png new file mode 100644 index 0000000000000000000000000000000000000000..52ba75317f6b5f5f9be51627b5540c82d1bd4f36 GIT binary patch literal 283964 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�R@<)qrCrl< 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

SXgWm)j7HEE;c5S8Qs!Gnz zA|L|3DC)TIT0H*QV%vfC4$x_@C4AUvkE%wgl4?tBv{x$PapX;Z{X;~3(Auai*4$qU zTdwKKe-5ZQ(9SXXA1n98)K;2LJRqtvAE3Dz+1U3+ZYJb4l2|)^MUiuDg!)X%X4? zoop3Z%9cHQWZ(Bt*|(5=iDV~a9WyFx$k?~RlraW_$vPOr?;3UAxA*7s{``*b{MB)E z9GIEwdR@=g`8v<@(b<$?HsdniRpHGX`U34qq^Kp^EuAqK|1i$xMPnzJv)j&cV#AAN zcuR2rNJejZGGveK=i&%9?DweCCsT5{7i+m*5AlyRhP|+LnF}|gC_?Fpc>n?KQrA2szyFD<$t;rzYt{DzT=9;dS4(o-*<*x%vR9c z#>y=7D0D*Kk2j?kUGH;ni-$bniI-$}+C=-Fqwncrdy`39zmoswbKkS-^J%?SAI(WE zuGnybL3;lD1-#Owb_BK6Wv9D2)$HPhF|%>QR8XeOE-{x#!DmF-ohkjRNs1RH9GA;q zh{)u_dMyn-yg%TY{M$`F!PksF=Unu>?~NT-`Yjwc%Ij4l_5l-c8jY3Qb=4F>``}E| z0?+Q43AY=ik|lucyKcSd*z+6n3zlwST`~_5bh#iOkpeo0CikLE2WtWuADN`{G_wt< zWwzPtnp?znw-~6;+H;?0%XZ-eqJmV4+)$G5IV0xtbGGYHo2YV7MGqT2qj6Mm`25(uCTY%HLJJ1*|GC ze$q|D54chK3GVbCv_2b5y#zt`spoL_d7<-DZ4p~Tn8tiGA&;WzAAj(TIS@F1X9o5I zagKMq<-XY_K6phLb^S`%Pad#U@2zh+_h!xt9=^dRJxEXAZlJ^Iu^AeyOKdHbR*r4B zFsjriDD`7X)qqsG_3$B$G<(h)nS&up1A^uoaL2TxQ{s-;AUH4I(_?NH`1JZXYvsE1 zG}cmR*NcfHd!1s)4auF(i5*<&7^+b`vH$j({?PB&(D+mc&Ai5J;&iSOISu7{Zr+p~ zGZr}Iz_!A#maw1OE&1nyj8OcCTwoq)0w6vL=*N6`^V{y`?o$2s^hTWW^wJSLyahS-Nf*5|tu~x8E@0U+{NiR}K`)F63gHkZ< z(;%&RI(9`mJalwj4LF9doDuKk>>f~uCkRAWl6M6W?K_Y)cO!f_x9^Jo(*C}|3c!k{ z-T2Pr7~Z>D@|vQmO8<-U3hCeu>uzGMxx=lNjdRG6>mjzNKSY_+izp>`e&JOW7p)gD zCQX4^PaV5yrF)cE7#of}oR|Ih@_>3kNJ!(&warV8P16zu_fmICU+k+2r2IZQu&`vf zt|YuIepb-hZ{3KOK1Yl;c_Dm;G#nbRv>x^Y$EBzEpqxSHhyxn(6tQmRlgDqI#2KOud!K0puQ`s1BwH)amc zy>}L@=!u=niCUn)jTp$}ue&|vJHQH`iR)AMSMz0^yStuz3&u@Ly|s1Q`%o&pPWEKD zy9Wyy_!rqK1r9pH=l-+ml>hh8a;KeJ;7fFJ45hL>$ZQizF(Z=koP2c^O)W`L}D+8N$*xki{v&djBy{rxj*+1Q$&Y3sfla#f3c zY;{0_M71C~;z1zjFF=08XA%v?{OtwszAzi-PZ5t=CZ)b!Q`m&Q5Xo~>VvMG3z(IaB z+x+^zOkE!(U?_$yIu$KK&pmpASw7FM&&NS+;-2^Q$)9^R?%#VBF6kJNMY9N|yb{CC zlE5!t3&eIDahW~m4+s_=9euL*zx7M+m5szXe}++K0b{a>7)<$WrfQWUtrQY=n^vzI z_Pi@~X%TRZz>(4%=FKIuj0y`=cb%Pd#Bv>#tm712q$d~d24a5h``NjExqV=83!dXm zNLjUH0J`8yz>QPax7o**=B}=K?#}SzFXx2Ptq_@j?AMg6Vxz-fx!s!BJ*#3ZaL<_s zyz^Ihwyi)vkkUD*yM8T>n1zK!pLH#|@S)9>-aGKC%asS}^&al8HD_T>wLY6~tU3mi zm|+tRYzb$)y#N>QwB2PWg4<%lR-jRs^nY<>Rnf?MumRkiq ztj}w@wZ(3w7VZ~(d?RrP#xt~zb>JBC~#N$vA1{ZRGsrU&en(YX3_FJg*l zbhIP32-f;E4wmu2F0Ue;$TkSeyc2ejCF*fNzhG6t7ChrOQ!y_xcGM!4wsvGD(#j*u8SoFkOqTIN2(10IWss9l7i53ieBR z&ovSRoY||upLo5Us$#CYb~3Z!m=jdSvw+GN zP~A1>(0c1Fw#lA`K8LC*qF5HnlL}N)f@~^ zf5(0x`KihKBf?NGLM=&Ok=RmpT_(8x1!M`AKo+dC=Cf6H_F~S~mrKB|f8fa9;f!Mt zpaUvBly-mK2&kQdqO#9K*A3)RJ-3Fga#p>}YMi$;OMX1*kj=+1=e-G7O^7)GM~PT> zJx3gB!3|oGpLa6n(PYOgdmbFYT-tI#Ov=jUU3no#QRX3dJv^@M7|(xngmVE*c?4E? z;+9*{BSp|`7x2I52cU!@QP~`ZvvhNo%9&3y57x;$-X77~17yIl5aUAtB)$UQY-2sz zLtIua)H(dset8ebwU0r+VRmpq+x}n={q|H7U1uBBlKttYNLXLmQugYLFd)tDDa#lB z*Q#km*-ywtnS7Tl=Dn^Q2Yp>O%ctu+dn)ILX4Hm^aZk{Ww_{yaAYtcD;^aNcz@M$- z2LYbC!(0FtX&~lZ;Uy2zaB!-2=~oL-3sN znUdL*Ovz8ZPez?1`Zy2%G)-JR&fT^kvf8Zxlp+ctZ-!D%tqWI5IKmpzJf_Nap?v!8k*)57`BS z0BH~0&z5@De@D8twGJ8;dGGS6Bh<(kUy4fAt5JjVWFJBm+ah4^gzfM0iP#ul(xs!z z1Wr5;5aaXVgxGwHLgj~$=b9DSUz^OZdU7aeWNY5V^fYJJa>}JoJcPoL+j$g?fEOBQ z>S-;Mm-;UCT8;=LR}f;_=bBFlKVuc*!Gr|;$Gz)RcddihsrDOzeHeB~4bCc6vB9(c zt`Za3I>OsYqp^nbeZGDO(f;+nJq6;oSaa6bqJ;=5`@l}HjHnY?mVu- zljHhSZ}D!*TbjvAXu|Ais7QgsmWh4Il1-q{35}F!cF!7G*8i zA%La;RQqn9I=TV6<(k$Rfgsy6LJ3?B-#g(bpu#A>)=k-@VN69-Nbz=#{=`ELJx}8& z@a1n-ODrt88>6#VnOvLtRkj;we*&j@|I>9$cJzO_jy;3Ymd!eVDxO2Sz5Y44xj<{> zJN@!^;(}09Cb;f#?|1pt4TF_caKeCF$kIt*A0;keBl4Z&c-lSK?d|y!+wnQ*%bRXzFb)Im{(CKd9#~|!3s37 z#CvFl2!4kcY zYp=ON%8^8zMXeZSS7-7XiP<8Gt1r}r5O4e2B|!ANsi<1f?`OF#inOu6||UuUSp*N*(=2KyPN z&l)sLF+?rP_W?U}=Jz*7+PnIT0$o0jyC*#R8h3;#sau*cy(bDQO7#}}Iro`D#Ek3@ z8^iD(|8jZHK;;v^tKx(YlIrI#N_oxDNEObNV{^L3pSV6(Ops?h4l!!B@D&qBuG~|o zv&#_adYgC?wky*xpe~)4*$=AP)Z`_~@J_TtZ=%SEFkR{h(I9Ge{W~S(d6vlB)q$vg_p%-d;QWB66k?q5Z4icpp zk0?ze+y|vXT|D-Y6(nc?Y!v^LJ}7ql_4MuaMz%a*#l}46AgEW4o#i@dlw7^f_u6o2 zaUVNeaNwZ_9PR5JkTCYpMK0tjG%c+%0TBqpJOu88+b8z{;*^*cLwZBl7!=<+kVD4h z?)Fz#*H?_^%P5AsLI{)WiThe_$Vl9WkYnvSX6_rJqVV62*X0=lPI?hsI&%^be`tM6 zsOoSaNss&w`>@xY2fsk3Nj|?j1GesEF2e=__DrzdrQZ}@&V8QoV`br>b0#>4DP8~5 zIXvQ?b^|aDv+K@LDw}?IAbMG)pv=_z*!B&3dh*aXrD}rJRaqoyt^aCisxwdyADNO_ zh+Wb!k1rkSm?3R_aHcShOx88;7C$9LMtW>^=yP|bSn;sW*Jt-7n>M4wdahSmWYnXw z$ZeC=yr!H&%5XrwRYQBN*ck3%ic(uO&K3xgn(%=4`HuS~$aV&#$GhOc-}-_)qX{6C z1Bp|9fU_(C%$^6Zn69*+SNfa1^51|ZEJB=x}I2fj(+YvdC2UzEwO ziVYrY)^q92POe3Z?ER}2d<+`b z3F{{)Bkz<|(!%kx{ca(1@j?Y>Rdjm5;o+1Zo_HgC1;6+rc&n`@|~0S~x3-~&a;dh-i* z_gu>I({&el4kccv0u53|PQ`x1)swBS;@CB@ac`~SJ zbflUKEY3IiC z@6pmeVq{_D#Lv9S6^dW-2(nd}vbU7nO)2E$^Yt{NW2i{2-n?SCzhLQKp49Q)uTJy9P|VDy_^m*-xd^p;bVF;FbzhT8J1G4ned zM(ryyKh-rNm%FNAZCT5sZwIY1aBnQR+xQ|tW3U3lmvB`kRgImxeHM9ahglCwRvQ<4 zVeVHCTYsi+M|O@TiT3C`S+v3h+4E7#>Lm^DUT|_>=USpRdat0U)qDMX4_^Oi5zQH$ z=gm_1Z)k=s8v)-g`g^%1ZG}tEsq7%KyUESMRW(8C9!b5A0o3vnWD@QA&z1tn!eVMQH(>TX z2OyIeh(FxRpBXq)Cl@U(7PCjM>$(OBMsk+`m173QaL<*12dvfAzOC6k7R3OH9K0aN zQ=U&UhJLScM$&Kx6k@{n8Diu#^#H;a!q{%1mEWf*bf3P~bWM=gTP5+#je4Ms!H7Ui zQa(^Ifj6I16%nF;e&=mjynMhr;fO@i)}@Kd)1|0%Y_|cOwRE|#c5tw&-+nV`)orOT z$C)Z>ka;D^t&+%|tv&r^zO%W#zHr7tws<&Cnxk*}!$FYFzyKXCy|p&goM=iM6zrX7 zL?uw;GbG!Cs!qG#Wbu7SY4q`!-(VQ2tz%(S%9u}it33K}wW}PjyGS&4kc)0%J0T_s zCIZAHp)@tf6&0{lh;6IBE7FQUOd3Gt@5r=Z*fIqcDDI&<+%nM~FK707i&-PRDzlPi_aO1X1wji~(oSqzr`On+mlXqj_UA4dXNGqWmc+%~IT5-Ad_Wap zS(jo;fO4Mf3xFlwcHyiAXIya^?rSah1(S)t(X%K(`kexOMC%b~NoPt7#taCwq#{_b z;+2rnV{KoxBKHVTujr#r+Jl~V_?by5pdJROa_|X}Tfuwx9}wGJtPihft?s*ishJDJ z@R{A4i}clO!eyze0k^UkRQZxbb}6mz!-F`&463TDDvm0x=&Ro0UD24HB7Q-$FaVY0 zRsnYodU~V8d)%rZ^})~H-if0o?+cMerfCoAt)CCRFP6XT#An?n7&Eo$+nU27y#&4c znJe8cJgwV-l2X7C~F4YVUhFSyh2wl zfiy##X55@Rj<&aLEl&b;7J^k!E;ACp|DK9Bp!vC9JN7qrH-ZD^)x=`RO z7p4} zbFDu|%RnkDu3GBKEN?it8&oy(E$HfqH)KWJszI+AE1)Ih98+qmCI_*S=A7y0zN zavbs7Vp&y=@tIMDk%Ow3R0GJ$;A9lBFn0rk@`rA=x*xuQd(ijqhl4#Eu8sgTW>j2`4jBs`&>(3lDi4)k&;D!IYQ{VNA!&E$+>*Me(TvlLttaKQ7G_Q>w3(LOK5$bWl*$jpt`I79D$&bWk7HlfE{7?``D zd7H8+XN593#2*v9IxN?{1pmzR4g#9&Mt0xKNT zZRe+h%XVZZyv~sMLneL(o2+(iiNC<|c6%I8Vq9)X zcwo=b!}pDg^Mw!}hZ!|HzqVT(1)optFi7l!ZGI7wHvg^nRJmCN5e1RoX!RxNgOIh}Blj0mDLn|Z)fR{h*d z))Taj^HVn~Duyu6`MpgI;b=%}zHzim2!<42{-&BIEqLp(xsA)?{@4Yf*-57}Lya|C zxOeAhPdG`cR7p=rqwX&Ho_R1&54tzy(5UzMC23}}6HVf7l8L@x4-&CM0lt5>-ssY- zv(L`Z9m(mVn(43CCmIN?6srjx=PJwQ_KeS&TZq+5Sp?%>$?m$dkH~_l=U9ucK;Qlv z%kU!KFNuV`&q zQ3Y3%0%jlNCemlk=cTAHM6>RB{4XJ##0yr-K zlXQSZ;{_bXu{JP6zlIg&SO?1MiOSa!=*pkY=8;r5hT!R3)B}{b^-S8`KGF zO#m=Sva1vEQ*~Vm)xj9YiA0;@-e5!B#sqygY48Y~bw#YZW~|iLk5bfVr@QR1%YESE zMUpzbj-4M(04AxSu9d=cbem$+qkhgDi6T+i%y5lJUlC%$yQ=TMmLEVF=t`+)kc_bo} zv3@I;8XckHjY2GlV6t^d>OfrSIM!sM)Tj-m?=eP>j|@jKyM z@E=B!N{Y52EC1qC0wW21!bp;AYcltFw-;O_+~v7mn18~n_T=kAY;*%%tHJB;S15xR zL-0FvfTdM@AAWo=(e?`{O3i;%`XfR9=tX*hdJ$FT_nQRq`)?QPoZMmz4Pt{H@QSt} zQEi8tWWYa;zX3`&V1(Ul>jac;z!ghSx(OmUQ00n*MqzgRWj&>G0Hr(GP{#3np7usP zXYT}{bn_UVGrV!r^32{)&EvR9og$`nUZO`!-HoJ)hD=;d)Qz zr|u96UV&(xks5M+=Q;AaR~{HH=eW-|{{#(*BThbK0#%xLRw@C9gS9kn+ln^}dhTdKCNF0;)XQ zIkuqPIwKkB2R+2UVvmx1Atwi#qg79VSfA|C9E*<4%k0~E-5;0-q>oKTWXH7%qtmp61)&+PAp z1C%6AF!gqIv?-p}I!N9OqrH~#g=SBIm9@I5+FKygDJ00%KoL0ivD?amo3xIqNMGKj zwW=2lO_E-dm_^oMVRn3Q^pCBXH0OoZA%>syl{e2)Jt!JEw~mk0g=xheH$G)uTzZBE zC`s8?Ok5|Fq>cj0&#Q%occkPGZXS4rfXe^WE&rh;*`q8S?e=y5@I1^*(66hlG@RuIiI9TT*^_-v5?NU+7-pTz7wuG%W}qr*&bON6{+i0RO)3jJx#h zxEh3G84`Ao(WBwFG0kf}MmVXUOAQphPh^WG_HpdSgDJQ_*3PY5zFNFw&W|G)!-glW z)1n;5wbv%f`)Z;a*){4SisOv;3=t_@zG0Cn^zu(f1&o{?Bbyt9t?F{nf zU91l5bfy~OWcumZf3#Cu4Ug9|Kk&C)K6jT9n`J0brAurKN@TLiraJ$y-$hEA@lCQb zE((pv6BtC8AQZfEb^YY&XJ(zgw>uO>>iz8nn76m|oJqL3OSO#~lboCIjLq|3N|?)W zk_d+T-+J_Sjs1;Nq60CDH18*WEn_lWK%cZ70an=l*{sQwgSDEti@@4_}dlWHYdEKAV0-M8lQ(-hIn*p zb!!))tlq$syvUZ0U!TF;ti`@1MOd!7y>Gq{=9!{&&QmmNT=e^!F}NFan@_Cj*^C*B zcCa*fRYynoX;XQaY3~ivm#wHZwt5s;Ib}Lpyx^EEHGE~?XvciCJY%*W*mHcZ z3^p+X*|kRcf1v)-FD|vu65{~6bI=G9JDTyv{JK^g`WF9n1*anSz2bRWr!TDywHniO z9t`0GW&w4Tat5iv46!=nHZsp+<#pg6M2YJdtt6Mc%^J&4yFu?C!JluPheDlFRIbPB z%w@c#81wh7k6CG5v^#ptK*GH<_)Oldaxb?D(!iN-RL&B;m3J}6f2qG%aRV33wip!# zH!U~|Pz5G%nT6ymPzK5$0Y2ic!Gg3Z)?!BMFH4LP@=qXE4ySy&mm)h;zkK#njy2rw zW4!6#*ORRNQfVEqP!hc`0xXmOM;jb?ceUYlM@f9Ig3}piIG>KgHejKg06Eg&C{tii zcR?{mI=sfDA;c`e}Mj_S4qm?daQn zaeG|x74Dfyj{|%tBb(#` zrfZlkt+`0S(DpM(qQg}rwEyOB0nHf+7Yfy;Z+ZH}vIaB+z*s`#EwgWyXNCosZ<+)zDg@8#pELiYXlaum`3`-ag>VwdqRC(>EDI(r1wC7&S$pK?YDqJpesMXj=`w_C*9o&mQUy z0v;F9#mwjgjuWO)Gl6MT9q4uNuo&W?S^_uRF+QjFJ>i6D^j%e}+;I_^UU{A>s1-85 zF)R1O)^Ij2m-Mf`cL@jhLX!m=kf6+*PX#(qpZ&4x`&+F>PIE$+7F^brt|WC^2z=aR z_^Nzv|1M3=3iwX(QeJs^bTF&k8v)xOPnPaJ85ZfFeh!@kE8+;HOf? zsaDTtiOOfyYVTP#T{F0d@cecKJA|+0W3BdC${0^ec3k7|)~Z>D7k7`%@C=t@b4VUC z7v|6{Fx?KbThUVkIZlY*i{824&bVyB-#t2lGRV7OhXeF8c#Dst!Blb&3W1W_3Ltv)LG@8ZxojexSv}TVqVU z3-qx9Dqg@{f{Mp!t@}sC>;5Q1GpAEJ^Rvo5PPgWjoV&d|E5u4U5f74Q-3gWk%srH3 z1D0nVvmjII6Ab)IkUt>vEitV|HeHrQg1reRP!fSR3Nj_RR~U*BZHO5Pj8cb9!&r z++)4XqQ2$CepSLsK01bwzkVRQjZw$=J=~ER^B%$^umQ9Gr+Cr4l{BR_Jezlx+P_Gn z5t{_B8ucg4?XH*BB7pik2iY(PP|vdG>);{3{^@^YJ8Ob$2M~?o8=Kqw zh&qOp8wJS&*L|rykJpPPU9gDBp%t_Wv9oN-)0ega-zfnu*OJ#L;GKHbNm)##Ej2kYUNOZv!t&deKSaP#+|?UR+U3U(Zvd^lY%n4 zGm84(`rHmjIFzL~GB%n(G@?RuCv^JW?_p*LBb(3shDg!$ScQdE^z|r9n76A>nMvei z@QxBm#Jh_JY=|xG;wp5z)dvx}eTXZzl2ICWqJNddn>EF`7rbtj$aF^xvvn`4$iz?j z-toCmZpd12$ui@t!y1XQ>EdC~;Y`}5ue>1qy7@wlO?a;bI`c6+tvk~^Y;l$^uZ;zj zaXg@o#w$PyBmbosUF*o6z1LRg-k7N+)>mov=Xq&#%BCUy(eaG>9nX{pQ%KaNAqQu| zqrf$LispQz%m)=wS>}wh0^5elx&;4cnUw0jOdMQL@tkvUPR9Z*BZg1_I}8-S-VI7+ zddE7O0Bv~*zr@h7=OG=hzHKZM+*+8@G4CWNs8Vd!JvNqlXed9dxwl7eVgLGVcX?$@ z5jANuQ*uK>kV)29aP!x3K^-+_k~1UE=p@akmVZNxYEW5V<^0UhK;F>O)VA!)N*n}; zQI)9XM8dZ$n3U#P?Tfp%J3)eCW+YcifNa*o@&z?920Fw_22h+ikLv+iIVqpe*Xc}X}? zGQ@6_*`GA)|InXO|3h@cFum#cv~{k-GO`=dm(cHYDGK?f3T&*^N6u~lN+nB-d6khk zWxO_8m;`t|7ls)5$YwTi?u{RAK~-c@H=CnXkZx>Q=<2flvhjAGeTp7~JMrEJA2*+~ zff^#OLdBli#0A^od~3+i0keR^pcAKJ2DCOjfK-_reAfyhVh^wB;PAA4(jDVV+xV&@ z@QZl(8wW7${W+}l6%`N2Aa8=H!*xo^?uqNd2ca7C^u>{+y**NWlU!_-X>U?^34&&Q zZ(|!1;W_e<3V^vRd4tLmY`+buaoAhGapXty^aQJG43@3LJyvXBB~M{>PTPsIQwMe9 zYr2+0bzv&w;wegNVs&F}zM1AeFWt%<7)oAKE==zOPq3yt?;Ybt&Rxs^7)JJKtxpRY zDSI4JSee!uCG zc$GS_Zls=%d#=Kpp@-%xz5W<2FSxJKlC@3$wcxRgZg7|SlroQAYN)ooMm97L?ajPCj$1@)Bt7m4lg7_DTmldYq=>us;4lVc$>$$WV~Nqj1|0GaHYM-X-w;8WH2~fhd1op zn|lZy77HtWWoU?5ZB<%ModdNZ3p>!;6N2<>j}$3)?7sZAVt(-w-I(g`w`Zz|4CJ27k4u?E=Ac(pV6qVyWfN5n4!gf&`W^mU1t44 z%DtoQ1WYHD(XYD>i^utZFaKE^ux&tg64?r_Pb?DU_?dVejUe?B@==e!1mune$Y1Hm z{%iGe?4zr{w`#h)aF6L-;b}f|c;;~f4iEx;D22#GVmkO?z-dBM>+Drzk6bK^zi#$!RJGqMQ4g4A=2KG^FV<5V-#R%1N8N z^v?Zt(|Zr3`Znj2)!ogxl6Si%X6p(!yjLa3C&j}T{C%z3S@h~v+jurN3^|A=&w)ly|d`% z7{D=mLNV&UG5k$Np!Lh_XS`&gKcM;mum2FZqJWj_`#PDK*&^%g%?DF^yIyo|)>*Si zuUT8#jhHn82i(KNrKpJ0JFDa4!BTnPKwiye@N&WXrmbXSNZ%)!6`;G4yLA&-m5lLj zJ(>o9ssR;RT?WEH<@&i2e1-R~CqI43$*xRO1r<}70z@hSVhA;I|A9v~@gp%wzKKnf zV39ODaJbXh245?9BK2IZDbg4LS*S2?n3|D>0OYh`&h@P~^x@aG%%w7vzml>u4SFeV z1VV1lxX?{~=mXa@K^7bjNq%Dv^ky8|*NvnxJb7Ew>*I%PeMLIvsoR7ME#x#Jn#htJ z|L$$a$K0=kjbtf@Q6Z^2a(eu(--jVpNuj%fsc$CYIBY&KGQgLktd=^?!aP9aN_G9` zJq7k$*W=$kf;`N4?LPJBn|PpJxF+4ByPhS=$q|>_=`w|1Be8Xq zqu}Wa4hBvI%52WoL9)pf>USs;KQ4wxz!c63oEw_Z(b#A}UCY_WNRu8OMHW$Zk=NbR zEj90Kbv0l6?08WJ%t!hF%Fv19rHwPhz7$=LQ9-G}mD_?74E{kE=}iIXB2@rgq%`L> zcjZgkn#`cF2`uw-&wfDl$o&)E7SAb4rFi1)g%>&`;rni`-mcxEMz*BQUmc!m-04b< zn{xm8h_81Xm(JkVU6AhJ|8)-%?hm#P(sHp8e@trSEAJinu%1wNnxQD_(-^2ns5>>& z-TZ$2QhFuhOeHfGD^>Ej7w{?SW*3BjSJA+=6QEHY4iaXn0qR{#8{SHu9or}t)|3yQ zn2^?WF%nRc9CfxjqH6vmwHp)=Jgwq$gkw1ikyp;!_D_FFbq}{X?(x`vfNRbLkjM<{ zb3?1E`w}+xsFr^X3fh%l{LQ+0G{raH^qnz%2i?~|g$$_JU1+V5tBhi+&m6mf_xGB* z<;(b0B*51wi$fP(p2-5-Hvoq0-#RWAj+A>YYeK)oy!d`ZSx~3TqP`;cE))XCZE<5} zsepp^*DYEVI6*;+@%aBy(2$yfW~iop*S{4sxD_QpTz5^olmt)sD4e*m_ZYlhcLk*3 zbs#+9j;WJ)v)cUpGnYJn!WaKwXHL6?LBO^mjMmTsJ^_k&w-D=s$K*iNSVW1`oH+g0 zh0*~8Hx~9|-Puu`%k4U^)VkWR{{`Uf$D=;A0D_EgtXM%}N2{R78P4YD=xAUPbQ(n< zyk0!8`C%V2c2M?#uqbYiotn28eOEsXuE;v^J~w@>l>TrnJdO6t%@`(M(_V zW{%y&Cu*1rK7Rl+I-5Lx$wVyp(Eu>f?}u>=hg|z^k;2&Hd=4IYN#lTzY>4A?0d6zQ z`SyC|V$Hz>kGWr*8lfxn(NslX-S?_HdE^qyVwa_axL_vdD@S+leB$3UJ=g3Bx}gSE zwotEoR&QKq9hWEZpwAIx@BFE-=kbC8EhVd}U&f+(<3hlyX-A#Cx4AyDuD6D(-Tz8_ zQ`JI=nn!ua+X-TkBhT*A-aYA8HL~nWYuwvnubyQJ*a`RBe!#;wl>+6GkHKa(@>_8B z)gu=pE0q}~eYi2=yP&sNSN=p(BZgJ*^@FW|TF2Q0NBO#p+^>5h&GM}$a3YLQ;;x&$ z)E_tzVLdWK7UAHvP|o|4#HE$Si1CM4kJ}{5>;`~b;T?QBkMq8ieUUeSVzc#yf+LouzB6}s&}&IGh#QY_%3ZEC zBJQ(AR82U!j~j@}0_Ro*)u`fA&u~pGJ_zmCrK{z}iVDd-c#E*0<>lV@Sj_nQM{+gV z#$;GmQ@@>7eNl70;AwtRL@{N*fPl(bq&G$Sj?PRC1zxb^hkt&qw>Cj>)@)lIop}7= z1ye;{wH#kKXP1!cjnER(R&7?*qfM|m66a8_!?PcD=tLe4G6*EdX6|9`XX*7Prp;MG zQtIfS3Hp%h9n928SjIh2_UD8ukD|gsJ+(~i3aH@TK~A#0vzPf>t^Q!S8SlX za-1RNsd@VtZOiA8FBm8G5p#v!mI|Pq0HlBttJl1-7e398jH5re`zdGimN=B^@+5 zxc1FIYwyQu`BkgUUapv&EAR^Ms|eX#mBP2@G|*vjPVaC24D+hKtfQs3hP&=Ki$jWK z-jh7bq4&=k{PmQ_ilI4i>K>xhfxpQqTG?yBb5rv_^e@MI|3m-U_+R=L4!&%k;r-&b z0tQr%WV{cZ>hZgiGMRxoSec#}cpS076Ui)dH_P~yMKsj+V-h(08+l>znu-r)(_({1 zyxD;R0;7xAp6Q@Wir;%3$G?|x944+h_x`iJN8WNb$8fxWSfyO9xlUl{AS5eSYK)ZoKKa~DL6iYAidm<2pvUIA zy?Xn>ikHnt+E$+k{}5u%5VQobc0HtQnK04?-I%!@O_*|k*AgQoSkRUb?M2YS@jzDdUuTinrE8-8fDgCwjC-_yDBbijhm>h#N?%%+UNG+e-tZspUft& zYQtB|SG6ATjcxc0|Dg~m*wtO5Y?}JGSQ`2C(zq(ow@x6N>^Sf&|5?z}>C*Y5M+P5I*3}sj&%2Ss9bkFBr7^qHyG~_Wea$F#<`t|B)ACDE)6vVNj{(Te7C>U` znf=T?VF=fFAcv~F3soH$H1udi-EPY>>*<5kTaE*}=% z69!Z1a0`5)?gBk?;vd-1Wlg#@=DWY8D{=oFI4V`a9tdpva-NGSH04x5zyzh zNz0R`c=)-ekjC6N>1~YL03u3vWF+bKUrGrUWMc$sc9~TQbYzd2TKR{!l%|HW>?Vt$ z!YDhQF`aYNfF&mNo(uD3&((wIjd^dA^d51R%Y59*@6*0_?S$}|-90Tj8Qnh-ot#-2 zZgvb`c-jAMw_ZFp3}8vUch^OtE%CqJoExT7?E3?P|)0 z`qb&ZuRh~U3}eD7#Z_BJ+S<4omXoVG?)&X(Lwv+cAVt}z!L8p*`s;snb$JQD z)m|m4kpb#IA!4CM(d*;8Jd|V@Ug4J}Yx;aW-|{MvSA+t}bNoeyjrqf6%5G52UX)3K zr-4Le{rJqvQ=ocxxDyhlOkDK81Djz;pN|5BgDZa|BGry)s$K=^+i-!$aa$VJ{nO-+ zscV`dMN;*+YysU{1e`fe^zA`Ngiq9+J8h(9fM@H)E&O&$6k?gfrd+nxc>lxY`&g&w zj3ko=t<{DXOE8BT`Eq*lE#PrFcRdv7_i%0m9oWBRaymA2diuuE7|0Rp?W$RZ>F-D& zpc4{f%pqrD?9_ej)1IdD=7GXZImqo?zuM5xb^lsRKYq?mkEWCX%I{KVrt>2w`uDr@Yl`G+fAAIln)!hQDjSFH+r(^;inY40=e+fY~P>Ji^v zUMPQb8Nk4&V$idq(U^y$4$$;G_t#VX%GJK%q22=ezwqUK_6;gt1t0nki5J)(@o0_6b&q%u15ZFz)P$t4gH%@$Ff)rGgJWiFpBLZj`@L zjn~PoWnyaV>hJN?*N3O}HbF&J-5D>BOQ2t&oNk{@of{;Bb_at(eV)*PfrovFZz@#q zvW)nP`hXBGxUp5i1rt4DM{I=XhfMJp*#yCgg%Gx*qu@;=z=Z14t!AqAN~<;f3960xw@zP~qdh>zftpeP zJY__6+tF8-HvCNhT77&#ceIaU)#biQpif`fax2xnTQ}7K=tGwXQD3e@;k>`S0KiXz z!@2|6on%-L@Z+P#!$Tna`vs6_hog2k>_T)Mr(!s<3ABficqDgV)fmy$a)|#Oh(E22wyx1;4)c%)Y z%5m_CkFS>NWdwa|a9~W}@;2YRn{^$ar`_n_Z<4qRd@E1@YrXND7H~2Y-O00oD_+6y z2g1F81Qp6(YxKLp%<;UxAc=_jhL!;Y`V!9=xWD9K(YDrgYR?PsgjDKqVQFdmYK>8k zym4t|mQQ(Mt9;dHvAU`@-Nz51Zs63n@smze0OG8AQ!=Eh7grSXX5Rc9=(HKCY47}! zY6#ON-QeG1(T|lZRiV6aO3Bt%_>^r3la$Lj*<8IzgcC0m8Ig)@<=c4VgTtBDD0oc< z1yk}n@w(e&l?VEy83dVawd=s^andjoG}^CuqOD%n`-Gm?#Ref!(397_N43<_u@K>Y)ysI+ZHv&%#A_87agAnG`9<9d-gQ7?wpH$)%}m<>etUhv{&k;Q?JPA)?8|}0{~}o zVNDI}oDT$LfA*YXoiCgu+FT*po#TpIs)L9pW8K|==?npN%0w$w*`rf5^YY@)+^uddp1%^$3f@wr$7hoa8Oo!HflL6Wa&<^H!# zu!1F%Yvk0GqYg~(I~4AHVm@Oem9qG_)9qS}YQ_8$M82VC@b<%PG}aeC2=}`I)Pzgs zs+FzcWWt7wq_K%U-~?sY+;l7d7)N7jddUzBztr;iXMyl90q@*C$;io>xtXt7h)Cbb z@sMdej4cn^$}{p$Bh@m3VO@rDM-HWIaQgrSp%EQqz!UcZ*hu8kV_e$h`iDjokv_cb zs-V4RD7N0>t=uhF$C1Y0tWl|BS%>>As> zBr-q0i*kF3+oU;de6hYLX)*eQCE@9@`_47Otnjqnf{XTcU)S^I{Qr-$_l|0E>%N7L ziWHSDQlukAkPgy8kfI<>dX0hPj7?!!<#HwG(~0PAK*>==8eHpFYo0GQ4Am* z-o=SQMeAM|y?ftpBHe}(`{J{ache26^D_sWWdS#eFKvN3?w?Qg{T`Hf&PT346Z-h1 zp-de=$Z%o3_GcITSGv#21*T|2@4=(Fsu!PJ^XEH(S$(gGV^4Q|Y^djbcF&3{WDBLv z9poPj9p?`Fq=#*L?6pr7_Ey9lpGQGha@J&mH;hl-uhk9K*-Q$wAJKzgm-O+Si8Lf%QgjCef?J^=bp| zKrZJS@q2ZLNjTlnWQ|4oQ^fL$0BEBg20Rqap%Xn#gvXnp-YZue8u3Q?x_CfZ13I#p z0}6MEX;_y#6s1`L1=;vxcVy9LUjHiy$NLbbo^n%}mF9}|V12sOWp_k@b=P#q;Hf6r z&G!~DTaJb1`W3c`-x_u(ad=O>ID1gk~Hz>T0_zjt!Mr^8^xmjS><0PxXr z+#s$SWEI3X4)@1f$zwbRh5`(TuXs)R4%nNfG(s>Gf8eXD$^X z=8k+IBr7ZP+PpT1-h^Bc{H&nxD6aQHtgdl(hFF!pCm<4*mH+oN;5~|^p<7uIT=!g< zh}B!3lxAlBCeoddwYtRhwhUtnPxRCghC40!K_%#OVev^fH5=1{en^)$zxF}gZ;z$8 zQNlWC!xq4>yFx&NKg`!x6{!VfPWkNzWH#_3vg#F%Q4Hob05Lz2@k05F$S)-19ghC% z@aa08dVM9q+2p4GC6P`C{VrEY;D^JibotWR#di&B9;+O5MblnQblT*2gOhcpfmh9j zkb0Cq<(2CuphzY1#E2oI#uVMoaly``g7DqD3|n8PWS$%ZE#wMxY~70q7QMsq^??D3 zZCPz%gR*$h)qlNOCt98m4aGH_LAO18gDb46$M>H!o}XRX0bL%p`Dg#K1KMfiw_>^~ z1=s;0BVStDYiMvPn4C=Ys#O40I>T(bE3rtjEif1S3nuzV-V&2I?&dgJH%={D#IF#> zZ+HZ@w}cvip#`~Kx84sxCTMQ05Klj+Mwxb(_FcK;==r)pt<7NGSY*it0;3NNgCBMl z`dI`CpQdagVK9f8Q)nZKk>ya7QwM&Iwi0+cw4%h>d}6;wUMj^{_jY$J0$vRH(2r+uv;B8>xkwgJWSd39wZs zv3I&Bzd**bFfz>-ud3J1`RDbz#$C=&@fLEwzFyc|?ygf+dLSec<8RDs*WD3&JUWs2 zb*~AT6=I{7ALk}Cv~C#2PGk{Ia9tOWhMxZqkAvo-B_2#uH@map{h~+^R(WjDIqQBMmk&=&;^c$0t@% zswPq9yU3k(iVn-V<-6duhL-1zdR-W3D?2ctvIF%(_IFqkU?_ z)Jsyvd-IUc+%IK?Zn2tq5bE~EU*q%3Hm1FK0vZf*ZCDv&)WtDERuWV6d@+5)MsyoD zpp(K0>Vme@oe#{Sc%92UC=DbOd(GMK|&pIJ4N+q43`Z4lZL_~aXD z@ce$-*+)m-xzQQXPM&{&Q4|OjKkFFFrX#&OC9-9y)p{}>=ZeM~hnb_ckCQdP)PuKual1WctB=s=F%;nfb%2}@{k~7NWtWe0xLsN-5>`Uax`vz}cfZW$gMG_qeJM=yBMUwk6~E*di6*X_0`(5WDqe1E;49@cL@-uEX)FAtBWf{K4%{i(Mvw)Q`{~>)Yi%=7RU5 z=i^G^`->9T3VYnE8ROT?EDofi9ivaC{}>Jb5*) zYL#* zsM}*4P7}_L=opkeu#}b$Q^=CD7!Ge;-Z)b?Vo1o}3FVtw%H%#-z>!y2{zzUi27ujL z!v1N&I^gx=ZFtJUfmO2ly&!DVK$vk{$}K9-f!kMbfFd)tJ^UwZ(zAn>k`Yve(u z8{`4r5#lf6v{mqKQM{(tWqjzPJ^(iF-aiMsY?&WTs9@a`_8*cVEU+{7x%EiA-$3&P6~g@#bikc3XtjZFG_fI_ z%NJ&C4xsUlu$K0bCxINBju(--01@(nRpcL>xx=xGxE2_O$&?3`yDEXN!tnz|XWr|{ z+|uum!Prs}jqYwqJ(lA3Dk7N!%Xx_N(- zZhAYMQrX${xBmdzWGX`vkw(91l+jO|#nRLK*7%R?C1@mEb{0Oaz=ylnt8c7qQzFYk z!|KH2hg9}^(?94I21BP+1mld6CKb+^pengZ;%~tT4cXw!99&NyxNS5Y}xkwtg+{d^_9gfkjZhZ5QOx*pF@T zG>?Qg?ZoOE(Q&Wgu}#*n=*mxg&~q{l&Z1rJ&*#&eD- z3?7L}jp(}y$aM3TQ+=vab;N*~(Y|fD>0M#lzM~u|$eV|YR|<6(CI_~3*N$dkk}I*8 z#$A=|MdrRdNjaw80|ak`&vQh*fEhNX)3W;dC|8!r;V`wN#+bAJzpxHK9ev&6f5X#7 z$!{M2!PBun9Le|p1*c!Y(NEmr)INu3z+Wy+IzUjjcZ2&*T-UY(HdROTQdPybcm);8 zoS^YXB<=t5?%!lhUl+uWbMw=+j+2C}u1!#!pIQCoU{3tK-&pxNQzI|)`TAj>LmXUz z=(dw^pQl@q%KuRbt$N|=_XjtZUr+v;t~Y*{KaBZWf3E)ZjY-gyB|EiH!d5|KQF_8d_~{`f%{AMCdlI<5sx17$c$;>BZ2bU}C4);dEEg>fAZvv3c&7Ga^leIcio zY|1a~DXYzDg&`;9skh18iieHB3;_C5;`fr)+8@1oZ7-`;QfBv7{!3Vy5OX12w-eEK zMr9v~s6LTHrsi$>lFk1#8Bp`abab=Hw$+tjvhg8sU~A85%oDS3l*K?BMYcO zcyrH!kCvaawq_%yc;k+~ypR9(5^9dROJAvd*?nUAQ^eH~`<`wr@tRiSPY1IgPK-<8 z5!4=_iXsTYEFC$WNORtGhCP2vr#q{8x_Cui8w`?4$cp1n$5QEb-bbIduN`fIQc$|~ zj5qTA3Xdu$+*;v_K6FRfiyI%#YW1qQa4u_v^ppP3`{Jsw>w*6R#<*#N_`y;3Z#rq^UcgULOgrk;p3FFcZoAM z(jhtz8EgL*ON(?la?LaSf%S{@z$G=dR}JD?rQg{4Lued%^2W8 zbD#ij_LL8bf18ZGc-;k#B_ECT_8gO<8E1XM93sM4B1=B0SYqF+51X#6lx8h3CMfo; zyP%I5yn88zHu)olE^S(u8$VVgGb3leJ&`87{u03_WEQmnz17`d6h3;$YFUK%X4xj8 zF<&<71JU+hq>{fT4987Ij(^BkDcWmjGy>|b()EugK)0KroPR5&DKUWka@bp%WX$u(1Vs0~w>m{`mp# zVGd%2l~LuPi9)5(XZbsdX-3k?aVynhJDEnIOfs-RPe`zh!U{{M#tC^D6Vu^lV%IiV zPv>qP7t`-Dryl)3G(vU3HpBUGF9X*>UlTV^CF|}sNnTnGjpk=a!`=V`V$%5W&`$?l zz*q8h@q4Wz=Q#w6^}%t2C}I5tU;<5;Se*``yXzl#_m408v{4` zBy$OTX!1v7bIF}=erx37WZjDZ5|k@oIrQ^#nANl;oOQO{ZTgMos|l5Zb?h&6PqUvz zlhLK+(8QFgk!4@6FuKYg<@TJZXErxY3M26`Lm;#@k?^AJ>Cr`I9lcVs>VM)WeNX9#IA z6&0dF>mRRS=P#z!;YZPtmVYLpZ@&3g-Gbl#0sWXa++5~kFFdIEotMeudedtQ-)?Rv zD&Jju!qhP)(;3{;G3I$KvO_oJxLwe_I`Dn8A&-cOu@zr>KyPPe=5HaoMTR?mT=j+7 zrKUp@h1Bjbz>BHsBR>xFgmkA--kbxZJYc+M0M}Fxu-*i?rX+y;A|Ls~dmydxUY>i% ze)dNTj3ZpCtgPr!prMFG1UsB@TUvr&l{(sAQsYr9^9NII10^jNXE;JCe==<1)H=zE<+TwQf~Q~QftO&lT!85ZfKdEHDSyg< z`#Hz8^oMd2W)^`KCkqFd-~8Nt9sH)|(7#fX@APYtRL_2eDe|?oppi|LVS7!=*lx4< zLvnjbzd4_umNtP;021eBNvg2k2EAFu`sUNCTR&O2I`zwfo_4SK`i~sFOl17roqAH& zsurl?G3ld-w|tVTz3VUC`S&#P|E-UJY*tTE+h(1@1NsOr#DDh@EG(^b*@0FKS!ntv zv54Jg?UBSiuWjH{Tq%dVBR*tOH^zOx(nkXgIQKpvm_fc9H$hJk9{(Zpg3hJ!y}V2s zr-RveD>wz#=Gys0Q?UIfKoNka+tk$5&DIruSb5=$Lb=_QU<*PX2`3rZylnvLVCHz` zt8CJ{VFr|L(jSNz4=o5@*Gt>1E54h^edZpzw8HkunsBMFZB(i~{rR*H{p#zcj1`LG zrv0-jXXl4rUEX_?Kn^1o)Qd|FzOrCXf4ZM8ov-bZcllPr? zBy*&f15Bq0K2B2>rU6ufcN?Dv6Dyqay}!pwmi2#U?mpXkXy%0q9)$0I_nfzua387c z(E46lJ-U;*H6j~!dXshuo{ z8UGI#z|btvp=X-U{G_g~JG0y-fh*3%%p1B z)&TGh;MJSEcaDD$zjPCdTfKA>5^hwTrQ`pJYObo=P-Hf$oQ_?Q-lIV;Z#K@aZ~&vJ zM0^%~?`6t#`4Y6?327)r>k|qD?Hzra+O@^VRz$t@OL+eTC@Pg*0XLza#}^IV@vxF8uwQFQeDAw zO`UOuD=X+N_vOS6Ky^?$|AzkL-c^Aa)cvf6OD?O)@rIWLRY%&1UeN2gLUfCh!)b}OA!x+Rg8H-m>jm*!bs9S}dexooi^H^_dsxbJ{GDIMEb$mSpQPK%dDYu)iLsQYEOoMQy{H#( z|9E*J^5NgZD3_ne;!~NDj*XHOLYfc9x5QG;-}c^)4mdd)?^@$yv84U=`H`*z_PhSK z=}n@5YGx|!NF%=RzLP$Yk!yg$eXh)tGp37gZO5|ITx_HflyhoVZS^-}SL(#YDK8@GxrN3spnnTyCw3qTYQ_h>u|aufOk+3W$cm8SP|eI zboEhD$-UI0+TgMXu$)3<@dynw|i;N8B)S8)_ocOW0EEIFG{*Fa@mzmbG#6K_020OyOs3gs$LFHdj*0x znJw=)%{_(Z{{!`HuXsiZz`j-jtqglBMspLrwsJwkstk9!Z@lY8V z#kV;<_v@hzZl;`9#YzPiQTM(m3(+dyvtY&>HFz5ot4Ssm<9*%YjQyPLb-%#213$9w zQF+aj%er<^iPB%a)^nrF;_Y*hZ`+-Yg2T5}7<|*-i~qI`I=KF4xe`=f`GpsKox9IF zr*_>ncJ>Z=In4~A!!b*6mSun^?EuT|<2Jpm~!;&4FEiX{3_@jTUnLgO) zQzq7iNZ4Jj$)6k>L-(&WVY65|uOaWy8Fy3(7YiOW|3kg?`}z;{w(gR8do%K%xsQ}m z`nOZMig*8pUh#R?E}_>}3f8mLOXzi~`C63mOe+#$9*Xgli@a_O8ur&N8s&KWk)d1h zMek!;Wn27F`j_JLq2?+j^vJl>$lr8afRUu_rOz zFbkqsQ{^nS7{{eGA6;?UQyn~CVZT{5}MS>|KOnZaxiOE{jJ41fAmTJpn*z{7FaE%hnKtdF`1qOs*Q6V#Lix6_egjrq>{y7wA7iE7q$FdvNi!D(a)ge-xZ zkF{^J1par5uV=lY zTW7RFS*`44-q!@!zA$cVsAG^DG@O4lHL2k7B$x%c6B^B2@&4CdBD|(Kb@8>S-N5Yg zp;@f)(VbMf_YZ-zy$}7KtofzFu+`H}#bKkT^$3IJNKNC+vA0^=lg6 zXbJ_T$QZ^i=FaB%n|k#Ivjj!;n!?XmP>sB z;)W^X&ZFhk9>bDSI`omua@oyB1m)%8kU1{Y*j~GQ@%uZ4?h9H@U}YM2cMTD!msv!C z6jLO8CyT{_L=4~PQ|e0xiHGkEeP!((svd-UdL#ycb+m(KuH*~vvgqq|XWksd(>6#1 z4SszL(G&T)|A|6&igvRs%xA1{@cOcWbybplHuS}to+XAakvXSP!;XhetvpvEKcRKR?zQ}Q_m4$~*#ba1n zg?rmjS$@NLRg1R9Z6~~&_5WEpu29zhEE{2cmmu4m;!r95m7*d-{D5W^cb=ef-qH~I zp@byab~lslce>4D)InuN0G8^aoi>k8MQpwLs{!2*I^iy&_bS6OjjgDUyZL8%sba0! zynnyLBGpXX<6Tu^<>c$_4nf~r4at>9^YxTFIMh1r(Oq4U%O8CD%QPAz#rtO;U{X4# zov747^z|jSgZ||84pfTdLnWFaM2h`_x*W87H@q^Z_K<2tvSLpf8s7!WTdU&j|z z7wli#A}K5paIL3Do}QwkmXR%ALkV-J`0t*aY^X)J)qQ+EkpeP`X=+y~eyBSGRUmO0 z`z7M2x|;jm9(MTkg~MInH4%lMBjLkM`oFq5w2mJ<(`*y71_>Xqyt)r6B?6WoZgA&2 zvhsfQ8Nqd~&sPT~sreH9spQiwizi>XYl+*&s?AKSRe{@%XX6WP=(R0LZT0e0#*TV5 zw1a`}xYepiwZp&YHfM)0{>S>=m-G;tF8@F3d_B8FGe?N%! zyI*Ca{@%!hEUT$1tk!{K+(Bes-qkvH{rtbGU-?Mi>rq*iJ+4YjuiIFpnHe9uqTd}^ zXH=%mWu=xurG7r#y`2)PQCk)TIJZVPxpJS4c+lr1bCuY{k@c!KN@CvFrvV#k&wOH?dn7nZJ>ozgF4&*ik3C!r__Y z%%*dLNp_&$L(-kNm{I~Q;hD)2O+1Q|s$+q`L&h=hl$TT{%4T-r5wg@3rI~p5Uv@I| zm3@P|1q3t1^LUj@d{Z4Mf6ZU}@n~-@5gc#r_!=2rU$X9HrWEp>?BLOj*(Wm_B@zn; z1AMv`{FA%=z2%FvU*4@)(juUU6O7E5$x3H>x{>P3qn1YZ5T_k?Q>H1E0W($@(e8D$ ze*xEup-1y#ZaSP_a!s+k!61^Sb*bgNJ8^P(P4XgV?_7Ld9=fhFP?y^;G@*PS)cTa~ zodD}p%S*WmqYb>cFhbYPGgO0>{pbiOBb^7kVz9^NSAWs>g@UJ(oc`J?UK^%FHwaDx zq`JSElMEO%|24JFr7!(y#hV6Hqo388){t4f?MN?1Gje&uIeNoVn z=r$?=f=XB^sa<#uGif?XLFmf$AxP6OJ2n+fP2mKL-hVkm(6i&{)oQ!yP}Q98-A>Px zNaAkZRq5`({c;Uit=7$2fp>i=p0I($m-R0yGaH_uDp+A4!K$Yv(IQPjZWIM8$Bb|Y zC^p#InU-qS_oqF%$lfa(3L=U>85ke6ObyE)7-(MJn{N!it@KZBz7wX1>VEJjp(@{# z$mpK_SV5RVDHE^Cuza48mHQXpcT=7*AM#o7Su4jeY%u}38JYLjEV&$%Vx}cFb2hV> zLw`p`bTYA$_J7kBuz;>iEI;xC`Z;Jww-;7la8}w1)jj?)o9oY~zkIW+WsHc_Q^>VM`#T)Gg=Pw+sseY~Zp2s66SXZ4 zu(ciQ0RO|DV0`=s!-&!sY@h$4^D1|18MHr@RBL6<9H zADSNXbKuqDFW>MQI8l|3S9unTyQ`f+EmP%GUR#xCK!xKy%7gbPt~Hw|cMpamsqs%l zN9!IC#P#-5{dl(Evk1;BOjmc6OhlyGj-(tJ%udgTT8}}N#1TyvRn1OSj>qhE%~7s% zVrQ;-0qakx;^wDMBXWK{ym43MT4tSL1>vioo?Wv=FwKEi2F1Z|-?}S?Yh)S0!_GS6 zzf=^^t>w{fm?o%HqFH2Zj@9lX78M>poXI%%U-obnf>R!$?8 zD#$~a9cZTQ9?X26ezP(ti9X6ce-*WZ@CtCZyYH}Y>oOugWBFxC1Kid;4FNpZ z{T1#AvOFzGBIeYAmr`{iS`Ur6*cim+?Z9SnQbgI> ze2O-unj=HI2&OD^PYQx(H4-7c-7McC!EK5+&mE)ee=oflQHtkU3+x+7)_e-7Pj=y> z%qB4*CP~w-i){g?g{D2{vV|Pxnp(@syG<|n_%&V1zsPfZkzdQMv~{yq{#k~HE5G2^ zqic-3Qz7eK-z5F0)2?iSTvWXl^UN6{F+bh@w&3**j|p}3a}*`B!?Bn1%~p2j9m+*> z<+~e{y>-)^Hfx8>mPiAl$aBCeijsnFrH%%kpf;}PDy+q)=g1Pvmqi1Q3iFssQ?3^K zgeqj(a=lWqcSbzs)fFzl3b#7dxKW*3_3{QW{w`pBMIvO+C8{JYn#`=asCiAiVz*K6 zp7KS@PwY+(*t#BE{jf-XmfcczIbe6dvf0PyzYj?`LUTI5HfcLjlSmyz7_+(E?=f(# z+P{*@^hCcl7>#{^m3|GGD004z)h8;@wOe(ek{|PkTm;Vi@8h|79t}n+<`5_+5O3iB z5hP4x>YeYus3J@TbG)>C{&8j$ox8HzLvEWgBn_rT=$~; zvi=i}MH;<+<+rsss|ctkziHW-X?P^b>4| zifG%F-4Ye*G7{~INsaCyN)#J@@R1ecV=~{%5=r;GCYZsxo@}J%n!SWp8QmlWQZRyJ z$@!L2i$rny1UdN=+-JqtMotDck?Ep8HRH;8$QAP95!Ec{y_YqG7cG0aPDq=w9i4~x zwK2j;rPFNr-^7L-^j%)LRr?f+@gs!Iqtcup)OrJ=oeN9C>tmrwRv}VBS(Worra16o ziJazang`szb}$0nx7EF3H@7#a4uk{a?{F|>x~)Y1-)NZQJo|B%O8>_bWJ`TTpT#$#as2@b-F;Y@`pXGUWMDNlI%(9LOX9~-b9-cTI_vVK!^y-ZZ zD2YZS{zr#pf(RD!uj!3)zBXxs_;+9GJg{tpg?v?i?qOspS9Y>~0`4q27~Mr=_Wt~_ z?Kmf3^Gf>^EQT$vlp9=e`$|%aar=_&Ardus3S|4zD1tkVE$V<9px^6Z4j}uJLl1^k z?l@lk`v|%9#_;|Xo(;C3P9A?INk`#~&mQ7K6130C?*;Kv^D^@V1vlChlBArMeh8h2 zWeBBM`ypF&q0bktzeVCXn90>YRm$hIdvpW-7wj7!v!_$ouPefa&m7O%nR4G)@G}UR zyK5v1m7!85O1Xut7Ob}rA{HH%*}i#iU(++?{bXX#F%lb(SG`Mhns|Rmh``Lml51@3 zk=BS^VhsN3UzIP;wEuEktyG!fS|uYzWebVe`@CwvOz_$;CFE;!l=RZ%+?p*!&A-PE z2IQmINO39B1>pT3;Q6SSQqQ9>>%fZ37J_)AQ4r!D|C*FQiY;)&R%zDV2Itl}w|@)vkE=z8E^q zCElMOmEQ28le|3RNrmjmeg@K~j+Vz%WHlaDlUI_;#qleq^aK`YxxCoc~C(i?WSRCt3|N2By)T|f+?R}jd`y*+I zZ2i3z?Q0s(*zl`C0c%%9lL0U9qi$K9uT-5+u>;S7BF)i#)ftL1dk;y-F|ULKI$^;P zS=g7~DV7}RBk1WG=Q%f&1ZpBoB}++4yHH_=u##_yKe)y{WZ5~kTeORWIU2qyhN;F< zPPGvo8rk?hJJ6*MiO}0Ar3r(^q#)kceitRdg&tHohQ4xq`i{bPp}1pb5QCmge+2lY z-j|Cu-pwr&E9=s%g@tkFg?TXP`qsj>y_s%TA<>m?rXugc)(Br2J;u}Su@X6icbDGQ z(nJfcZtKuONmE~rPmz9p63jKCuQ5#_E9hhbjtOgspZ!s`FK7@F~6TxA+>5CrbtJ`W)_*eIKf2uVEK?aKz-wgN2Mug@%3&)qL>(_S%Xhg)1_|Y zxkaAx#6xrLpQ@nabQ2A7bykurNMlg*wJ9CT2Wl~v_iFTA1j=lchY1zzOx4rk^9fb8 zKAYf^po3aJ%W?|b{+Qu4WEi~sVzXR#64xIvAnrHMMW$k4XnbVKR2yf6e2O4sF)L%6 z_vKqo8hcmQS@2CUyCRIzoiN|e5A&9{Rfmq6@;dQnf1tVcvTRi0B+}DO_+@U7+TGm~ zw)n3WEx~x(iQb!gYZxy&UdYIPBK{$Ky3|&_-NEYia1Bk<)6rHQnZ03 z_;$Rf7`YnbeKllN=x};ZWB+;Y>Q=AT4l z$}R%vQ@!NxQ?b6H32ufkt|GGZS&0yH3I~hz+~IWk02XV-zdSOdjmj0Z56cp+-WSGn z<41lUh~fW48eL|b#xr}*n36-qKFK;Q)FK-1&0+MathieI!7UZc2Cg3&W1; zc9JRGS_9zAA4^(N*1Cl49E ziLc%f)`?akGm-rET0^nvdwq>%@gQ?%4}GsMXJw9Q0~L0e_w%{UuM5x39}m#kwc)v| zNxFMv+Hr*xdZz`cpGd#Q4Q2*tbU%v{d-%Qk<@IVsBPF|f5+;fM+`pa*>g|QLG!&FX z7qgAI-g#p-iFF|V@j~(IXaK`%LK_!n4cX2OcIVNzM;3qX;^soL{)(k~ljK3!=5)_01nbSr=AzQ5^B>PE{>6#HE+x|Y?=DCXAWQFDX@R3sg4dgNQ%eQ_ z3l&;WQ~1ho#udpvLO$M~HvSEn(XWo5DD7(p{dn3I{AH|~P~d^CZixe9U+2YoO(Z^R z>@&+$xaD5)+0x^iP9JpgMSME_?-0^TX=l7MujzW!GL?LTi;j9Gu2_|5!!ynF9>vpw z=W4ftoo`!?4BXeXlL^lrGMNvhv3x^7JQg$HxMUkXq=9#hn)m6LyG;J3WTN_1KoS>@ z_(^t>_SS0k-k@1hgmz44qGPnvKWlw@V`S0FlA(%@E=EB~t13acds4pA=Yw(&KmXsCrzXqEUP)Ix;IonjChksu-2-*sbCJpGS?sTgk8Ui_sg;dUS zCmo1<9yU7;ph^rHf0;+fsH!`mIG(!_Bg?_got~Z77f)6|I_cG>g@x1aZcCnSgseDh z#21SVc4}6_Y;JPbZn_{tQy;n5We+jeAV&zlWtUqwf_i1F?lhHZCIH*Ab(ht33Hf41 z!Za%1=6r*?8FfOx>rs{dJ9$g5IGP*O)z?e7?%F_*HV4$WWl+1AUz?csJpF$$0>a@J zb63(Y+Xx}fWR zGGVQD&qhT}O)leS3v1@)pKDj>)R}(V&7%C?K>n$0`b}KtQwD{NWx@`2;?a?o6RSJ{ z;?X)~$ie5F^y~sx$P{*gL;Fx6pNPARAhNLM4&p zmi63e95)G=SgPhvrQfHP59$4M!EYRVjPw;g*R@H9DzT#XJOv-V2s3`1LdX61M=5+E ztwh8`V@0|=(y8_Bk^7(Qz_@bakND)@AS(6v6htcPOgwkGfkEviMawz{KHeuree?i% zg#P6fSF@9r3|yn~QN+&SI~{JAxh)F@-qj-+V1WmR?kFi`Y5R2;_;s|baQZKED)&l+ zWHS2g%QpdTAzfQr&6K5IfZoaSSp^fiBF6W#wEt$1zwL1DN%`#B@kQ>G(CY_j-pfQM zZuO@62y_DO5LCF~I)83Dghp;rp>mzibIoTD>z8JsyR-Xc*m2llw$txw$O1fSHgcWe ze_-Y$A@unPiq;~U|KS2$;^*ru73^C3qD7b|GJM;Wv+2+XSyrwsXlbS>gSyPK(Tc$s zC%T*^H`;km542z<*uZvDdMaqx`ikJ}OLYPLz+n-)xZ$=^_LXYPisgQ5ew#Unjbtc31c=y!X&LpD*{mYrXWmd^MFaUqTxe>yOi(W28a1IS`e2Pv@7(mD$Pu;EnyhcQHr- zN*{g5k#6GObKG24wq?2~S{emsrkU+Dmx$1+6tQ$Kr#f>HyiJTZI=UY*&8hB3sFW!@ zwagpT`O4GJ$H8rpn8hp@xef|6-5N@Vam6ss+{8%d_D;<+ zF8E{js%K$1IzIBP1xQ@ClA_6QtFM*NTtpIe2XbFpqXE&FKWGnOe7TI3-j%ftRap}) zN?SjpJq08veDg@l?G28fgZvZb-)2K2W7~#z{MS0p^{h6IFB4+-NqUMb0vOrZHR-=Q zxf~(3&@#J-^}INOUFe>6!C?2*a9 zU`zA2TkIEy{J6tR^{rv2+Jj!6vKKx+v;bBM3IO3wKwXeCQk?v}T2o7y?+CmMR@yFO zQ8ao4fAyL7Flg7s5_J#-F`dWZe!@^<+h7>d9+r#6^rT%pZQ6Bo)=9f*?suT1>2QHc z!}&C!=MnmjI2i5}H^wxb_IA7A?wZ%{GNSe_iUUlT;Y#QdWFS@Dx)C&eTdO<6R7_2+ zp?IuCKB4h^^-TkPEwq-Ff0fgG5%QvUYs#HCK z6{nf;tsoP0lijJi4P`-C<^e4%(kWipT~b|F$Lr0n^YTtK%A>E!=con-jwQ^mk8+>R zr)B_Z$2jXg?AuEq-rFo9V%Yj0Af9EwLTPN&!iR88Cf`N8jk^ZftJU`EI49WSmG%%f zr#5)({W{Q27?~##)9$k&AX(|ZhTB6^S<&-6Mq6%tGIU|;i<6q7*N}L9?DD(EK;PGz zxrM9|@%=;!AdJFF#9Kehk_d_#MPr|5otWXyhis;zBA#{}R2=8aF@e#Kn`PPrNPa%Q zkxVdo#BNz~@(H{;$A0M6E(_W^XN>R4{$RXalGWB)KI=NURnNM7zoq{pP3-(>}<_Fv4JMFFg9BF_* z@yObxgKH;poVK*05e3|p4zW+~Vl8&Bv#(bA(BRYhK7DjzlfFQ7is_@>9&L^I1M5&~ zYhE^2FgZTefL=vv*X_&tMnWs5qkiu5`Ua1f?7B6B59ZH)uWxnleth$0<*in8v#yq3C#iBeG4mnu#3Kf2@~gn z!P3K8T9lD2!Vg)Qre|>qv{W8}Gt{Fe3&**+<9b;e1*s&Yc^v3jgmn~SXD9hsYOkoh zpmOTDvcL2wn=T~(Gt$8K!);fDCDAGkdBC4`pAbLvtb^a~b%J}n?`f(3xwI68(Xub> z)Yta|*V5&Q6DoHRgcGn3^r|<*jeyS8&d$qlZ%s{2!?USAlgOb~zZe=BX%mb+SB?&t zmOJ3Y;w_*TPM2X93Ahi=IHa>d87Uj&>KkKJos?4Cq@?<|& zS644ogDyHiy7!TOvT3S4XdSr|o$ZD=C(YEpLD>FH*gH977j{Zv&y(3cp{KxdXR*zxwAX~)R{R@mbYh>`I(pE_Aav)(^*D>C z(2UJbOqgLNJ<>c|O1XIYnU23~GRE?zqwi-RT>H9jd!5d0__@CH61;t>!R~&j4GwC^_5)u8HDq58U%T zBHMSQIK0t$z-~p_AyHX?ru{Mo^h2`NBdjc0V_Kpvx}%Ounds?%Ib%v-If~j8GO8no zs(8ZWs%n9O2vjOUy8ydd*_zu3+a4xazILdraJSV-$W#(Axbz*uL(fe(rJS^h~lnjgjvf_LZv0#telEZNNoq9oP zXCP@l>$D}3kaZh>{Kb9Z#dl@rg=NpRFQ5l^VJ^&X)2leiufvYjV7H5kiq3=~>x2Cz zieWH6PVNAp5TDqPOf@GQ)>Cv68M#M|zt+5q}&4hvELYz;qD+8KRWpV5z}W z!;q7)XVZKR=Vx7VrGZA8a+s>nP4<`~Jo0c%B?LLB`>Ol$(WKB#LN20kg4iLZ^T%$r zasO?A|GjLkZi)B73kXaz46q^DRB5iU3he7si__?qY>|;+Ahy9a`cEQ~8ZTR?qejNY z+cG=B@9r^1tpF3Z4PI;D4nf$y=1G6-280ofM%zI!1h`*J%qbeD=6!3fs&)5RZ--E1 z>ujW5e1SYCDpDk4ze-ntCIC+Gw}>y@Dim8PGhg1Ihi%;%A7>2JL!^&~9-qa5P;vFs z0ui$>vOb=@%dSK>l%X3!*6wwrQ2T8@X8LiN^p?gAK)Ys?FyEz*)ViuNA>XYIX8mWK zb$&q8E_%{*hqKz5VCO}!8A_?&X{P%pOP!hGgF2DsUosoA@ikae~bifu#yIAFnBU4el2U+63 z`1l^9i?kqzsnP)ksga1GLLFAdkYJUwcxTidsx_78iAGP{(_q+1T!X3)w`0iuTHyr{ zYcPCl^;dV?*$D{eL3N;Yep&>4MsX>ry9JY5sjGM3{x2t z%0IrI3UoG8hM6&!iH8|l-HX`OQ`FX%d0uulG2iGFqne{TCL}+y3dWs6FnrbM7_76J|E4iaq(0u<_N@1n*&p>A#t4@cWTgsX58E8s4gydRP;N z=5iv)g8|^0!vo&ybCB zpC)EZlZA}%mn9RL#{d5#>@CBh?A~`_0|h}TL8OsxB&55$ySqbT=ukjHxgr1>}iAspF(}tPE?el zwtO#^oyXRr?k5Del`E(C1{j=xZ_A*W6x5!l9f*8Y6EZpog;m4mH3w_(S+!Id?m1M; zu|)>6yHATjbnP2-@tRVT_alRu_fvk_FgltSFQnS2A<{0=L+@zFCbC2&AxCTXN7vsB zu3vzE^bFZIKJd1WAoKW}u2) z@<#{#D~+)X5;*bPdqU}Z{hS|xZLsgV@e;c+(e*D_|1`K@zcB0!=gdGSwN0XG7p>z6 zHZgZ~m9V!>3++|@>?K-@L-(%lHhT$Xw%lHzbL?wDhAG6k6|lAqRo6(wkm|;8f{Ht> zIjfvV7|%i4r}(W7iAB6LKdN`Phr!H=Ez&?uScOsVbR5X_C z`Ok~TbAJsFX8g+PqDt~HB|%lBlJKn3_FZ%sgB)`_$#*{qLi$t+fh5%RvV85f+}h#Y z)9HsBfd`5F%9=n%feTiF0KapE`%uh|JNSP8@xAq;MgUrKeSC6nPl1H_VK@_3vZ?IwMVt4iLhHhb-U$q)LaJYV_M+6CWQz+((h-@wLbn}1PTb4vWM>F3hQ ze|-NzvV^-Z6Mp-_xMddItJ90pMfef(pVGXCw%08HhXWdt!-}f9gwUYScauoj0TArP znWH{>h@5cYO1>t>w#$>ccrM}tpWZrM5454;jz7ezMzG@}GXa2e+C3feFInP9%?GUF z1stZwb@@9xw!vbAaW{*L+8v{#w)}2y$Irdw1oYB?1SB=C<40rsgZBsh{GXd^Lcw9A z2chiBZmy2j*HVg*EI5LC>U6TG7E`6gu0A<24mg7K{H!#MaQ+y?P~~vlo~3nM|FIH4 z4(+tBe|GpyH!MdU%{BVds#=u0HD(i<<}}Qbo9?thT5@|#LO+Hc@g2m+yKnIXg)9C_ z{A$3%_fNp?KN8_lhATCrQ-hH*1J}-Ye1mT_0+l+Gd=`?5{lutACql^2#~XZ>8rI>X z1W($og5fAc{IwJ*cBrJda-_ELq;XYI&S_a$+3$wC#5I3-PO0p&scd7f8U>zf+#|Pd z$+U~Y=pPne&hUWN)!`rV+mf#t{K}#Qd+UN`vo2C56=r#eS`ar|=di{171c$@?H_#z z$D4H5+!*v9`9h9;`$8|pr^qg{y8y!{Y?LFZbFg$OQse%^G`XsVPF!llbw=o|q-=^zvBjuBVLRS5-zXy&iyt@KD#)^5gT%UycS-3~pYHFi;3O zPgoPNT;U48#lf|dQozk^p>@ubP25ivj4GM5BsUPHcIB)Ne3e=R0FP3KbS}pQG`N6o;lY81nz0c9}6ztAt zZ>t3AC%F9fVygEB&Vm0pJNfd)0>|ueRz)q$=l?E3y;KuWNH10~1-}Dv$wj z)(97JWCDWu3a^KzKMAY>=fAa0$InseC=9o~w+wxHPDBdr3h3c#)bzLxEJ~+Fmx*T^ z^<5tSPhPqBVJ|Ph2`*yxsK@yv5pBp^H@KIiD#}?!)fuXdoQkSMw(|)%wWwxR@F?b- zsV-0bPK9wU#B!W9CCZSQ&Q+&f=KfkHbu#8Y5~Ew|s6d3j#QmUQ;;#r}b>95dd%#!=j^DLoOYH@I{j$!=P^&$Q^0dj6 z*P-JGGciPgtm|iUxFB5h$9lEh+C6fMuDU2{V(&BiqDw(W#Fsk>9tjq|*s1loR7^nv zI@*`!(v&O)_D;So7$j6MAFGh+(E-*cW2+KD=F*dkii?sf0RFnn!2+ba4L>+t4ALT~ z5QhTLBC_rMf{)@nZ0Z+#VV)ZK{`jW>gWseDtP%+caCWQkS1OlpXV{>g+q%P*$L^ro zQfC1?EY(HFQyeC!(IJL!8kzaEE$J%o0kuqKLE_69P4x3eF2dvf=8qKa>ZNSvpJ4sx z@H-Kc1kWIn1^X{a2L335DEAC-oTaZ-l2$fWIIx#l%E6z~xrMwc)!_mSA;pDu0|cSgg7Bi|!$vnk4@fGY~aF}~wJ;6f6me_o2JH~nlQruv%g z>mF5+>3PI7*XyCfb5z&^m;JD$yr!jMNZYD8u<$UYqp}=mX7O#e&XoHgK40WlEhyQr zJTq=UEKMD6iGSR>s)J8+Hnp6HsjBG?j2s5omq&rYym%obht7-~9s)l@vZCRla);({ z<6(L#{>sCK4@!XQUOb}JSeLn`cNV*imTco6?-T$E2ueZe5qHg2QDY9vG%H|@g(R7* zmAbLT%;(u36TKEF-UxiT7JA=g@3qtT{)OG9@yW>+OnR;I^3a(FI5lCXG2TR6e3=vk z)Tsv#y2%@&jc)-2u-3^*aXf4n=knY5u^mSd9|nIfnZ72K=VttKG4cNg&lOxGQl3XV zqv|S(Fx~u_$umVYQzr-CmyHt1!R7F+k?5!a0DIdEF7Y2`@zwwA>f&$C(JaxdE6zRM zJCWRVI1~`h?LAFMT)E?>-D1BpBIj3#DMt#|8!dEY?5)ebQhr7jJd`nM#{pb&xSD>S ztIQhMow#tiy-$P6H2hg0qKv^QNP39VQ}VFf2O|)!)4tlZT5^ zR3wcE^VJm26L!hdZn!TTA{R*Cy=^pz1~>V6hd1Zd@(yfT4yzaHjG`=8M2!wi2(say zg+WY^PuqpPsH7nV(bCnnkY`{y{0+#(3Z?_P8x1TI1zs(jk zxGtzMXga5R1Jwm<;H{8c|=URZcaS6k0noTOf;s< zJeseV>CdKmt~p6z`6FaZtznKFSL@A#P@1;B@ac(;(13HXSMtI!P2yn(OU$bZyG{J| zw6S=f1v9yvI{0>eL|bxsu-(C{l9y!kO$@sXQ**&c2BLeE?U-S9xteTuq&R)aPy8~n zC%L%GE{t@K5J!6ZVF!w;WGigO90Bi27S5D9YoT7Qf!1{4x<)8VRfd&*g*-o$#zcQF zVz)P~RkA_!qVM zNLPY@a9TOQs)n+mGz@xn@r>f76vF4&ld#aOXveEjE)p91M@fT8H-6==8|jaWdawEe zD?hV4avR0cs$?XCc6Z#qT)mgv1&v18h;6~x)0k%Dxe~PBgTvL>;J%gEW+lY&xI34B zk-S3*iBjFKUziA>z#Tx0|8f8o|KkACmpUxW*)zS+Wbb8a0I;#=B-%H@C%7lD-`xqQEyfOl_i&C6zPEjx41=I}MUORhF&2kY1{&fx`ju z-~wLY;jp$D5|mHg&ifc+_*#Rn41cMc_@PN6G^SGHQo`4^Uk~%RpMo`dZrDOTwA5Ze>l0h_9zaE z4>90Gt$y~cC0b~<%i>JW(X~hK!OKo~eiN}cHXgHw+nS7@cdW3^*?I8+ry8fz8uGIm zV0AkTWu)GEK+D|RNzN5nrAWNgILf z3;c-<#KfT$ZqKHcgmbzi*=o9tX_(q}x+FDI23hjZbo(Z;tnRm7RtWOSnlec@4F9Ds>I>-Ix`VVQt1S_(>5PH&+=3y{@+DQ()a8LIOfw3byC{Q zO<-tCPl6%Y=ZD|Q8oHIqecOs;JK{1jPg0>t=Bc|KvYnhLE z7!`(u#vZvW5u$985=ORVLt5`OuTVLI?S&TrDoL{%;vaO((rOME?)VhZh6p_^0~KYe z58tAYa`l-5pc40>;iWGeRCtQYy?3%z@N(GNHj6Z6XQqgVQ{Mj8G*lpC2>6@!W=yA zDo6UPbAArh%WNhMVBHV0komJ!efpQ)***CW(v`ww9vF_am_TL-!0s^oc@<*6jb7jL zA?7*wv5&ErAtI!d2Z|Hn~{rypQ|X zXAVR3vh^_XRjofR(v!S91ilN$He!p=HoL6-Jm7BkaQz;-B-B^mnqiRcO6Zu}pmoa= z@hBr`=XBDU$bgYbFVK>I&E?9dmtHJ^6Q@!0k}dce#aFEn^3;>~nKR|&iDOXX=y?c# zWYm>Xctj@M`ne0@jQaZ~T~s*82$OL1GCV}9p{U(3%Wc1JExAEzP#kQ&0Dg6PdnY}o z(56p3Cj{5GwK?Hsn_Kl-($k%88dD!2_ZF1lVSLW-&5K_ z8>hJ4S`7BH%+?4XUPq1-f{-?+)^8~zCqWhx zjLGMRO6wKTG{PhCY8s~dcy6M-h{fUlvS@r{mA=1deya$n$p78XG$Hkd0IRulEF9VR zTX#q*81I{My(-N_o&k^clneAWd4Fzlk221mNw3ebIdsxf{Z;mIZRr%e_wtxIev~=l zh;2CmTxy83r>t^*GzUDNGp0Y;onx5V8Ckmj(V231%T+(!n%{!Wt~7)uKFj=#)9kW2 zj3si01Z-Cmf*lou`X&J%4+lWv-FJ?&>$;uQTtSPR-HHglXDx#qee5j=0e=nQk=`0# zmT0yON8`4fJ?|g>$XK<(|BtK5>94EF-eCH=*nWP}0oMoaYO;vL{IpVe2}>@Eq5PK? zVA{rS!VJzj4s@PX4Lh$H+`?XiA+NhQ*<@ndt?!k*bdQ(@Gn*;dZK(5K?#`)k9g^)b zGG~$9f<(Ms7EUK>C)Ql%>M_q3cn#y_=AIYf-W&Fn0;Ua!d-99ianfo(?99Iy-~CmwX%y0r{&%+25wUISbrC_EaT{+k7S49oDu7Sdy;!0E_!>JBPzP;EYP zYrjZ8u(;vdtjUDYCnhDG{d$h}`^dqs#WS;2y z)3cwSWdg}yE@JAJnYF<0b;Hl5sAF{^D7u@Z5%`<{$N7MYK-p0CZ%qI6(aB7{XJHxE zNyn>gOHo)9h!^l*??e%(KSB|Yf3Z(CLC=CRPVwmRv45`47X9dF0*F2Onc9>uS)Kge zE-@;j14J4%W^V*d3*j0*xL4`%g-w1+In+1iAK<5?rwf++&-)o$a4N@xYec`m4~++z ze<=v!FW?&b2rxC?ugy(%bynvM<5p9z~9b^P?HAy&lmd8ZLE9C zEAw8=@jr%Pqz|LKzeL>ser5P4E{$mTfi;*G1Di1aZD*=`WaR#Qwa1748IRwD@C%V+ zM-mtf#ajq=b9H@Z_wt|Tw_dU^D#v3}4Bj_tXlZ}{u|jYG zGrC^#Fm;v3vBUh(O_u1-EDFb7wc0<;Gz58!ghT^AlcCWb$?I zZ*R+j-^43CO9{UhIzCWGh_H2~guYxesnt`+N^cGze3Kog($Wk|?%^jasKXD3*HZ~Xj*x` z_EqQdZ4FNHy-h9G`SL2|elzVs3uY`ctP=s#{eY*@}`P;mc2057*&pBwx+4On$|ac5_9%bDtOnwZrw` z@I|N?eJ%i-5@@shhlc+1yO8He^J$0c??TXf5Gr14*fMilih&I9kx2*RAk>K(q-V zq|{!Dj1f-mO>Rdel9@>n2`;8UN>IOLgK2~e9H6zjLWW}FM&ZDHDIKWQr@2ID<3B%` z5-*IXe`!O%yRhl~WIX4Q8?3;qfERCYo zl-h}Qop`XEj1q^~{{B(nLw54YWxPrF&)#ac+N;Hn2pp#`N1OKZj280sAl}aY-|RvU zl*LbuO*&hua;5k`8@|fbC)Wq+5>veFc z%!Qm#j;xyXt9Zi0j6fu|11mZs1HwL$@73;n6$sx_0%S~VN^;C~@r$5`&lm#v_Mh)} z!Fupb2{be!z@GyG#V;EF8q`nR4JTgyI~)W?-#Gu{l0gLhBb9P==PeS}2ER?vu5=u0VSU40n&~O`(fAgXF9uVbT|< za}m^iQt!?{qOzvxqh`B!l*pH`sO;D!-piAz^NK4-__IYaP)HTdDp*nOk8SV+uI=(g zwY9$&Tk^+lRL;sRQTbQ+uS-0wjTs{{%8eG`CFlD6r#f(fmg|i2Gb-iEY9}yDyV9Z4O#)^Ud(7VD zYuV>Yc-C{}0$%28*j%g}sJ3Ot!a5p`tKv0Qclb!}+h>GJ&DzkTWqL2(l`+bg7R4On zn&9oFWw9)=b7D^^ke-J5{2P$uH`j44X)RbwEq-|YZ~>T$uJSXj`c{6SSH}pLbExFU zIa-_xzBZ_tVLv8oX~S~hh7KK?r0kvGQ<$uAzJK=;R4!9@f_f-4^R+f#x=1@RS0@aQ zsZOBujJ4_n(f;Dy-7BsiRTkAtqG z-Px*`6XS$eR3N()-mZ?panE{4onW{>R_0{X;zRYdX{iv23Ztptq=v_WCfF5ce@tVe zwLBd%wE^!5Qe@;&<+3;05Fu5J(@`hS5F~91_pS%Y6ylaG^yy8VsA+R!-~%%01Xjuo zxzbWcBv-(cN_C(82aWuG<>~r1g+wf9&r+El;)7t8(xloY?3OVUR8)C17rR*5N>Q7)#uQ&CQP%G$r?(a7KuMM8!YPUJg3?HuXOi_{Sd_V$pn01H#yij_1V`A;CyF{1+)>m)%BD0{5Ky?&~j(JV)5L@Hq#~ z33F8u4iPhmrgvXrm%qX`rJQCR;m7tNeYI6{i;Vkkzlo0++;0-R%rmc6oHt252cs@G zkn7*tzLU3?@LSzkFgURkVVd}T=`j*p_%-Q|;p|p7r~F#`+ginu zNC%0&lk!WmkrKD>l1E_INANLN_bI0p!TR2~Y@BOzT*||DTXyl;X`|dw7h~Lxe2TOW zC2fpqcmr`{Z8!Y3S#?RGwsXrCm9b8p)mfyZ$khOzcM6GCd8xDi0q(V{PKUlI1s?sa42X z3KT|~*(BQ*-saxeD+*BdLN2BwJZ!ETGl}mf!}ulD&V-C!)qkAsZ0O+Bowg6~;sL&$ zNNZTBnP~!FQErAMIr4I|3Bref44e6)(iKq*N{UBCbA5b4J&|YBEp?!d{Jml(v(2yv zjaYlLRyH!^eV*Z=#nJ_yeM-+qG!v=5J;O8Bxt8H=B^?m9$E;cTHniDxG3K zLJ&;6IKy%8XOgvKc`MIYr(X6>(3VHZVehZ_HYY>*{}kdA!GFAI{}krYPy~RYrlz=A zIEf%CHIw%H(4WSHKTsuN|AilVdDp^01k@nqk6Qm9;NhZrN$&pv9sc&d^_tp$o!`-n z8n2Mawyv=CIgK6dnr8&_oG35l(*MttA^e-{B5`@+KoDF&8y&(P`%al@Lo?|g^pO8` zWZ;=DBU@HIHAU89qo8>q@U-{HB+U578WTfG4%Wjw=7p6;9pP(Z@bJ71>LXfMJy$6n z*Pn3p^YC+JQ>f+zp)1C|B&pmBQS5JKwfHTp4Sbbnp!_$yd#kLX!^PFe5a{lVZ}ZQ8 zEz6Ew7`x$wZat*xp}LFsX`e0?$J^1zj_gkZ!;<3Y|Aeb?X*Cj@A#+RkuQ^1b9RFhu zQT>1B5OK8#qWm31gcSW>gNXV*z#@A518|<3lgIF1gsPfI+zrZt*sKl4k8Oy^a#BPa zBc%DG;8O{}(ed9PX9@kx5d~=L5fiVpR*<@Oqj!eh!n2mcqnBdDjVWDwbKLdlCG_Hb zqJM1Scw3Qh_qYF>*(1q3ut}F7yD?7*1daaW>*~J*<>X(LU}hIEa2D=6VNjC(WhL@i z*aT)>*^rR;J=yd_eHz}TU*WWqE}A?z;*hC1{IW|L#cevmI3LIewc0$Cou3sSEnDWKQ?cjT4j8KeQ|g{A=#;C-{;?TYXs4?!oy46ntkn`6cBRJu7y3Bu z`7iV_7xDZe`QPYc>4*n@x?-Nj$$>iN(5^*5CA9PBC`b(Cbov>QI}l z+^h*Uh59;(zl@us*BQR)hYuT|=SUvNT)8doLoivIrz^y;PBafUuX!fG7?fjSMDivx zd~HGsaj!PQk-4(?jJ22i*oJ#J6)js5Ywml&8u1VOXZ0=j7B@ofn$x*0zItU1ik=R? zPRd;h7z)#Zryw{QfidBbZz9#JnAwyi>|ZO7n|YVYb8w}v2Elzjn))#lJ|ntb3|T&4 zYRQUT2jpt|p|f`-_3gzTargmk{83j^9xH!?HixWiCRJx5mBB|}_d~8$&nqK<-2IoU z>zd5E1kcxFZLwlSdOg9!e(zu!t5Jm+59I96M}eBb)nn^bo!9j`Oh8*KM$ndC!1^UeV9$L86$Y^| zQi4bbPwQgw8-9V|f-l0T+s#_qlVTu4KQ?#Q zk6T$<#k@$rO~*O#0$m&dpR-#FKEFkq9u1C*enS0cNQ-rHcso{ABt5eBHhD=D=7;5~ zJKB|_&u{CzU4M_SU_Ak>RlwpRX)x-t$J%f5K1V@k!KqgH*S%vufy};)_2&ps z#gH!-^g1FkEPcjImk&8M>?{lp7aud^9k-h=p~*5QXp!ihKx@=2Ms}JPFF?;KPySBw z;z`u^0ju>Xc$`P{1|NXjDWD`J-;z!xXR&f5$#aCCHZH+Ta^mUU13N90#K0X0n3Xug zyJ&c01`383MixpVpZNkR>s~IVx%#N<`|jY9q!MBXM6~c9)p^WYMVzbz2luXbMQw_; z64MdiZ2h(o+0M7EdBuD8vz@jd1xhtiX>v2mkIcm9-;!F8UJkQ3og~xRRv`9061m<# z24KjvL9&IaR!-C0kN&AG;epVeDD&ZN_pAfW7d`W4*g{+%|GsT}-)#USxa)v4)mV#q zZ}*@b)v)R*6VYW7#TNwELK<|yx`N15B_l=EM^8|Du2N!sd%)J5tC@2cYaX6URlVGg@3!iQ@@&TGc$c_W#; zx3GC(MLYQVP^^q`TenykEZ${va_P;S05`CtwW~D*=#8XMT0M4nvZ>O`I+Urt3OY&f zI_3L?qfcBr$8SIeSy}|(`Ca@-F&?Y7IjvKAz@0NFy!11-MzDAw2*x@3J4*|xs^%F; zL#6?^#0?uYOGw!50(Qwk@%>?LZBjs1Z7{FtojqMtB@^53M@A0B(2Oc1qsg2_iY<0S zF_mJ{uexoKo0}%!qB&>D>(X$4Ct{2x##Kh4c+uPvnQk`XjqTfMwiwZBS#-^*v^=R<`jV@w)EM~hssCs<%YPA%BA(!d%aeaCIUL*Gn-lvH&?&Aq68wF zm99$Npg=Ef*c+kGw|Zj&J8t4q#1F;xd=71Zuhw9|=G727oMmptFq#%Je;bbI?C1P5 zPOJ7+1$qZ)XK^%Vq;Z#Xo*D8CmF`a!*<=vhsSU${YEO7 ziFXw?yR+%;D$NaO*zTJs1Af8D@Rpppfag**0fjKs%hMfm1%k`pUh3vHjWE1XvDb4j z*Tc=Rc0+O7*t44u8h*^DvpXxkIu$z&H<+g(T%+f~8@p>N8WX7qP;c6*)qtI`&7Fx* z$4w@7s#)NdJ`_2MqEeGV;*`G22H0d6p8$q1h|c{izaHv%TCMl5b~KA)6BO&G<56Xcj#m&ymYGvF-@O2arC5y1Rd_r zrdetjba|!ch;r%qo$_&3rhYgO3smvTUh?HIj>8P*d%#*1= ztp_PML`D^nA3-{yGHeAnUun8FErILkn)~0^WrdcVcZd`0Hcjdq*50x|HJzPkrsZtp&^KwH>Ca>}Bcp;r}?sq*BXW3SM7DQ794`flOx(>5r zXM1Es6=85v(ELN5jG7hcFYS-vh!^cOn)X##15+&!Z}MOLN{t)H%y~>_g#|yR5>r znFJ{g-=!%B%QP!fRI9nz_v+hSRp0&fmg^7(;mNQlUrDf|a*I42L|cq_8vP?YnflZZ>;pbWCQu*RTE8>Rz!P9ZVfubrwdf0k^J3cDLMq zp=nHr_s^WvZ~E>k1N{W<>Gj=bulXRMJ}RHTz488=NXDyb<p?tXJM7;$XXox-eZUG ziA8*Px>s58D7D(+{sA&N&ry}Dw>d_c3VildxfK&@LM=}fsiM}^s)S9HxL!0&&s$pL zX=b##Fb?t$)tB3}H~w2<$dfbWpJSd?x(Hzn6!cDV(*;l~SJcaG@UR9y-z7jbN9FJP zJ#rCOrq|`Zlb+%hLq*| zOYXVe!~Iu&#enPcA*M69sd?jQsUbu>_j)T%;q-D}1)N(g*RmpBI-|TM5L&3jfD?^0 za+`rVVb$6&%gInKs@_x<-8&=n-i-5 zM>S(o;XWxn^y^8MIYBa5 z;W$pZ-j`ux{QzB3VGjP<*n*$BhOe+uSVVMb3yQ9o`4S7$cg`RK9+v{~t=onop?mW% zR#W`Fc?)mXPf8&!-Chx1OUDvG>I3d4Pu|){i3+LuIk!v*>0e?58c6dfbW_C1l7Fl? z$(>(`;3Go|So0-D@a1zmsNcN6hLg5-=l3wLR1cSnhWA_;#jd3gzRickXB zkeX!80iZhdGw^S;2=p%M5AS_ok-3B2vHIR0OTNmv^mT1YzxnZ+J|jtGPXlsxcjJ6` zva3Wa8ch@LctDL|fe`$%|Kz09H9+#|$U%0S0UAu|>%>q}<>v*Lw}{Kk#LVKT<@rw% zB7NjhOK(jo@ig3sQs>=pxHtKw=EmvCnNyB}?*<#b+Fp2D3Anh2z-EkQ+Hd&QJb(%t zoP2T)_NR$WZG_og>Y`PeqBb8_>bb6j=PKiis8MR=49cTo`sZQOVFJ*BBRd?iDFGg1 zfgo@S@ZIj!(B7fsJTF24A;6-jKH+IJ)>ZFKsKbI|o`VE)M;-W+M`ZWn$Q*?M@XBX< zrl8MzRW{!5Q;*N|=-6Fpq!IPkX(QXE0vea2xT`(@Fkn4vH|%3~)V;lg1$A982!Hjt zl|}A!CmE1sAn5w&%6H?+xp4NvM{rC7o?ZIcJud$X`9iM_S0Mo;4?PK(y{l|}bJmS< z(1p&@Bqk{OQUp1ah8vrVECBhPI(IT3Pwe%0)iTC*6uP+{P_aAuC5(7@U?dt ze7{$Jo{9Vv&;uauGQ#H@!hmdywfvGq7K+UOOc8KC5Y+jDZ@r&m=qfoulP%}IE#p%Q zA>hQvz+>|{mxFP+NayZwnMuL>iuZ=^SQOlUI!yEF3|6}m-yKIqlgz6LfG?U#Ed z(Tpz6kuT+0Vpi?zpnhA*dSx>WzSjk~kl;F!Yu*Ip8e0_+`=UZv5-!P9x)a#&^J z5uwArqbL|I=XQZ+((!NlX3~Q-pe~vm6@yRSo-H3~`!BBnmg^jZM&_vL$ZD9UVJB(s z;(e9Ih*BblMqCcc^D6X=4Kv_fJt1Tb?R5GdjIL|nUV9t78%`rla##Ba%xoGCp&O*e zaGkNqCDX3~`v3x%W7~(<<#`0MF5aPR2%lpxK?Zz6djf$ss+2<~_eih&^}s3unLL2S z7hvzl*|pal@Guqu_rk*!{dE5gAWxhfF4Wkio|0&aDeeGwlCZ>5I*Z^QS1gCMcC`nN z9;Ndk+LC6=nXhf2{9m{FoPqmWo{$>xkEYy>^>r~qDXGlSRS3I)+ZHrh9hHF?iopOM zb7-6s=H@Lkof}g`ju^L6O+r?OBMDglmd64nKtjn8SWzLC#&3+0V zVMS<*z3zObdyV zo9Tx?2c%me?HTn=8|weu4m~&qwo8GC9dfVDutif0GX3tGFvYZL#0FevWtCX2kILf7 z0(2I-(T|#tEYa4{b(L_{=>7OA=GM&1MrZRJGRa+)#hny8lr$=Oe0pbkQhj_`xOs$$ zm?muXNqG*lRt}yv5#;!I{gO{2xpE`qeoSU9J-g8?RM>a2;N2Xm-DD`oIjqNqB~`)d z0gr0eOlFAaHjV{Z%1HTBurEmS(gLAyEY&OxB8v}L8hLiTD_6w`*pk5sl9ZWWv{io^ z+}NQIzgMdD?#eM4gs7Ke#C;Qu9a%|s(#l4wc zkT+4^CiuidN*d_{zafS=zk1)ENPxtlhj2Dyv;9_g>>H*2XsT{7J>}8ea@1>L8B#o( z_W{~F*}cdOE4xoeE#wr-1y{5{Ajs)O%Htgwp1_3f~mMn zdtR`J3%bu6z&!TKGT~*_^KX&}+XUUWN4cCVqQNufZ$D`UmTO-%KJ50NjBzme=5H3# zPAz^m!vERmTxO=-aKTj$T`qp`KDc_!z~~zs1s|m~&?)|#go}!tt2A(cUvnWUmH4{& zj88Jt+^VOPIO04&f+E~Ccbr$tGnpEwXq|f;>PuNBnXktay6gzB!6N;j-Wjtv@I^rB z>XWXYW!15w*(uB{3T4;1_e=H|5~Z7)GS_uLP%pUJL4OAty&NgtIFDbN%bO?FM+;J{RaIDL@$$buWSnd2d(I`{>(_qc?R(W}GR@#mO|xTb-faVo9JX7! zO$)krF0^ma^J$0of#+Afw1L&gIkO3)ao$+TYX8YxTOC!%x$v8SgU0<3TChRpC-imB1l;1>a|v65*IlcG~r`~S#Z)383`CjAmXwy zl}dv02AWG9h_9}W9WSpK`fqOg5In!VoNM5brHJ3_N@6-#zGi8hssR1YF)VaY|rccA2q_ zt<2ebg-J-K!qR5sjjU|KQ7GIXPdj06Ap1?jBk3>6sF%LCXj2&ar zWsoJ|SyvaKZs{&<*5`QI>WVX?5}xet^c<11To9CE3&%3}P&$eJw&?Kf@1&rfFy>5` zZNZRf@jZJvP>JOfpd692ix=I}TSDvLMR#DoyprjL>TGnWkytZXt6<(BMIPNaOkXF% z#uU?mmEPL+x_@Q!V8aJ?Cm70Gjq#cmg*j&U*#u|%%hA-ULB-!!_p6~h?Vl=)zmB*i zy)ygZUf0U@i><`J{p;bF7)rYB+orAzT9LfL-R(e(fV)v7_8^!U#>&iyruvfhh7fE4 z4bq8Z)G9r+^_@13>bOQycv*CYc4vl&48xH_P3bL@tTu&)(qS$12z^mewR1*xng5?3ot!4Pfgq2 z{Y`eu7QB)3GYNy->R1L&;qykQ+dEu2jF}&}Z8z_ZxtFvox`2R_2)pb}CZV#-O!um> zu?vjp=HVff(ZOokn9~ocTHPc&*#Ynqt=|F!{pol5f^mf6xAq^)sc4%2I?w#e+Wx5K zLDZqcug%&i9~|26AA3o&@7eaB5M*hyysLw2v=r}h1Vpo=d#vt^F*ucT@V&IKSMFOs zCwDE{$~8l7_$nBM<{J`A@xx;Rb;3E!^SV#v&;gk+aKw|H7UYI3D$>z#G_UB;ca-hn zdy(g*)76qhvd_5 zpEr_SzEm+~;mX5@D2vMr%v<^Ccf>W)TTlcXu6D;O>o)osB_in#bNFg^2|2`kkREAE z6C!290=ZRDqkTe&4HIFf^1a0zdHu1BICuU{bzs4ehmHjL6-XbosVz3S^sM}{E=F_C zWcu^*#x7`n_GZPSzIHUs$$Zwem(PAX`))O~9=PlbJG^p!a@APi)1C>x`GYj6&xf#P zy;Jhz4Aol1izySGUEQlDmC}{x{05Kee6)e7F@ET4zq>QU`@0JTuo)~L?+*s~=V5_p z_T0l9LbVGuH>XaakoJ{IOUVhB#iNm>WgvMcvOL+hDugSg`q><^z>ovaM8Y=+ZJR-~ zpU9r?W*e;#$<7^)iQb3PPE?Uor?gd8e*({%+e}uCC1_4l<8Op$>_?XJL`596^ffBS zPac>~&}%rV(2NXqbx(Z?$(|TsO2Y9XUFBgS-%6FydgDKDknX7GxX>WCQ#t}hm=Z?b zzy5Z>;TkJat5FRkmfj$l{SsbEiy`spTR&T%j?6=Q(Y;cP zFKVSTF4FT9405tC4uOQ}*?hjCP76w5_Pjq6NSmo=Vw!DpT;nv%Amr8pu_n_|Z8w?a zD|ossbvEJaR%4sg|nC*KPd?0kR52zY^2GL@XE-(U4!MD(tSyyvI~FQRm*2RSg( z%E)&&|LP~8fZgPU%UbSLJA5ElmaeKDh+o!nTXj(sx_cdqxN)baH72f{K!kwm=;;~3 z8U;$GPRphA?z*(7DVv^85f($t96Jf_W}nqs!B2WIJvkRvopnHj!|L*n9BPokJMIUH|C~3pu+;a13|{eC>?iORFasrf>%?R0#LM@3 z9q|sj2F&%;5;X4N9CacV|MD|jxKo+2)WvA5c_UkZY)QC(0qJH_>|LBo`OA1|?G&Wh z1q%m|IS3u>V?xigc`)2n>chPkS?aI85U4~X!=8dVQIAkpw#@MT+|LittLM;Vyw*B8 zhqAvow6H42e5k7EGEc*L9MAeHmp?(~OGb8gGTSC>-v({J6 zDM4VL_^i1KOPU#H(mUF?k5s&(h04`0_})An-`LeCPp_bst`Mu;R(4W8HgWa;ZWd*B z+Mqp3%Dfa^oUGC^d3xv<2ACCa7zGsT-2EyQ0KtdVA5`H$*ZCFJy^h2L{c;^k1*;m- zoywX)(_FOVJ7$oAF1V76-GS$4pquVB`CeyL<3*Q+YU(?~roIt~@BiA*z1RO<-SvL&kCi*te}4Pn z(VF`^^;W+x)c?IOd;Ytc(vL>(qV~uCoSF6IrHyypIcLxyN8auQ;0j+r_TK*b{_^+t zeqOuZEw29|#YXP_tjI-cGH+aDKxPyQ?0*E!Sh)R8~6=gaTcw!h#1->xG0 zeIr+f{FiA*)a768Xt7~ClTc9fM#NR>W((Itvw7m>>)*(5wuLfTI@zVzvFxAUy(p&T z6gYY9c2iii;(q=Azt?7|b|HgTe~DWM4fUip@r literal 0 HcmV?d00001 From 4774b89dc1c860a111ab6eafe086838987302cc1 Mon Sep 17 00:00:00 2001 From: liminggui Date: Thu, 15 Apr 2021 23:54:54 +0800 Subject: [PATCH 27/32] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E8=AE=A2=E9=98=85?= =?UTF-8?q?=E5=86=8D=E8=BD=AC=E5=8F=91=E5=88=B0Charles=E5=8D=A1=E4=BD=8F?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ proto/tunnel.go | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0166700..ec14ccf 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,8 @@ sudo iptables -t nat -D OUTPUT 2 * 与Tunnel的多账户认证,账户可设置有效期 * HTTP/1.1 keep-alive后端也能复用tcp * 启用ws-listen后的平滑重启问题 +* 监听配置文件变化重新加载路由 +* 订阅增加用户名标识,用于辨别在线用户 # 感谢 diff --git a/proto/tunnel.go b/proto/tunnel.go index bd8f36c..4b25600 100644 --- a/proto/tunnel.go +++ b/proto/tunnel.go @@ -102,7 +102,6 @@ func (s *tunnel) copyBuffer(dst io.Writer, src *tcp.Reader, srcname string) (wri } } if er != nil { - if er != io.EOF { err = er } else { @@ -115,7 +114,11 @@ func (s *tunnel) copyBuffer(dst io.Writer, src *tcp.Reader, srcname string) (wri break } else if s.curState != stateClosed { // 如果非客户端导致的服务端关闭,则关闭客户端读 - dst.(*net.TCPConn).CloseRead() + // Notice: 如果只是CloseRead(),当在windows上执行时,且是做为订阅端从服务器收到请求再转到charles + // 等服务时,当请求的地址返回足够长的内容时会触发卡住问题。 + // 流程如 curl -> anyproxy(server) -> ws -> anyproxy(windows) -> charles + // 用Close()可以解决卡住,不过客户端会收到use of closed network connection的错误提醒 + dst.(*net.TCPConn).Close() } } } From bbd6063e77f6a95b8b616e9d34c6c2d5b46a3de0 Mon Sep 17 00:00:00 2001 From: liminggui Date: Fri, 16 Apr 2021 09:13:44 +0800 Subject: [PATCH 28/32] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=82=AE=E7=AE=B1?= =?UTF-8?q?=E6=A0=87=E8=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- conf/router.yaml | 2 ++ nat/conn.go | 6 +++--- nat/handler.go | 26 ++++++++++++++++++++++---- nat/message.go | 1 + utils/conf/router.go | 1 + 6 files changed, 30 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index ec14ccf..49a9fd9 100644 --- a/README.md +++ b/README.md @@ -169,13 +169,13 @@ sudo iptables -t nat -D OUTPUT 2 * ~~修复iptables转发后百度贴吧无法访问的问题~~ * ~~支持windows平台使用~~ * ~~通过websocket实现内网穿透(必须为http的非CONNECT请求)~~ +* ~~订阅增加邮箱标识,用于辨别在线用户~~ * TCP 增加更多协议解析支持,如rtmp,ftp, socks5, https(SNI)等 * TCP 转发的mysql的连接请求会一直卡住 * 与Tunnel的多账户认证,账户可设置有效期 * HTTP/1.1 keep-alive后端也能复用tcp * 启用ws-listen后的平滑重启问题 * 监听配置文件变化重新加载路由 -* 订阅增加用户名标识,用于辨别在线用户 # 感谢 diff --git a/conf/router.yaml b/conf/router.yaml index e8270e1..d489682 100644 --- a/conf/router.yaml +++ b/conf/router.yaml @@ -53,6 +53,8 @@ websocket: user: # 密码 pass: + # Email用于定位用户 + email: # 订阅头部信息 subscribe: - key: diff --git a/nat/conn.go b/nat/conn.go index 6ed9a48..884091d 100644 --- a/nat/conn.go +++ b/nat/conn.go @@ -89,8 +89,8 @@ func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) { return } xtime := time.Now().Unix() - if xtime-user.Xtime > 60 { - conn.WriteMessage(websocket.TextMessage, []byte("xtime err")) + if xtime-user.Xtime > 300 { + conn.WriteMessage(websocket.TextMessage, []byte("xtime err, please check local time")) return } if user.User != conf.RouterConfig.Websocket.User { @@ -125,7 +125,7 @@ func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) { clientNum++ //这里不用len计算是因为chan异步不确认谁先执行 remote := getIPAdress(r, []string{"X-Real-IP"}) - log.Printf("client ip %s connected, total client nums %d\n", remote, clientNum) + log.Printf("client email %s ip %s connected, subscribe %v, total client nums %d\n", user.Email, remote, subscribe, clientNum) go client.writePump() go client.serverReadPump() diff --git a/nat/handler.go b/nat/handler.go index d6cc149..b6a86f6 100644 --- a/nat/handler.go +++ b/nat/handler.go @@ -27,6 +27,8 @@ var ClientHub *Hub // LocalBridge 客户端的ws与http关系 var LocalBridge *BridgeHub +var tempDelay time.Duration + // ConnectServer 连接到websocket服务 func ConnectServer(addr *string) { interruptClose = false @@ -81,12 +83,22 @@ func connect(addr *string, interrupt chan os.Signal) { defer c.Close() w := newClientHandler(c) - err = w.auth(conf.RouterConfig.Websocket.User, conf.RouterConfig.Websocket.Pass) + err = w.auth(conf.RouterConfig.Websocket.User, conf.RouterConfig.Websocket.Pass, conf.RouterConfig.Websocket.Email) if err != nil { log.Println("auth:", err) - time.Sleep(time.Duration(3) * time.Second) + + if tempDelay == 0 { + tempDelay = 3 * time.Second + } else { + tempDelay *= 2 + } + if max := 1 * time.Minute; tempDelay > max { + tempDelay = max + } + time.Sleep(tempDelay) return } + tempDelay = 0 err = w.subscribe(conf.RouterConfig.Websocket.Subscribe) if err != nil { log.Println("subscribe:", err) @@ -142,13 +154,19 @@ func newClientHandler(c *websocket.Conn) *ClientHandler { } // auth 认证 -func (h *ClientHandler) auth(user string, pass string) error { +func (h *ClientHandler) auth(user string, pass string, email string) error { + if user == "" { + return errors.New("user is empty") + } + if email == "" { + return errors.New("email is emtpy") + } xtime := time.Now().Unix() token, err := tools.Md5Str(fmt.Sprintf("%s|%s|%d", user, pass, xtime)) if err != nil { return err } - msg := AuthMessage{User: user, Token: token, Xtime: xtime} + msg := AuthMessage{User: user, Token: token, Xtime: xtime, Email: email} return h.ask(&msg) } diff --git a/nat/message.go b/nat/message.go index 91cc7d5..fe83ab6 100644 --- a/nat/message.go +++ b/nat/message.go @@ -17,6 +17,7 @@ const SEND_CHAN_LEN = 200 // AuthMessage 认证 type AuthMessage struct { User string + Email string Token string Xtime int64 } diff --git a/utils/conf/router.go b/utils/conf/router.go index 9febbeb..632c91e 100644 --- a/utils/conf/router.go +++ b/utils/conf/router.go @@ -37,6 +37,7 @@ type Websocket struct { Host string `yaml:"host"` //connect的域名 User string `yaml:"user"` //认证用户 Pass string `yaml:"pass"` //密码 + Email string `yaml:"email"` //邮箱 Subscribe []Subscribe `yaml:"subscribe"` //订阅信息 } From 6d8db01f6827744f448ff4a4ef297177e104b3f4 Mon Sep 17 00:00:00 2001 From: liminggui Date: Sat, 17 Apr 2021 00:57:06 +0800 Subject: [PATCH 29/32] =?UTF-8?q?=E5=8A=A8=E6=80=81=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E9=87=8D=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- anyproxy.go | 2 +- conf/router.yaml | 42 +++++++++++++++++++------------- go.mod | 1 + go.sum | 4 ++++ nat/conn.go | 11 ++++++--- proto/tunnel.go | 8 +++---- proto/websocket.go | 2 +- scripts/build.sh | 21 +++++++++++----- tunnel/tunneld.go | 2 +- utils/conf/config.go | 57 ++++++++++++++++++++++++++++++++++++++++++-- utils/conf/router.go | 33 ++++++++++++------------- utils/help/help.go | 15 ++++++++---- 13 files changed, 144 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index 49a9fd9..d1a174f 100644 --- a/README.md +++ b/README.md @@ -174,8 +174,8 @@ sudo iptables -t nat -D OUTPUT 2 * TCP 转发的mysql的连接请求会一直卡住 * 与Tunnel的多账户认证,账户可设置有效期 * HTTP/1.1 keep-alive后端也能复用tcp -* 启用ws-listen后的平滑重启问题 -* 监听配置文件变化重新加载路由 +* ~~启用ws-listen后的平滑重启问题~~ +* ~~监听配置文件变化重新加载路由~~ # 感谢 diff --git a/anyproxy.go b/anyproxy.go index 4a891ba..ae29447 100644 --- a/anyproxy.go +++ b/anyproxy.go @@ -79,7 +79,7 @@ func main() { logging.SetDefaultLogger(logDir, cmdName, true, 3, writer) // 设置代理 - gProxyServerSpec = config.IfEmptyThen(gProxyServerSpec, conf.RouterConfig.Proxy, "") + gProxyServerSpec = config.IfEmptyThen(gProxyServerSpec, conf.RouterConfig.Default.Proxy, "") config.SetProxyServer(gProxyServerSpec) // 调试模式 diff --git a/conf/router.yaml b/conf/router.yaml index d489682..5e242ef 100644 --- a/conf/router.yaml +++ b/conf/router.yaml @@ -1,18 +1,30 @@ -log: - dir: ./logs/ -# 使用的DNS服务器 local 当前环境, remote远程, 仅当target使用remote有效 -dns: local -# 默认环境,local 当前环境, remote 远程, deny 禁止 -# auto根据dial选择,local dial失败则remote -target: auto -tcpTarget: remote -# 默认域名比对方案 -match: equal -# 全局代理服务器, 优先级低于启动传参 -proxy: # 监听端口IP, 优先级低于启动传参 listen: -# 域名 +# 日志目录 +log: + dir: ./logs/ +# 监听配置文件变化 +watcher: true +# anyproxy 和 tunnel通信密钥, 必须16位长度 +#token: anyproxyproxyany +# 可访问的客户端IP,为空不限制 +#allowIP: + +# 默认操作,可热加载 +default: + # 使用的DNS服务器 local 当前环境, remote远程, 仅当target使用remote有效 + dns: local + # 默认环境,local 当前环境, remote 远程, deny 禁止 + # auto根据dial选择,local dial失败则remote + target: auto + # tcp 请求环境,local 当前环境, remote 远程, deny 禁止 + tcpTarget: remote + # 默认域名比对方案,contain 包含,equal 完全相等, preg 正则 + match: equal + # 全局代理服务器, 优先级低于启动传参 + proxy: + +# 域名,可热加载 hosts: - name: github # contain 包含,equal 完全相等, preg 正则 @@ -36,10 +48,6 @@ hosts: target: deny - name: dev.example.com ip: 127.0.0.1 -# anyproxy 和 tunnel通信密钥, 必须16位长度 -#token: anyproxyproxyany -# 可访问的客户端IP,为空不限制 -#allowIP: #websocket配置 websocket: diff --git a/go.mod b/go.mod index 8ef34dd..5cee9e3 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/keminar/anyproxy go 1.12 require ( + github.com/fsnotify/fsnotify v1.4.9 github.com/gorilla/websocket v1.4.2 golang.org/x/net v0.0.0-20200602114024-627f9648deb9 gopkg.in/yaml.v2 v2.3.0 diff --git a/go.sum b/go.sum index e665047..9fed637 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,13 @@ +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM= golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= diff --git a/nat/conn.go b/nat/conn.go index 884091d..a0c6a85 100644 --- a/nat/conn.go +++ b/nat/conn.go @@ -67,9 +67,14 @@ func NewServer(addr *string) { }) log.Println(fmt.Sprintf("Listening for websocket connections on %s", *addr)) - err := http.ListenAndServe(*addr, nil) - if err != nil { - log.Fatal("ListenAndServe: ", err) + + for { + // 副服务,出错不退出并定时重试。方便主服务做平滑重启 + err := http.ListenAndServe(*addr, nil) + if err != nil { + log.Println("ListenAndServe: ", err) + } + time.Sleep(10 * time.Second) } } diff --git a/proto/tunnel.go b/proto/tunnel.go index 4b25600..fe7bd48 100644 --- a/proto/tunnel.go +++ b/proto/tunnel.go @@ -220,7 +220,7 @@ func (s *tunnel) lookup(dstName, dstIP string) (string, cache.DialState) { // 查询配置 func findHost(dstName, dstIP string) conf.Host { for _, h := range conf.RouterConfig.Hosts { - confMatch := getString(h.Match, conf.RouterConfig.Match, "equal") + confMatch := getString(h.Match, conf.RouterConfig.Default.Match, "equal") switch confMatch { case "equal": if h.Name == dstName || h.Name == dstIP { @@ -258,11 +258,11 @@ func (s *tunnel) handshake(proto string, dstName, dstIP string, dstPort uint16) host := findHost(dstName, dstIP) var confTarget string if proto == protoTCP { - confTarget = getString(host.Target, conf.RouterConfig.TCPTarget, "auto") + confTarget = getString(host.Target, conf.RouterConfig.Default.TCPTarget, "auto") } else { - confTarget = getString(host.Target, conf.RouterConfig.Target, "auto") + confTarget = getString(host.Target, conf.RouterConfig.Default.Target, "auto") } - confDNS := getString(host.DNS, conf.RouterConfig.DNS, "local") + confDNS := getString(host.DNS, conf.RouterConfig.Default.DNS, "local") // tcp 请求,如果是解析的IP被禁(代理端也无法telnet),不知道域名又无法使用远程dns解析,只能手动换ip // 如golang.org 解析为180.97.235.30 不通,配置改为 216.239.37.1就行 diff --git a/proto/websocket.go b/proto/websocket.go index 3cf1fa1..665d390 100644 --- a/proto/websocket.go +++ b/proto/websocket.go @@ -40,7 +40,7 @@ func (s *wsTunnel) getTarget(dstName string) (ok bool) { } host := findHost(dstName, dstName) var confTarget string - confTarget = getString(host.Target, conf.RouterConfig.Target, "auto") + confTarget = getString(host.Target, conf.RouterConfig.Default.Target, "auto") if confTarget == "deny" { return false diff --git a/scripts/build.sh b/scripts/build.sh index 85d9215..d7e5418 100644 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -5,29 +5,38 @@ cd $ROOT_DIR mkdir -p dist/ +# 版本号 +VER="1.0" +GOVER=`go version` +COMMIT_SHA1=`git rev-parse HEAD` +HELP_PRE="github.com/keminar/anyproxy/utils/help" +LDFLAGS="-X '${HELP_PRE}.goVersion=${GOVER}'" +LDFLAGS="${LDFLAGS} -X '${HELP_PRE}.gitHash=${COMMIT_SHA1}'" +LDFLAGS="${LDFLAGS} -X '${HELP_PRE}.version=${VER}'" + # anyproxy echo "build anyproxy" # for linux echo " for linux" -CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o dist/anyproxy anyproxy.go +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$LDFLAGS" -o dist/anyproxy-${VER} anyproxy.go # for mac echo " for mac" -CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o dist/anyproxy-darwin anyproxy.go +CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "$LDFLAGS" -o dist/anyproxy-darwin-${VER} anyproxy.go # for windows echo " for windows" -CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o dist/anyproxy-windows.exe anyproxy.go +CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "$LDFLAGS" -o dist/anyproxy-windows-${VER}.exe anyproxy.go # for alpine echo " for alpine" -CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -tags netgo -o dist/anyproxy-alpine anyproxy.go +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -tags netgo -ldflags "$LDFLAGS" -o dist/anyproxy-alpine-${VER} anyproxy.go # tunneld echo "build tunneld" echo " for linux" -CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o dist/tunneld tunnel/tunneld.go +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$LDFLAGS" -o dist/tunneld-${VER} tunnel/tunneld.go # for alpine echo " for alpine" -CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -tags netgo -o dist/tunneld-alpine tunnel/tunneld.go \ No newline at end of file +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -tags netgo -ldflags "$LDFLAGS" -o dist/tunneld-alpine-${VER} tunnel/tunneld.go \ No newline at end of file diff --git a/tunnel/tunneld.go b/tunnel/tunneld.go index 59c6a27..7fb91fe 100644 --- a/tunnel/tunneld.go +++ b/tunnel/tunneld.go @@ -68,7 +68,7 @@ func main() { logging.SetDefaultLogger(logDir, cmdName, true, 3, writer) // 设置代理 - gProxyServerSpec = config.IfEmptyThen(gProxyServerSpec, conf.RouterConfig.Proxy, "") + gProxyServerSpec = config.IfEmptyThen(gProxyServerSpec, conf.RouterConfig.Default.Proxy, "") config.SetProxyServer(gProxyServerSpec) // 与anyproxy不同之处在ServerHandler diff --git a/utils/conf/config.go b/utils/conf/config.go index cb21a70..3d4526e 100644 --- a/utils/conf/config.go +++ b/utils/conf/config.go @@ -3,6 +3,8 @@ package conf import ( "fmt" "log" + + "github.com/fsnotify/fsnotify" ) // RouterConfig 配置 @@ -10,10 +12,61 @@ var RouterConfig *Router // LoadAllConfig 加载顺序要求,不写成init func LoadAllConfig() { - conf, err := LoadRouterConfig("router") + filePath, err := GetPath("router.yaml") + if err != nil { + log.Println(fmt.Sprintf("config file %s path err:%s", "router", err.Error())) + return + } + conf, err := LoadRouterConfig(filePath) if err != nil { - log.Println(fmt.Sprintf("yaml.load: %s loadYaml err:%s", "router", err.Error())) + log.Println(fmt.Sprintf("config file %s load err:%s", "router", err.Error())) return } RouterConfig = &conf + if conf.Watcher { + go notify(filePath) + } +} + +func notify(filePath string) { + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Println("config new notify watcher err", err) + return + } + defer watcher.Close() + + done := make(chan bool) + go func() { + defer close(done) + for { + select { + case event, ok := <-watcher.Events: + if !ok { + return + } + if event.Op&fsnotify.Write == fsnotify.Write { + conf, err := LoadRouterConfig(filePath) + if err != nil { + log.Println(fmt.Sprintf("config file %s load err:%s", "router", err.Error())) + } else { + RouterConfig = &conf + log.Println("config file reloaded:", filePath) + } + } + case err, ok := <-watcher.Errors: + if !ok { + return + } + log.Println("config notify watcher error:", err) + } + } + }() + + err = watcher.Add(filePath) + if err != nil { + log.Println("config notify add file err", err) + return + } + <-done } diff --git a/utils/conf/router.go b/utils/conf/router.go index 632c91e..57f1a4e 100644 --- a/utils/conf/router.go +++ b/utils/conf/router.go @@ -41,38 +41,39 @@ type Websocket struct { Subscribe []Subscribe `yaml:"subscribe"` //订阅信息 } +// Default 域名 +type Default struct { + Match string `yaml:"match"` //默认域名比对 + Target string `yaml:"target"` //http默认访问策略 + DNS string `yaml:"dns"` //默认的DNS服务器 + Proxy string `yaml:"proxy"` //全局代理服务器 + TCPTarget string `yaml:"tcpTarget"` //tcp默认访问策略 +} + // Router 配置文件模型 type Router struct { Listen string `yaml:"listen"` //监听端口 Log Log `yaml:"log"` //日志目录 - Token string `yaml:"token"` //加密值 - DNS string `yaml:"dns"` //默认的DNS服务器 - Target string `yaml:"target"` //http默认访问策略 - TCPTarget string `yaml:"tcpTarget"` //tcp默认访问策略 - Match string `yaml:"match"` //默认域名比对 - Proxy string `yaml:"proxy"` //全局代理服务器 + Watcher bool `yaml:"watcher"` //是否监听配置文件变化 + Token string `yaml:"token"` //加密值, 和tunnel通信密钥, 必须16位长度 + Default Default `yaml:"default"` //默认配置 Hosts []Host `yaml:"hosts"` //域名列表 AllowIP []string `yaml:"allowIP"` //可以访问的客户端IP Websocket Websocket `yaml:"websocket"` //会话订阅请求信息 } // LoadRouterConfig 加载配置 -func LoadRouterConfig(configName string) (cnf Router, err error) { - configPath, err := getPath(configName + ".yaml") - if err != nil { - return cnf, err - } +func LoadRouterConfig(configPath string) (cnf Router, err error) { data, err := ioutil.ReadFile(configPath) if err != nil { - return cnf, err + return } - t := Router{} - err = yaml.Unmarshal(data, &t) - return t, err + err = yaml.Unmarshal(data, &cnf) + return } // 获取文件路径 -func getPath(filename string) (string, error) { +func GetPath(filename string) (string, error) { // 当前登录用户所在目录 workPath, err := os.Getwd() if err != nil { diff --git a/utils/help/help.go b/utils/help/help.go index e06a6bd..fab2ca0 100644 --- a/utils/help/help.go +++ b/utils/help/help.go @@ -8,13 +8,16 @@ import ( "time" ) -//VERSION 版本 -const VERSION = "0.9" +var ( + version string + gitHash string + goVersion string +) // Usage 帮助 func Usage() { fmt.Fprintf(os.Stdout, "%s\n\n", versionString("anyproxy")) - fmt.Fprintf(os.Stdout, "usage: %s -l listenaddress -p proxies \n", os.Args[0]) + fmt.Fprintf(os.Stdout, "Usage: %s -l listenaddress -p proxies \n", os.Args[0]) fmt.Fprintf(os.Stdout, " Proxies any tcp port transparently using Linux netfilter\n\n") fmt.Fprintf(os.Stdout, "Mandatory\n") fmt.Fprintf(os.Stdout, " -l=ADDRPORT Address and port to listen on (e.g., :3000 or 127.0.0.1:3000)\n") @@ -57,7 +60,7 @@ func Usage() { // TunnelUsage 帮助 func TunnelUsage() { fmt.Fprintf(os.Stdout, "%s\n\n", versionString("tunneld")) - fmt.Fprintf(os.Stdout, "usage: %s -l listenaddress -p proxies \n", os.Args[0]) + fmt.Fprintf(os.Stdout, "Usage: %s -l listenaddress -p proxies \n", os.Args[0]) fmt.Fprintf(os.Stdout, " Proxies anyproxy reqest\n\n") fmt.Fprintf(os.Stdout, "Mandatory\n") fmt.Fprintf(os.Stdout, " -l=ADDRPORT Address and port to listen on (e.g., :3001 or 127.0.0.1:3001)\n") @@ -77,6 +80,8 @@ func versionString(name string) (v string) { now := time.Now().Unix() buildNum := strings.ToUpper(strconv.FormatInt(now, 36)) buildDate := time.Unix(now, 0).Format(time.UnixDate) - v = fmt.Sprintf("%s %s (build %v, %v)", name, VERSION, buildNum, buildDate) + v = fmt.Sprintf("%s %s (build %v, %v)", name, version, buildNum, buildDate) + v += fmt.Sprintf("\nGit Commit Hash: %s", gitHash) + v += fmt.Sprintf("\nGoLang Version: %s", goVersion) return } From bed12c8fcb04815d5e7abb919573e5a2f6048f85 Mon Sep 17 00:00:00 2001 From: liminggui Date: Sat, 17 Apr 2021 01:13:05 +0800 Subject: [PATCH 30/32] fix dstIp is empty --- proto/tunnel.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/proto/tunnel.go b/proto/tunnel.go index fe7bd48..4ad45b4 100644 --- a/proto/tunnel.go +++ b/proto/tunnel.go @@ -337,10 +337,12 @@ func (s *tunnel) handshake(proto string, dstName, dstIP string, dstPort uint16) return } } else { - if dstIP == "" { - err = errors.New("dstIP is empty") - } else { + if dstIP != "" { err = s.dail(dstIP, dstPort) + } else if dstName != "" { + err = s.dail(dstName, dstPort) + } else { + err = errors.New("dstName && dstIP is empty") } } if err != nil { From a874452f833b4b2a277874509df944e4d9473d18 Mon Sep 17 00:00:00 2001 From: liminggui Date: Sat, 17 Apr 2021 14:09:55 +0800 Subject: [PATCH 31/32] =?UTF-8?q?=E7=89=88=E6=9C=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nat/conn.go | 5 +++++ scripts/build.sh | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/nat/conn.go b/nat/conn.go index a0c6a85..24f8ff2 100644 --- a/nat/conn.go +++ b/nat/conn.go @@ -68,13 +68,18 @@ func NewServer(addr *string) { log.Println(fmt.Sprintf("Listening for websocket connections on %s", *addr)) + i := 0 for { // 副服务,出错不退出并定时重试。方便主服务做平滑重启 err := http.ListenAndServe(*addr, nil) if err != nil { log.Println("ListenAndServe: ", err) } + if i > 1000 { + break + } time.Sleep(10 * time.Second) + i++ } } diff --git a/scripts/build.sh b/scripts/build.sh index d7e5418..0755e3f 100644 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -6,7 +6,7 @@ cd $ROOT_DIR mkdir -p dist/ # 版本号 -VER="1.0" +VER=`git describe --tags $(git rev-list --tags --max-count=1)` GOVER=`go version` COMMIT_SHA1=`git rev-parse HEAD` HELP_PRE="github.com/keminar/anyproxy/utils/help" From 22338fe6f55fcd942042b1b4a5d15ca0fcbfe369 Mon Sep 17 00:00:00 2001 From: liminggui Date: Sat, 17 Apr 2021 16:49:02 +0800 Subject: [PATCH 32/32] =?UTF-8?q?=E5=90=88=E5=B9=B6tunnel=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makefile | 13 ++++++++ README.md | 18 +++++------ anyproxy.go | 31 ++++++++++++------- conf/router.yaml | 2 +- nat/conn.go | 9 ++---- scripts/build.sh | 64 +++++++++++++++++++++----------------- tunnel/tunneld.go | 77 ---------------------------------------------- utils/help/help.go | 20 +----------- 8 files changed, 79 insertions(+), 155 deletions(-) create mode 100644 Makefile delete mode 100644 tunnel/tunneld.go diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..807c23c --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ + +linux: + bash ./scripts/build.sh linux +mac: + bash ./scripts/build.sh mac +windows: + bash ./scripts/build.sh windows +alpine: + bash ./scripts/build.sh alpine +all: + bash ./scripts/build.sh all +clean: + rm -rf dist/* \ No newline at end of file diff --git a/README.md b/README.md index d1a174f..34f3226 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,7 @@ anyproxy 是一个部署在Linux系统上的tcp流转发器,可以直接将本地或网络收到的请求发出,也可以将请求转到tunneld或SOCKS或charles等代理。可以代替Proxifier做Linux下的客户端, 也可以配合Proxifier当它的服务端。经过跨平台编译,如果只做网络包的转发可以在windows等平台使用。 -[下载Linux包](http://cloudme.io/anyproxy) 、 [下载Mac包](http://cloudme.io/anyproxy-darwin) 、 -[下载Windows包](http://cloudme.io/anyproxy-windows.exe) 、 [下载alpine包](http://cloudme.io/anyproxy-alpine) +[下载Linux包](http://cloudme.io/anyproxy) 、 [下载Mac包](http://cloudme.io/anyproxy-darwin) 、 [下载Windows包](http://cloudme.io/anyproxy-windows.exe) 提醒:请使用浏览器右键的“链接另存为”下载文件 @@ -42,8 +41,6 @@ tunneld 是一个anyproxy的服务端,部署在服务器上接收anyproxy的 `使用iptables将本用户下tcp流转到anyproxy,再进行docker pull操作` -![解决Docker pull问题](examples/docker_pull.png) - > 案例2: 解决相同域名访问网站不同测试环境的问题 `本地通过内网 anyproxy 代理上网,遇到测试服务器域名则跳到外网tunneld转发,网站的nginx根据来源IP进行转发到特定测试环境(有几个环境就需要有几个tunneld服务且IP要不同)` @@ -68,7 +65,7 @@ go env -w GOPROXY=https://goproxy.cn,direct ``` git clone https://github.com/keminar/anyproxy.git cd anyproxy -go build anyproxy.git +make all ``` > 本机启动 @@ -81,10 +78,10 @@ sudo -u anyproxy ./anyproxy ./anyproxy -daemon # 示例3. 启动tunneld -./tunneld +./anyproxy -mode tunnel # 示例4. 启动anyproxy并将请求转给tunneld -./anyproxy -p '127.0.0.1:3001' +./anyproxy -p 'tunnel://127.0.0.1:3001' # 示例5. 启动anyproxy并将请求转给socks5 ./anyproxy -p 'socks5://127.0.0.1:10000' @@ -170,12 +167,11 @@ sudo iptables -t nat -D OUTPUT 2 * ~~支持windows平台使用~~ * ~~通过websocket实现内网穿透(必须为http的非CONNECT请求)~~ * ~~订阅增加邮箱标识,用于辨别在线用户~~ -* TCP 增加更多协议解析支持,如rtmp,ftp, socks5, https(SNI)等 -* TCP 转发的mysql的连接请求会一直卡住 -* 与Tunnel的多账户认证,账户可设置有效期 -* HTTP/1.1 keep-alive后端也能复用tcp +* ~~与Tunnel功能合并,使用mode区分~~ * ~~启用ws-listen后的平滑重启问题~~ * ~~监听配置文件变化重新加载路由~~ +* TCP 增加更多协议解析支持,如rtmp,ftp, socks5, https(SNI)等 +* TCP 转发的mysql的连接请求会一直卡住 # 感谢 diff --git a/anyproxy.go b/anyproxy.go index ae29447..c1f2151 100644 --- a/anyproxy.go +++ b/anyproxy.go @@ -8,6 +8,7 @@ import ( "net/http" _ "net/http/pprof" "os" + "time" "github.com/keminar/anyproxy/config" "github.com/keminar/anyproxy/grace" @@ -25,6 +26,7 @@ var ( gProxyServerSpec string gWebsocketListen string gWebsocketConn string + gMode string gHelp bool gDebug int gPprof string @@ -36,10 +38,10 @@ func init() { flag.StringVar(&gProxyServerSpec, "p", "", "Proxy servers to use") flag.StringVar(&gWebsocketListen, "ws-listen", "", "Websocket address and port to listen on") flag.StringVar(&gWebsocketConn, "ws-connect", "", "Websocket Address and port to connect") + flag.StringVar(&gMode, "mode", "", "Run mode(proxy, tunnel). proxy mode default") flag.IntVar(&gDebug, "debug", 0, "debug mode (0, 1, 2)") flag.StringVar(&gPprof, "pprof", "", "pprof port, disable if empty") flag.BoolVar(&gHelp, "h", false, "This usage message") - } func main() { @@ -57,10 +59,7 @@ func main() { } cmdName := "anyproxy" - logDir := "./logs/" - if conf.RouterConfig.Log.Dir != "" { - logDir = conf.RouterConfig.Log.Dir - } + logDir := config.IfEmptyThen(conf.RouterConfig.Log.Dir, "./logs/", "") envRunMode := fmt.Sprintf("%s_run_mode", cmdName) fd := logging.ErrlogFd(logDir, cmdName) // 是否后台运行 @@ -89,23 +88,33 @@ func main() { //浏览器访问: http://:5001/debug/pprof/ log.Println("Starting pprof debug server ...") // 这里不要使用log.Fatal会在平滑重启时导致进程退出 - // 因为http server现在没办法加入平滑重启,第一次重启会报端口冲突,可以通过重启两次来启动到pprof - log.Println(http.ListenAndServe(gPprof, nil)) + // 因为http server现在没办法一次平滑重启,会报端口冲突,可以通过多次重试来启动pprof + for i := 0; i < 10; i++ { + log.Println(http.ListenAndServe(gPprof, nil)) + time.Sleep(10 * time.Second) + } }() } - // websocket 配置 + // websocket 服务端 gWebsocketListen = config.IfEmptyThen(gWebsocketListen, conf.RouterConfig.Websocket.Listen, "") if gWebsocketListen != "" { gWebsocketListen = tools.FillPort(gWebsocketListen) go nat.NewServer(&gWebsocketListen) } - + // websocket 客户端 gWebsocketConn = config.IfEmptyThen(gWebsocketConn, conf.RouterConfig.Websocket.Connect, "") if gWebsocketConn != "" { gWebsocketConn = tools.FillPort(gWebsocketConn) go nat.ConnectServer(&gWebsocketConn) } - server := grace.NewServer(gListenAddrPort, proto.ClientHandler) - server.ListenAndServe() + + // 运行模式 + if gMode == "tunnel" { + server := grace.NewServer(gListenAddrPort, proto.ServerHandler) + server.ListenAndServe() + } else { + server := grace.NewServer(gListenAddrPort, proto.ClientHandler) + server.ListenAndServe() + } } diff --git a/conf/router.yaml b/conf/router.yaml index 5e242ef..e5018d6 100644 --- a/conf/router.yaml +++ b/conf/router.yaml @@ -6,7 +6,7 @@ log: # 监听配置文件变化 watcher: true # anyproxy 和 tunnel通信密钥, 必须16位长度 -#token: anyproxyproxyany +token: anyproxyproxyany # 可访问的客户端IP,为空不限制 #allowIP: diff --git a/nat/conn.go b/nat/conn.go index 24f8ff2..001e7de 100644 --- a/nat/conn.go +++ b/nat/conn.go @@ -68,18 +68,13 @@ func NewServer(addr *string) { log.Println(fmt.Sprintf("Listening for websocket connections on %s", *addr)) - i := 0 - for { + for i := 0; i < 1000; i++ { // 副服务,出错不退出并定时重试。方便主服务做平滑重启 err := http.ListenAndServe(*addr, nil) if err != nil { - log.Println("ListenAndServe: ", err) - } - if i > 1000 { - break + log.Println(fmt.Sprintf("ListenAndServe: num=%d, err=%v ", i, err)) } time.Sleep(10 * time.Second) - i++ } } diff --git a/scripts/build.sh b/scripts/build.sh index 0755e3f..8370eb8 100644 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -5,38 +5,44 @@ cd $ROOT_DIR mkdir -p dist/ +# 路径 +GOCMD="go" +GITCMD="git" + +# 目标文件前缀 +BIN="anyproxy" + # 版本号 -VER=`git describe --tags $(git rev-list --tags --max-count=1)` -GOVER=`go version` -COMMIT_SHA1=`git rev-parse HEAD` +ARCH="amd64" + +#组装变量 +GOBUILD="${GOCMD} build" +VER=`${GITCMD} describe --tags $(${GITCMD} rev-list --tags --max-count=1)` +GOVER=`${GOCMD} version` +COMMIT_SHA1=`${GITCMD} rev-parse HEAD` HELP_PRE="github.com/keminar/anyproxy/utils/help" LDFLAGS="-X '${HELP_PRE}.goVersion=${GOVER}'" LDFLAGS="${LDFLAGS} -X '${HELP_PRE}.gitHash=${COMMIT_SHA1}'" LDFLAGS="${LDFLAGS} -X '${HELP_PRE}.version=${VER}'" -# anyproxy -echo "build anyproxy" -# for linux -echo " for linux" -CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$LDFLAGS" -o dist/anyproxy-${VER} anyproxy.go - -# for mac -echo " for mac" -CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "$LDFLAGS" -o dist/anyproxy-darwin-${VER} anyproxy.go - -# for windows -echo " for windows" -CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "$LDFLAGS" -o dist/anyproxy-windows-${VER}.exe anyproxy.go - -# for alpine -echo " for alpine" -CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -tags netgo -ldflags "$LDFLAGS" -o dist/anyproxy-alpine-${VER} anyproxy.go - -# tunneld -echo "build tunneld" -echo " for linux" -CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$LDFLAGS" -o dist/tunneld-${VER} tunnel/tunneld.go - -# for alpine -echo " for alpine" -CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -tags netgo -ldflags "$LDFLAGS" -o dist/tunneld-alpine-${VER} tunnel/tunneld.go \ No newline at end of file +# 编译 +echo "build ..." +if [ "$1" == "all" ] || [ "$1" == "linux" ] ;then + echo " for linux" + CGO_ENABLED=0 GOOS=linux GOARCH=${ARCH} ${GOBUILD} -ldflags "$LDFLAGS" -o dist/${BIN}-${ARCH}-${VER} anyproxy.go +fi + +if [ "$1" == "all" ] || [ "$1" == "mac" ] ;then + echo " for mac" + CGO_ENABLED=0 GOOS=darwin GOARCH=${ARCH} ${GOBUILD} -ldflags "$LDFLAGS" -o dist/${BIN}-darwin-${ARCH}-${VER} anyproxy.go +fi + +if [ "$1" == "all" ] || [ "$1" == "windows" ] ;then + echo " for windows" + CGO_ENABLED=0 GOOS=windows GOARCH=${ARCH} ${GOBUILD} -ldflags "$LDFLAGS" -o dist/${BIN}-windows-${ARCH}-${VER}.exe anyproxy.go +fi + +if [ "$1" == "all" ] || [ "$1" == "alpine" ] ;then + echo " for alpine" + CGO_ENABLED=0 GOOS=linux GOARCH=${ARCH} ${GOBUILD} -tags netgo -ldflags "$LDFLAGS" -o dist/${BIN}-alpine-${ARCH}-${VER} anyproxy.go +fi \ No newline at end of file diff --git a/tunnel/tunneld.go b/tunnel/tunneld.go deleted file mode 100644 index 7fb91fe..0000000 --- a/tunnel/tunneld.go +++ /dev/null @@ -1,77 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "io" - "os" - - "github.com/keminar/anyproxy/config" - "github.com/keminar/anyproxy/grace" - "github.com/keminar/anyproxy/logging" - "github.com/keminar/anyproxy/proto" - "github.com/keminar/anyproxy/utils/conf" - "github.com/keminar/anyproxy/utils/daemon" - "github.com/keminar/anyproxy/utils/help" - "github.com/keminar/anyproxy/utils/tools" -) - -var ( - gListenAddrPort string - gProxyServerSpec string - gHelp bool - gDebug int -) - -func init() { - flag.Usage = help.TunnelUsage - flag.StringVar(&gListenAddrPort, "l", "", "Address and port to listen on") - flag.StringVar(&gProxyServerSpec, "p", "", "Proxy servers to use") - //CONNECT请求不支持ws-listen 和 ws-connect,且tunnel只做接收anyproxy的安全转发,不需要支持ws - flag.IntVar(&gDebug, "debug", 0, "debug mode (0, 1, 2)") - flag.BoolVar(&gHelp, "h", false, "This usage message") -} - -func main() { - flag.Parse() - if gHelp { - flag.Usage() - return - } - - config.SetDebugLevel(gDebug) - conf.LoadAllConfig() - // 检查配置是否存在 - if conf.RouterConfig == nil { - os.Exit(2) - } - - cmdName := "tunneld" - logDir := "./logs/" - if conf.RouterConfig.Log.Dir != "" { - logDir = conf.RouterConfig.Log.Dir - } - envRunMode := fmt.Sprintf("%s_run_mode", cmdName) - fd := logging.ErrlogFd(logDir, cmdName) - // 是否后台运行 - daemon.Daemonize(envRunMode, fd) - - gListenAddrPort = config.IfEmptyThen(gListenAddrPort, conf.RouterConfig.Listen, ":3001") - gListenAddrPort = tools.FillPort(gListenAddrPort) - - var writer io.Writer - // 前台执行,daemon运行根据环境变量识别 - if daemon.IsForeground(envRunMode) { - // 同时输出到日志和标准输出 - writer = io.Writer(os.Stdout) - } - - logging.SetDefaultLogger(logDir, cmdName, true, 3, writer) - // 设置代理 - gProxyServerSpec = config.IfEmptyThen(gProxyServerSpec, conf.RouterConfig.Default.Proxy, "") - config.SetProxyServer(gProxyServerSpec) - - // 与anyproxy不同之处在ServerHandler - server := grace.NewServer(gListenAddrPort, proto.ServerHandler) - server.ListenAndServe() -} diff --git a/utils/help/help.go b/utils/help/help.go index fab2ca0..2bde782 100644 --- a/utils/help/help.go +++ b/utils/help/help.go @@ -28,6 +28,7 @@ func Usage() { fmt.Fprintf(os.Stdout, " -ws-listen Websocket address and port to listen on\n") fmt.Fprintf(os.Stdout, " -ws-connect Websocket Address and port to connect\n") fmt.Fprintf(os.Stdout, " -daemon Run as a Unix daemon\n") + fmt.Fprintf(os.Stdout, " -mode Run mode(proxy, tunnel). proxy mode default\n") fmt.Fprintf(os.Stdout, " -debug Debug mode (0, 1, 2, 3)\n") fmt.Fprintf(os.Stdout, " -pprof Pprof port, disable if empty\n") fmt.Fprintf(os.Stdout, " -h This usage message\n\n") @@ -57,25 +58,6 @@ func Usage() { fmt.Fprintf(os.Stdout, "Thanks to https://github.com/ryanchapman/go-any-proxy.git\n") } -// TunnelUsage 帮助 -func TunnelUsage() { - fmt.Fprintf(os.Stdout, "%s\n\n", versionString("tunneld")) - fmt.Fprintf(os.Stdout, "Usage: %s -l listenaddress -p proxies \n", os.Args[0]) - fmt.Fprintf(os.Stdout, " Proxies anyproxy reqest\n\n") - fmt.Fprintf(os.Stdout, "Mandatory\n") - fmt.Fprintf(os.Stdout, " -l=ADDRPORT Address and port to listen on (e.g., :3001 or 127.0.0.1:3001)\n") - fmt.Fprintf(os.Stdout, "Optional\n") - fmt.Fprintf(os.Stdout, " -p=PROXIES Address and ports of upstream proxy servers to use\n") - fmt.Fprintf(os.Stdout, " (e.g., 10.1.1.1:80 will use http proxy, socks5://10.2.2.2:3128 use socks5 proxy,\n") - fmt.Fprintf(os.Stdout, " tunnel://10.2.2.2:3001 use tunnel proxy)\n") - fmt.Fprintf(os.Stdout, " -daemon Run as a Unix daemon\n") - fmt.Fprintf(os.Stdout, " -debug Debug mode (0, 1, 2, 3)\n") - fmt.Fprintf(os.Stdout, " -h This usage message\n\n") - - fmt.Fprintf(os.Stdout, "Report bugs to https://github.com/keminar/anyproxy or .\n") - fmt.Fprintf(os.Stdout, "Thanks to https://github.com/ryanchapman/go-any-proxy.git\n") -} - func versionString(name string) (v string) { now := time.Now().Unix() buildNum := strings.ToUpper(strconv.FormatInt(now, 36))