├── .gitignore ├── LICENSE.txt ├── README.md ├── echo_server.go ├── example.sh ├── go.mod └── tcptee.go /.gitignore: -------------------------------------------------------------------------------- 1 | .*.sw[opn] 2 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) 2023, hit9 3 | 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without modification, 7 | are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | * Neither the name of tcptee nor the names of its contributors 15 | may be used to endorse or promote products derived from this software 16 | without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 22 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 23 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 24 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 25 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 26 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 27 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | tcptee 2 | ====== 3 | 4 | tcptee is a simple tcp traffic duplicator. 5 | 6 | Usage 7 | ----- 8 | 9 | ./tcptee -bind :8000 -backends :2015,:2016,:2017 10 | 11 | Example 12 | ------- 13 | 14 | go run echo_server.go -bind :8001 15 | go run echo_server.go -bind :8002 16 | go run tcptee.go -bind :8000 -backends :8001,:8002 17 | echo 'Hello world' | nc 127.0.0.1 8000 18 | 19 | Or run this script: [example.sh](./example.sh) 20 | 21 | License 22 | ------- 23 | 24 | BSD. 25 | -------------------------------------------------------------------------------- /echo_server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Chao Wang 2 | 3 | // +build ignore 4 | 5 | // Simple echo server. 6 | package main 7 | 8 | import ( 9 | "flag" 10 | "io" 11 | "log" 12 | "net" 13 | "os" 14 | ) 15 | 16 | func main() { 17 | bind := flag.String("bind", ":8001", "address to bind") 18 | flag.Parse() 19 | ln, err := net.Listen("tcp", *bind) 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | for { 24 | conn, err := ln.Accept() 25 | if err != nil { 26 | log.Fatal(err) 27 | } 28 | go io.Copy(os.Stderr, conn) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /example.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | go run echo_server.go -bind :8001 & 4 | go run echo_server.go -bind :8002 & 5 | go run echo_server.go -bind :8003 & 6 | go run echo_server.go -bind :8004 & 7 | go run tcptee.go -bind :8000 -backends :8001,:8002,:8003,:8004 & 8 | sleep 2 9 | echo 'All backend echo_servers should echo this message!' | nc -w0 127.0.0.1 8000 10 | kill $(lsof -i:8000 -i:8001 -i:8002 -i:8003 -i:8004 -t) 11 | sleep 2 12 | jobs 13 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hit9/tcptee 2 | 3 | go 1.19 4 | -------------------------------------------------------------------------------- /tcptee.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Chao Wang 2 | 3 | // Package main implements a tcp tee. 4 | // Usage: ./tcptee -bind :8000 -backends :2015,:2016,:2017 5 | package main 6 | 7 | import ( 8 | "flag" 9 | "io" 10 | "log" 11 | "net" 12 | "strings" 13 | ) 14 | 15 | // Backends is the backend connections. 16 | type Backends []net.Conn 17 | 18 | // Read implements the io.Reader. 19 | func (b Backends) Read(p []byte) (n int, err error) { 20 | for _, conn := range b { 21 | n, err = conn.Read(p) 22 | if err != nil { 23 | return n, err 24 | } 25 | } 26 | return 27 | } 28 | 29 | // Write implements the io.Writer. 30 | func (b Backends) Write(p []byte) (n int, err error) { 31 | for _, conn := range b { 32 | n, err = conn.Write(p) 33 | if err != nil { 34 | return n, err 35 | } 36 | } 37 | return 38 | } 39 | 40 | // Tee is the tee handle. 41 | type Tee struct { 42 | ln net.Listener 43 | laddr string // server addr 44 | addrs []string // backend addrs 45 | } 46 | 47 | // New creates a new Tee. 48 | func New(laddr string, addrs []string) *Tee { 49 | return &Tee{laddr: laddr, addrs: addrs} 50 | } 51 | 52 | // Listen the tee. 53 | func (t *Tee) Listen() (err error) { 54 | t.ln, err = net.Listen("tcp", t.laddr) 55 | if err != nil { 56 | return err 57 | } 58 | log.Printf("tee is listening on %s\n", t.laddr) 59 | return nil 60 | } 61 | 62 | // Serve the tee. 63 | func (t *Tee) Serve() error { 64 | for { 65 | conn, err := t.ln.Accept() 66 | if err != nil { 67 | return err 68 | } 69 | go t.handle(conn) 70 | } 71 | } 72 | 73 | // ListenAndServe is the Listen followed by Serve. 74 | func (t *Tee) ListenAndServe() (err error) { 75 | if err = t.Listen(); err != nil { 76 | return 77 | } 78 | return t.Serve() 79 | } 80 | 81 | // Handle the connection. 82 | func (t *Tee) handle(conn net.Conn) { 83 | // Connect to backends 84 | var backends []net.Conn 85 | for _, addr := range t.addrs { 86 | c, err := net.Dial("tcp", addr) 87 | if err != nil { 88 | log.Println(err) 89 | } 90 | backends = append(backends, c) 91 | } 92 | var err error 93 | _, err = io.Copy(Backends(backends), conn) 94 | if err != nil { 95 | log.Println(err) 96 | } 97 | } 98 | 99 | func main() { 100 | bind := flag.String("bind", ":8000", "address to bind") 101 | backends := flag.String("backends", "", "backends split by comma") 102 | flag.Parse() 103 | if *backends == "" { 104 | log.Fatal("No backends") 105 | } 106 | tr := New(*bind, strings.Split(*backends, ",")) 107 | log.Fatal(tr.ListenAndServe()) 108 | } 109 | --------------------------------------------------------------------------------