├── README.md ├── bursts.go └── main.go /README.md: -------------------------------------------------------------------------------- 1 | # packet-proxy 2 | 3 | A proxy for both UDP and TCP, which saves packets to a file. 4 | 5 | For TCP connections, packets are split up using timing, i.e. bytes that come at around the same time are clustered into a "packet" on file. 6 | -------------------------------------------------------------------------------- /bursts.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net" 5 | "time" 6 | ) 7 | 8 | // ReceiveBursts reads "packets" (as separated by time of 9 | // no data transfer) and sends them to a channel. 10 | func ReceiveBursts(conn *net.TCPConn, maxTime time.Duration) <-chan []byte { 11 | // Use a larger buffer to attempt to prevent backpressure from 12 | // hurting our ability to measure time. 13 | // It can still happen though if there is a lot of data being 14 | // received. 15 | res := make(chan []byte, 64) 16 | go func() { 17 | defer close(res) 18 | rawData := readConnAsChan(conn) 19 | for { 20 | buffer, ok := <-rawData 21 | if !ok { 22 | break 23 | } 24 | ReadMoreLoop: 25 | for { 26 | select { 27 | case data, ok := <-rawData: 28 | if ok { 29 | buffer = append(buffer, data...) 30 | } else { 31 | break ReadMoreLoop 32 | } 33 | case <-time.After(maxTime): 34 | break ReadMoreLoop 35 | } 36 | } 37 | res <- buffer 38 | } 39 | }() 40 | return res 41 | } 42 | 43 | func readConnAsChan(conn *net.TCPConn) <-chan []byte { 44 | rawReads := make(chan []byte, 64) 45 | 46 | // Goroutine to read the raw data. 47 | go func() { 48 | defer close(rawReads) 49 | for { 50 | data := make([]byte, 0x10000) 51 | n, err := conn.Read(data) 52 | if err != nil { 53 | return 54 | } 55 | rawReads <- data[:n] 56 | } 57 | }() 58 | 59 | return rawReads 60 | } 61 | 62 | // WriteOrClose writes a buffer to a socket, and closes the 63 | // socket if the write fails. 64 | func WriteOrClose(conn *net.TCPConn, data []byte) error { 65 | for len(data) > 0 { 66 | n, err := conn.Write(data) 67 | data = data[n:] 68 | if err != nil { 69 | conn.Close() 70 | return err 71 | } 72 | } 73 | return nil 74 | } 75 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "net" 9 | "os" 10 | "path/filepath" 11 | "strconv" 12 | "strings" 13 | "sync" 14 | "time" 15 | 16 | "github.com/unixpickle/essentials" 17 | ) 18 | 19 | type Proxy struct { 20 | Addr string 21 | TargetAddr string 22 | LogDir string 23 | TCPTimeout time.Duration 24 | FileLock sync.Mutex 25 | } 26 | 27 | func main() { 28 | proxy := &Proxy{} 29 | flag.StringVar(&proxy.Addr, "addr", ":23778", "address to listen on") 30 | flag.StringVar(&proxy.TargetAddr, "target", "cm-ge.xlink.cn:23778", "address to proxy to") 31 | flag.StringVar(&proxy.LogDir, "log-dir", "saved-packets", "output directory to dump packets") 32 | flag.DurationVar(&proxy.TCPTimeout, "packet-time", time.Second/4, 33 | "max time between contents of TCP 'packet'") 34 | flag.Parse() 35 | 36 | go proxy.RunUDP() 37 | go proxy.RunTCP() 38 | select {} 39 | } 40 | 41 | func (p *Proxy) RunUDP() { 42 | udpAddr, err := net.ResolveUDPAddr("udp", p.Addr) 43 | essentials.Must(err) 44 | udpConn, err := net.ListenUDP("udp", udpAddr) 45 | essentials.Must(err) 46 | defer udpConn.Close() 47 | 48 | log.Printf("listening on UDP address %s...", udpAddr) 49 | 50 | outgoing := map[string]*net.UDPConn{} 51 | remoteAddr, err := net.ResolveUDPAddr("udp", p.TargetAddr) 52 | essentials.Must(err) 53 | 54 | for { 55 | b := make([]byte, 0x10000) 56 | oob := make([]byte, 0x10000) 57 | n, oobn, _, addr, err := udpConn.ReadMsgUDP(b, oob) 58 | if err != nil { 59 | log.Printf("listener recv UDP error: %s", err) 60 | continue 61 | } 62 | 63 | addrStr := addr.String() 64 | if _, ok := outgoing[addrStr]; !ok { 65 | proxyConn, err := net.DialUDP("udp", nil, remoteAddr) 66 | essentials.Must(err) 67 | outgoing[addrStr] = proxyConn 68 | 69 | go func() { 70 | for { 71 | b := make([]byte, 0x10000) 72 | oob := make([]byte, 0x10000) 73 | n, oobn, _, _, err := proxyConn.ReadMsgUDP(b, oob) 74 | if err != nil { 75 | log.Printf("proxy recv UDP error: %s", err) 76 | continue 77 | } 78 | _, _, err = udpConn.WriteMsgUDP(b[:n], oob[:oobn], addr) 79 | essentials.Must(err) 80 | 81 | p.SavePacket("udp", addr, "out", b[:n]) 82 | if oobn > 0 { 83 | p.SavePacket("udp", addr, "out_oob", oob[:oobn]) 84 | } 85 | } 86 | }() 87 | } 88 | proxyConn := outgoing[addrStr] 89 | _, _, err = proxyConn.WriteMsgUDP(b[:n], oob[:oobn], nil) 90 | essentials.Must(err) 91 | 92 | p.SavePacket("udp", addr, "in", b[:n]) 93 | if oobn > 0 { 94 | p.SavePacket("udp", addr, "in_oob", oob[:oobn]) 95 | } 96 | } 97 | } 98 | 99 | func (p *Proxy) RunTCP() { 100 | tcpAddr, err := net.ResolveTCPAddr("tcp", p.Addr) 101 | essentials.Must(err) 102 | listener, err := net.ListenTCP("tcp", tcpAddr) 103 | essentials.Must(err) 104 | defer listener.Close() 105 | 106 | log.Printf("listening on TCP address %s...", tcpAddr) 107 | 108 | for { 109 | conn, err := listener.AcceptTCP() 110 | essentials.Must(err) 111 | go p.HandleTCP(conn) 112 | } 113 | } 114 | 115 | func (p *Proxy) HandleTCP(conn *net.TCPConn) { 116 | log.Printf("TCP connection established from: %s", conn.RemoteAddr()) 117 | defer log.Printf("TCP connection finished: %s", conn.RemoteAddr()) 118 | 119 | remoteAddr, err := net.ResolveTCPAddr("tcp", p.TargetAddr) 120 | essentials.Must(err) 121 | proxyConn, err := net.DialTCP("tcp", nil, remoteAddr) 122 | essentials.Must(err) 123 | 124 | defer proxyConn.Close() 125 | 126 | var wg sync.WaitGroup 127 | wg.Add(2) 128 | 129 | go func() { 130 | defer wg.Done() 131 | defer proxyConn.Close() 132 | for inPacket := range ReceiveBursts(conn, p.TCPTimeout) { 133 | p.SavePacket("tcp", conn.RemoteAddr(), "in", inPacket) 134 | if WriteOrClose(proxyConn, inPacket) != nil { 135 | return 136 | } 137 | } 138 | }() 139 | 140 | go func() { 141 | defer wg.Done() 142 | defer conn.Close() 143 | for outPacket := range ReceiveBursts(proxyConn, p.TCPTimeout) { 144 | p.SavePacket("tcp", conn.RemoteAddr(), "out", outPacket) 145 | if WriteOrClose(conn, outPacket) != nil { 146 | return 147 | } 148 | } 149 | }() 150 | 151 | wg.Wait() 152 | } 153 | 154 | func (p *Proxy) SavePacket(network string, addr fmt.Stringer, direction string, data []byte) { 155 | log.Printf("packet: net=%s addr=%s direction=%s size=%d", network, addr, direction, len(data)) 156 | 157 | outDir := filepath.Join(p.LogDir, network, addr.String()) 158 | if _, err := os.Stat(outDir); os.IsNotExist(err) { 159 | essentials.Must(os.MkdirAll(outDir, 0755)) 160 | } else { 161 | essentials.Must(err) 162 | } 163 | 164 | // Don't want any other writer to conflict to find the 165 | // next file index. 166 | p.FileLock.Lock() 167 | defer p.FileLock.Unlock() 168 | 169 | listing, err := ioutil.ReadDir(outDir) 170 | essentials.Must(err) 171 | nextIdx := 0 172 | for _, entry := range listing { 173 | name := entry.Name() 174 | parts := strings.Split(name, "_") 175 | if len(parts) != 2 { 176 | continue 177 | } 178 | x, err := strconv.Atoi(parts[0]) 179 | if err == nil { 180 | nextIdx = essentials.MaxInt(nextIdx, x+1) 181 | } 182 | } 183 | outName := fmt.Sprintf("%06d_%s", nextIdx, direction) 184 | outFile := filepath.Join(outDir, outName) 185 | essentials.Must(ioutil.WriteFile(outFile, data, 0644)) 186 | } 187 | --------------------------------------------------------------------------------