package xicmp import ( "context" goerrors "errors" "io" "net" "net/netip" "sync" "time" "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/transport/internet/finalmask" "golang.org/x/net/icmp" "golang.org/x/net/ipv4" "golang.org/x/net/ipv6" ) func clientIDToAddr(clientID [8]byte) *net.UDPAddr { ip := make(net.IP, 16) ip[0] = 0xfd ip[1] = 0x00 copy(ip[8:], clientID[:]) return &net.UDPAddr{IP: ip} } type record struct { id int seq int addr net.Addr last time.Time } type xicmpConnServer struct { conn net.PacketConn icmp4 *icmp.PacketConn icmp6 *icmp.PacketConn ips map[netip.Addr]struct{} rec map[string]record readCh chan packet closedCh chan struct{} mu sync.Mutex } func NewConnServer(c *Config, raw net.PacketConn) (net.PacketConn, error) { icmp4, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0") if err != nil { return nil, err } icmp6, err := icmp.ListenPacket("ip6:ipv6-icmp", "::") if err != nil { return nil, err } ips := make(map[netip.Addr]struct{}) for _, ip := range c.IPs { ips[netip.MustParseAddr(ip)] = struct{}{} } conn := &xicmpConnServer{ conn: raw, icmp4: icmp4, icmp6: icmp6, ips: ips, rec: make(map[string]record), readCh: make(chan packet), closedCh: make(chan struct{}), } go conn.clean() go conn.recv4() go conn.recv6() return conn, nil } func (c *xicmpConnServer) closed() bool { select { case <-c.closedCh: return true default: return false } } func (c *xicmpConnServer) clean() { ticker := time.NewTicker(time.Minute / 2) defer ticker.Stop() for { select { case <-ticker.C: now := time.Now() c.mu.Lock() for key, r := range c.rec { if now.Sub(r.last) > time.Minute { delete(c.rec, key) } } c.mu.Unlock() case <-c.closedCh: return } } } func (c *xicmpConnServer) recv4() { var b [finalmask.UDPSize]byte for { if c.closed() { return } n, addr, err := c.icmp4.ReadFrom(b[:]) if err != nil { var netErr net.Error if goerrors.As(err, &netErr) && netErr.Timeout() { select { case c.readCh <- packet{ err: err, }: case <-c.closedCh: return } } continue } msg, err := icmp.ParseMessage(1, b[:n]) if err != nil { continue } if msg.Type != ipv4.ICMPTypeEcho { continue } echo, ok := msg.Body.(*icmp.Echo) if !ok { continue } if len(echo.Data) <= 8 { continue } if len(c.ips) > 0 { netipAddr, ok := netip.AddrFromSlice(addr.(*net.IPAddr).IP) if !ok { continue } if _, ok := c.ips[netipAddr]; !ok { continue } } cAddr := clientIDToAddr([8]byte(echo.Data[:8])) c.mu.Lock() c.rec[cAddr.String()] = record{ id: echo.ID, seq: echo.Seq, addr: addr, last: time.Now(), } c.mu.Unlock() p := pool.Get().([]byte)[:len(echo.Data[8:])] copy(p, echo.Data[8:]) select { case c.readCh <- packet{ p: p, addr: cAddr, }: case <-c.closedCh: pool.Put(p) return } } } func (c *xicmpConnServer) recv6() { var b [finalmask.UDPSize]byte for { if c.closed() { return } n, addr, err := c.icmp6.ReadFrom(b[:]) if err != nil { var netErr net.Error if goerrors.As(err, &netErr) && netErr.Timeout() { select { case c.readCh <- packet{ err: err, }: case <-c.closedCh: return } } continue } msg, err := icmp.ParseMessage(58, b[:n]) if err != nil { continue } if msg.Type != ipv6.ICMPTypeEchoRequest { continue } echo, ok := msg.Body.(*icmp.Echo) if !ok { continue } if len(echo.Data) <= 8 { continue } if len(c.ips) > 0 { netipAddr, ok := netip.AddrFromSlice(addr.(*net.IPAddr).IP) if !ok { continue } if _, ok := c.ips[netipAddr]; !ok { continue } } cAddr := clientIDToAddr([8]byte(echo.Data[:8])) c.mu.Lock() c.rec[cAddr.String()] = record{ id: echo.ID, seq: echo.Seq, addr: addr, last: time.Now(), } c.mu.Unlock() p := pool.Get().([]byte)[:len(echo.Data[8:])] copy(p, echo.Data[8:]) select { case c.readCh <- packet{ p: p, addr: cAddr, }: case <-c.closedCh: pool.Put(p) return } } } func (c *xicmpConnServer) ReadFrom(p []byte) (n int, addr net.Addr, err error) { select { case packet := <-c.readCh: if packet.p != nil { n = copy(p, packet.p) pool.Put(packet.p) } return n, packet.addr, packet.err case <-c.closedCh: return 0, nil, io.EOF } } func (c *xicmpConnServer) WriteTo(p []byte, addr net.Addr) (n int, err error) { if len(p)+8 > finalmask.UDPSize { errors.LogError(context.Background(), "drop packet to ", addr, " with size ", len(p)) return 0, nil } c.mu.Lock() r, ok := c.rec[addr.String()] if !ok { errors.LogError(context.Background(), "drop packet to ", addr, " with size ", len(p)) c.mu.Unlock() return 0, nil } r.last = time.Now() c.rec[addr.String()] = r c.mu.Unlock() // errors.LogDebug(context.Background(), "id ", r.id, " seq ", r.seq, " addr ", r.addr) b := pool.Get().([]byte)[:finalmask.UDPSize] defer pool.Put(b) copy(b[8:], p) if r.addr.(*net.IPAddr).IP.To4() != nil { b = marshal(b, ipv4.ICMPTypeEchoReply, r.id, r.seq, len(p)) _, err = c.icmp4.WriteTo(b, r.addr) } else { b = marshal(b, ipv6.ICMPTypeEchoReply, r.id, r.seq, len(p)) _, err = c.icmp6.WriteTo(b, r.addr) } if err != nil { errors.LogErrorInner(context.Background(), err, "xicmp write") return 0, err } return len(p), nil } func (c *xicmpConnServer) Close() error { c.mu.Lock() defer c.mu.Unlock() if c.closed() { return nil } close(c.closedCh) _ = c.icmp4.Close() _ = c.icmp6.Close() _ = c.conn.Close() return nil } func (c *xicmpConnServer) LocalAddr() net.Addr { return c.conn.LocalAddr() } func (c *xicmpConnServer) SetDeadline(t time.Time) error { _ = c.icmp4.SetDeadline(t) _ = c.icmp6.SetDeadline(t) return nil } func (c *xicmpConnServer) SetReadDeadline(t time.Time) error { _ = c.icmp4.SetReadDeadline(t) _ = c.icmp6.SetReadDeadline(t) return nil } func (c *xicmpConnServer) SetWriteDeadline(t time.Time) error { _ = c.icmp4.SetWriteDeadline(t) _ = c.icmp6.SetWriteDeadline(t) return nil }