diff --git a/cmd/main.go b/cmd/main.go index 9c1b72e..804a1a3 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -7,39 +7,53 @@ import ( ) var usage = ` + 通过伪造ping,把udp流量通过远程服务器转发到目的服务器上。用于突破某些运营商封锁UDP流量。 + By forging ping, the udp traffic is forwarded to the destination server through the remote server. Used to break certain operators to block UDP traffic. + Usage: pingtunnel -type server - pingtunnel -type client -l LOCAL_IP:4455 -s SERVER_IP -t 4455 + pingtunnel -type client -l LOCAL_IP:4455 -s SERVER_IP -t SERVER_IP:4455 + -type 服务器或者客户端 + client or server + + -l 本地的地址,发到这个端口的流量将转发到服务器 + Local address, traffic sent to this port will be forwarded to the server + + -s 服务器的地址,流量将通过隧道转发到这个服务器 + The address of the server, the traffic will be forwarded to this server through the tunnel + + -t 远端服务器转发的目的地址,流量将转发到这个地址 + Destination address forwarded by the remote server, traffic will be forwarded to this address + + -timeout 本地记录连接超时的时间,单位是秒 + The time when the local record connection timed out, in seconds ` func main() { - fmt.Println("start...") - t := flag.String("type", "client", "client or server") - listen := flag.String("l", ":4455", "listen addr") - target := flag.Int("t", 4455, "target port") - server := flag.String("s", "127.0.0.1", "server addr") + t := flag.String("type", "", "client or server") + listen := flag.String("l", "", "listen addr") + target := flag.String("t", "", "target addr") + server := flag.String("s", "", "server addr") + timeout := flag.Int("timeout", 60, "conn timeout") flag.Usage = func() { fmt.Printf(usage) } flag.Parse() - if flag.NArg() != 0 { + if (*t != "client" && *t != "server") || (*t == "client" && (len(*listen) == 0 || len(*target) == 0 || len(*server) == 0)) { flag.Usage() return } - fmt.Printf("type %s\n", *t) - fmt.Printf("listen %s\n", *listen) - fmt.Printf("server %s\n", *server) - fmt.Printf("target port %d\n", *target) + fmt.Println("start...") if *t == "server" { - s, err := pingtunnel.NewServer() + s, err := pingtunnel.NewServer(*timeout) if err != nil { fmt.Printf("ERROR: %s\n", err.Error()) return @@ -48,13 +62,19 @@ func main() { s.Run() } if *t == "client" { - c, err := pingtunnel.NewClient(*listen, *server, *target) + + fmt.Printf("type %s\n", *t) + fmt.Printf("listen %s\n", *listen) + fmt.Printf("server %s\n", *server) + fmt.Printf("target %s\n", *target) + + c, err := pingtunnel.NewClient(*listen, *server, *target, *timeout) if err != nil { fmt.Printf("ERROR: %s\n", err.Error()) return } - fmt.Printf("Client Listen %s (%s) Server %s (%s) TargetPort %d:\n", c.Addr(), c.IPAddr(), - c.ServerAddr(), c.ServerIPAddr(), c.TargetPort()) + fmt.Printf("Client Listen %s (%s) Server %s (%s) TargetPort %s:\n", c.Addr(), c.IPAddr(), + c.ServerAddr(), c.ServerIPAddr(), c.TargetAddr()) c.Run() } } diff --git a/src/pingtunnel/client.go b/src/pingtunnel/client.go index 3be3f5d..b1e43b5 100644 --- a/src/pingtunnel/client.go +++ b/src/pingtunnel/client.go @@ -7,7 +7,7 @@ import ( "time" ) -func NewClient(addr string, server string, target int) (*Client, error) { +func NewClient(addr string, server string, target string, timeout int) (*Client, error) { ipaddr, err := net.ResolveUDPAddr("udp", addr) if err != nil { @@ -24,24 +24,33 @@ func NewClient(addr string, server string, target int) (*Client, error) { addr: addr, ipaddrServer: ipaddrServer, addrServer: server, - targetPort: (uint16)(target), + targetAddr: target, + timeout: timeout, }, nil } type Client struct { + timeout int + ipaddr *net.UDPAddr addr string ipaddrServer *net.IPAddr addrServer string - targetPort uint16 + targetAddr string conn *icmp.PacketConn listenConn *net.UDPConn - localConnToIdMap map[string]uint32 - localIdToConnMap map[uint32]*net.UDPAddr + localAddrToConnMap map[string]*ClientConn + localIdToConnMap map[string]*ClientConn +} + +type ClientConn struct { + ipaddr *net.UDPAddr + id string + activeTime time.Time } func (p *Client) Addr() string { @@ -52,8 +61,8 @@ func (p *Client) IPAddr() *net.UDPAddr { return p.ipaddr } -func (p *Client) TargetPort() uint16 { - return p.targetPort +func (p *Client) TargetAddr() string { + return p.targetAddr } func (p *Client) ServerIPAddr() *net.IPAddr { @@ -82,16 +91,21 @@ func (p *Client) Run() { defer listener.Close() p.listenConn = listener - p.localConnToIdMap = make(map[string]uint32) - p.localIdToConnMap = make(map[uint32]*net.UDPAddr) + p.localAddrToConnMap = make(map[string]*ClientConn) + p.localIdToConnMap = make(map[string]*ClientConn) go p.Accept() recv := make(chan *Packet, 1000) go recvICMP(*p.conn, recv) + interval := time.NewTicker(time.Second) + defer interval.Stop() + for { select { + case <-interval.C: + p.checkTimeoutConn() case r := <-recv: p.processPacket(r) } @@ -119,33 +133,58 @@ func (p *Client) Accept() error { } } - uuid := p.localConnToIdMap[srcaddr.String()] - if uuid == 0 { - uuid = UniqueId() - p.localConnToIdMap[srcaddr.String()] = uuid - p.localIdToConnMap[uuid] = srcaddr - fmt.Printf("client accept new local %d %s\n", uuid, srcaddr.String()) + now := time.Now() + clientConn := p.localAddrToConnMap[srcaddr.String()] + if clientConn == nil { + uuid := UniqueId() + clientConn = &ClientConn{ipaddr: srcaddr, id: uuid, activeTime: now} + p.localAddrToConnMap[srcaddr.String()] = clientConn + p.localIdToConnMap[uuid] = clientConn + fmt.Printf("client accept new local %s %s\n", uuid, srcaddr.String()) } - sendICMP(*p.conn, p.ipaddrServer, p.targetPort, uuid, (uint32)(DATA), bytes[:n]) + clientConn.activeTime = now + sendICMP(*p.conn, p.ipaddrServer, p.targetAddr, clientConn.id, (uint32)(DATA), bytes[:n]) } } func (p *Client) processPacket(packet *Packet) { - fmt.Printf("processPacket %d %s %d\n", packet.id, packet.src.String(), len(packet.data)) + fmt.Printf("processPacket %s %s %d\n", packet.id, packet.src.String(), len(packet.data)) - addr := p.localIdToConnMap[packet.id] - if addr == nil { - fmt.Printf("processPacket no conn %d \n", packet.id) + clientConn := p.localIdToConnMap[packet.id] + if clientConn == nil { + fmt.Printf("processPacket no conn %s \n", packet.id) return } + addr := clientConn.ipaddr + + now := time.Now() + clientConn.activeTime = now + _, err := p.listenConn.WriteToUDP(packet.data, addr) if err != nil { fmt.Printf("WriteToUDP Error read udp %s\n", err) - p.localConnToIdMap[addr.String()] = 0 - p.localIdToConnMap[packet.id] = nil + p.Close(clientConn) return } } + +func (p *Client) Close(clientConn *ClientConn) { + if p.localIdToConnMap[clientConn.id] != nil { + delete(p.localIdToConnMap, clientConn.id) + delete(p.localAddrToConnMap, clientConn.ipaddr.String()) + } +} + +func (p *Client) checkTimeoutConn() { + now := time.Now() + for id, conn := range p.localIdToConnMap { + diff := now.Sub(conn.activeTime) + if diff > time.Second*(time.Duration(p.timeout)) { + fmt.Printf("close inactive conn %s %s\n", id, conn.ipaddr.String()) + p.Close(conn) + } + } +} diff --git a/src/pingtunnel/pingtunnel.go b/src/pingtunnel/pingtunnel.go index ac9f8b0..2dc8796 100644 --- a/src/pingtunnel/pingtunnel.go +++ b/src/pingtunnel/pingtunnel.go @@ -1,12 +1,16 @@ package pingtunnel import ( + "crypto/md5" + "crypto/rand" + "encoding/base64" "encoding/binary" + "encoding/hex" "fmt" "golang.org/x/net/icmp" "golang.org/x/net/ipv4" + "io" "net" - "sync/atomic" "syscall" "time" ) @@ -19,9 +23,9 @@ const ( // An Echo represents an ICMP echo request or reply message body. type MyMsg struct { - ID uint32 TYPE uint32 - TARGET uint16 + ID string + TARGET string Data []byte } @@ -30,37 +34,61 @@ func (p *MyMsg) Len(proto int) int { if p == nil { return 0 } - return 10 + len(p.Data) + return 4 + p.LenString(p.ID) + p.LenString(p.TARGET) + len(p.Data) +} + +func (p *MyMsg) LenString(s string) int { + return 2 + len(s) } // Marshal implements the Marshal method of MessageBody interface. func (p *MyMsg) Marshal(proto int) ([]byte, error) { + b := make([]byte, p.Len(proto)) - binary.BigEndian.PutUint32(b[:4], uint32(p.ID)) - binary.BigEndian.PutUint32(b[4:8], uint32(p.TYPE)) - binary.BigEndian.PutUint16(b[8:10], uint16(p.TARGET)) - copy(b[10:], p.Data) + + binary.BigEndian.PutUint32(b[:4], uint32(p.TYPE)) + + id := p.MarshalString(p.ID) + copy(b[4:], id) + + target := p.MarshalString(p.TARGET) + copy(b[4+p.LenString(p.ID):], target) + + copy(b[4+p.LenString(p.ID)+p.LenString(p.TARGET):], p.Data) + return b, nil } +func (p *MyMsg) MarshalString(s string) []byte { + b := make([]byte, p.LenString(s)) + binary.BigEndian.PutUint16(b[:2], uint16(len(s))) + copy(b[2:], []byte(s)) + return b +} + // Marshal implements the Marshal method of MessageBody interface. func (p *MyMsg) Unmarshal(b []byte) error { - p.ID = binary.BigEndian.Uint32(b[:4]) - p.TYPE = binary.BigEndian.Uint32(b[4:8]) - p.TARGET = binary.BigEndian.Uint16(b[8:10]) - p.Data = make([]byte, len(b[10:])) - copy(p.Data, b[10:]) + + p.TYPE = binary.BigEndian.Uint32(b[:4]) + + p.ID = p.UnmarshalString(b[4:]) + + p.TARGET = p.UnmarshalString(b[4+p.LenString(p.ID):]) + + p.Data = make([]byte, len(b[4+p.LenString(p.ID)+p.LenString(p.TARGET):])) + copy(p.Data, b[4+p.LenString(p.ID)+p.LenString(p.TARGET):]) + return nil } -var uuid uint32 - -func UniqueId() uint32 { - newValue := atomic.AddUint32(&uuid, 1) - return (uint32)(newValue) +func (p *MyMsg) UnmarshalString(b []byte) string { + len := binary.BigEndian.Uint16(b[:2]) + data := make([]byte, len) + copy(data, b[2:]) + return string(data) } -func sendICMP(conn icmp.PacketConn, server *net.IPAddr, target uint16, connId uint32, msgType uint32, data []byte) { +func sendICMP(conn icmp.PacketConn, server *net.IPAddr, target string, connId string, msgType uint32, data []byte) { m := &MyMsg{ ID: connId, @@ -130,7 +158,22 @@ func recvICMP(conn icmp.PacketConn, recv chan<- *Packet) { type Packet struct { data []byte - id uint32 - target uint16 + id string + target string src *net.IPAddr } + +func UniqueId() string { + b := make([]byte, 48) + + if _, err := io.ReadFull(rand.Reader, b); err != nil { + return "" + } + return GetMd5String(base64.URLEncoding.EncodeToString(b)) +} + +func GetMd5String(s string) string { + h := md5.New() + h.Write([]byte(s)) + return hex.EncodeToString(h.Sum(nil)) +} diff --git a/src/pingtunnel/server.go b/src/pingtunnel/server.go index 3052ef9..31ceb8e 100644 --- a/src/pingtunnel/server.go +++ b/src/pingtunnel/server.go @@ -4,25 +4,28 @@ import ( "fmt" "golang.org/x/net/icmp" "net" - "strconv" "time" ) -func NewServer() (*Server, error) { +func NewServer(timeout int) (*Server, error) { return &Server{ + timeout: timeout, }, nil } type Server struct { + timeout int + conn *icmp.PacketConn - localConnMap map[uint32]*Conn + localConnMap map[string]*ServerConn } -type Conn struct { +type ServerConn struct { ipaddrTarget *net.UDPAddr conn *net.UDPConn - id uint32 + id string + activeTime time.Time } func (p *Server) Run() { @@ -34,13 +37,18 @@ func (p *Server) Run() { } p.conn = conn - p.localConnMap = make(map[uint32]*Conn) + p.localConnMap = make(map[string]*ServerConn) recv := make(chan *Packet, 1000) go recvICMP(*p.conn, recv) + interval := time.NewTicker(time.Second) + defer interval.Stop() + for { select { + case <-interval.C: + p.checkTimeoutConn() case r := <-recv: p.processPacket(r) } @@ -49,13 +57,15 @@ func (p *Server) Run() { func (p *Server) processPacket(packet *Packet) { - fmt.Printf("processPacket %d %s %d\n", packet.id, packet.src.String(), len(packet.data)) + fmt.Printf("processPacket %s %s %d\n", packet.id, packet.src.String(), len(packet.data)) + + now := time.Now() id := packet.id udpConn := p.localConnMap[id] if udpConn == nil { - addr := ":" + strconv.Itoa((int)(packet.target)) + addr := packet.target ipaddrTarget, err := net.ResolveUDPAddr("udp", addr) if err != nil { fmt.Printf("Error ResolveUDPAddr for udp addr: %s %s\n", addr, err.Error()) @@ -67,11 +77,13 @@ func (p *Server) processPacket(packet *Packet) { fmt.Printf("Error listening for udp packets: %s\n", err.Error()) return } - udpConn = &Conn{conn: targetConn, ipaddrTarget: ipaddrTarget, id: id} + udpConn = &ServerConn{conn: targetConn, ipaddrTarget: ipaddrTarget, id: id, activeTime: now} p.localConnMap[id] = udpConn go p.Recv(udpConn, id, packet.src) } + udpConn.activeTime = now + _, err := udpConn.conn.Write(packet.data) if err != nil { fmt.Printf("WriteToUDP Error %s\n", err) @@ -80,9 +92,9 @@ func (p *Server) processPacket(packet *Packet) { } } -func (p *Server) Recv(conn *Conn, id uint32, src *net.IPAddr) { +func (p *Server) Recv(conn *ServerConn, id string, src *net.IPAddr) { - fmt.Printf("server waiting target response %s\n", conn.ipaddrTarget.String()) + fmt.Printf("server waiting target response %s -> %s %s\n", conn.ipaddrTarget.String(), conn.id, conn.conn.LocalAddr().String()) bytes := make([]byte, 10240) @@ -102,13 +114,29 @@ func (p *Server) Recv(conn *Conn, id uint32, src *net.IPAddr) { } } - sendICMP(*p.conn, src, 0, id, (uint32)(DATA), bytes[:n]) + now := time.Now() + conn.activeTime = now + + sendICMP(*p.conn, src, "", id, (uint32)(DATA), bytes[:n]) } } -func (p *Server) Close(conn *Conn) { +func (p *Server) Close(conn *ServerConn) { if p.localConnMap[conn.id] != nil { conn.conn.Close() - p.localConnMap[conn.id] = nil + delete(p.localConnMap, conn.id) } } + +func (p *Server) checkTimeoutConn() { + + now := time.Now() + for id, conn := range p.localConnMap { + diff := now.Sub(conn.activeTime) + if diff > time.Second*(time.Duration(p.timeout)) { + fmt.Printf("close inactive conn %s %s\n", id, conn.ipaddrTarget.String()) + p.Close(conn) + } + } + +}