如何把网络设备从 traceroute 中隐藏

在和朋友一起吃饭的时候,A 提了一个有意思的问题:怎样可以把一个机房内的路由设备从互联网「隐藏」呢?

「隐藏就是没有人可以知道这个设备的 IP 地址,这还不简单,只要禁用 ICMP 就可以了」, B说。

A 说,这样是可以。很多安全团队在实施起来也确实是这么做的,但是这样并不好:机房内所有的 IP 都无法 ping 通了。这样会增加 debug 的难度,得不偿失呀!

C 说,那就依然转发 ICMP 包,但是如果是 TTL=1 的包,就不要回复 ICMP Time Exceeded 了。

B 说,人家要的是「隐藏」,要是像你说的这么做,别人还是知道中间有一个设备的存在,没有完全符合要求。

一个 traceroute 的例子:第7跳直接丢弃 TTL=1 的包,不返回错误1

事实是这样的。假设一个简单的物理拓扑是 A -> B -> C,B 不回复 ICMP Time Exceeded,那么 traceroute 看起来就是 A ? C,可以猜测得到中间有一个路由器,但是已经禁止回复 ICMP Time Exceeded。看起来像下面这样。

C 说,traceroute 的原理是发送 TTL=1, 2, 3, … 的包,不断让路由器回复 ICMP Time Exceeded 信息,来得到每一跳的 IP 地址。要想完全隐藏,只需要:

  • 自己不回复 ICMP Time Exceeded
  • 让下一跳回复,仿佛下一跳就在自己的位置;

这样就可以完全隐藏了。要达到这个目的,只需要:

  • 对于 TTL=1 的包,不是丢弃,而是转发给下一跳,并且 TTL 依然保持为 1,即可。A ---[TTL=1]---> B ---[TTL=1]---> C, 对于客户端的 traceroute,看起来就像:A → C。

这样(理论上)好像确实可行了。三人对这个结论满意了。

后来我把这个讨论记录在了博客上(你现在正在阅读的一个),一位读者马上就发现了问题:可是这样 C 会出现两次吧!

确实是这样,假设在 A -> B -> C 的链路中:

  • TTL = 1 从 A 进入的时候,A 会在 ICMP 中回复自己的 IP;
  • TTL = 2 从 A 进入的时候,B 会直接转发给 C,C 会在 ICMP 中回复自己的 IP;
  • TTL = 3 从 A 进入的时候,B 会 TTL -1 转发给 C,C 会在 ICMP 中回复自己的 IP;

这样 C 就出现了 2 次!

看来,B 必须完全不减 TTL,直接转发,才能隐藏自己。不过这样就有出现环路2的风险了。

  1. 使用 mtr 检查网络问题,以及注意事项 ↩︎
  2. 网络中的环路和防环技术 ↩︎


如何把网络设备从 traceroute 中隐藏”已经有9条评论

  1. 请教个多线程问题,就是我有两台服务器10.58.3.21和10.58.3.23, 10.58.3.21会开启多个线程从10.58.3.23:9091服务拉取数据,每个线程都是独立或者32MiB数据

    问题是:
    如果客户端上面开启2个线程,那么每个线程的耗时为50ms
    如果客户端上面开启4个线程,那么每个线程的耗时为100ms
    如果客户端上面开启8个线程,那么每个线程的耗时为200ms
    以此类推耗时成倍上升,我用c语言测试也是一样的结果,我可以理解多线程有损耗,但是这种成倍耗时上升,不清楚是不是正常的

    服务器之间的带宽是万兆,服务器都是56C cpu,256G memory

    golang代码如下:
    10.58.3.21(client):
    “`
    package main

    import (
    “fmt”
    “io”
    “net”
    “sync”
    “time”
    “flag”
    )

    //const concurrency = 32 // 并发 goroutine 数量

    func main() {
    concurrency := flag.Int(“c”, 1, “number of concurrent goroutines”)
    port := flag.Int(“port”, 9090, “server port”)
    host := flag.String(“host”, “127.0.0.1”, “server host”)
    name := flag.String(“name”, “client1”, “server name”)
    flag.Parse()
    var wg sync.WaitGroup

    for i := 0; i < *concurrency; i++ {
    wg.Add(1)
    go func(id int) {
    defer wg.Done()
    start := time.Now() // goroutine 开始时间
    conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d",*host, *port))
    if err != nil {
    fmt.Printf("[%s][Worker %d] Dial error: %v\n", *name, id, err)
    return
    }
    defer conn.Close()
    buf := make([]byte, 4*1024*1024) // 1 MiB buffer
    total := 0
    count := 0
    for {
    count = count+1
    //start1 := time.Now()
    n, err := conn.Read(buf)
    total += n
    if err == io.EOF {
    break
    }
    if err != nil {
    fmt.Printf("[%s][Worker %d] Read error: %v\n", *name, id, err)
    return
    }
    }
    duration := time.Since(start)
    fmt.Printf("[%s][Worker %d] Finished reading %d bytes, duration: %v\n",*name, id, total, duration)
    }(i + 1)
    }

    wg.Wait()
    fmt.Println("All goroutines completed")
    }
    “`

    10.58.3.23:9091(server)代码:
    “`
    package main

    import (
    "fmt"
    "math/rand"
    "net"
    "time"
    )

    const dataSize = 32*1024*1024 // 128 MiB

    func main() {
    ln, err := net.Listen("tcp", "10.58.3.23:9091")
    if err != nil {
    panic(err)
    }
    fmt.Println("Server listening on :9091")

    rand.Seed(time.Now().UnixNano())
    buf := make([]byte, 32*1024*1024) // 1 MiB buffer
    for i := range buf {
    buf[i] = byte(rand.Intn(256))
    }

    for {
    conn, err := ln.Accept()
    if err != nil {
    fmt.Println("Accept error:", err)
    continue
    }

    go handleConn(conn, buf)
    }
    }

    func handleConn(conn net.Conn, buf []byte) {
    defer conn.Close()

    fmt.Println("Client connected:", conn.RemoteAddr())

    start := time.Now()
    sent := 0
    for sent < dataSize {
    n, err := conn.Write(buf) // 写入初始化的数据
    if err != nil {
    fmt.Println("Write error:", err)
    return
    }
    sent += n
    }
    duration := time.Since(start)
    fmt.Printf("Sent 32 MiB to %v, duration: %v\n", conn.RemoteAddr(), duration)
    }
    “`

  2. 请教个多线程问题,就是我有两台服务器10.58.3.21和10.58.3.23, 10.58.3.21会开启多个线程从10.58.3.23:9091服务拉取数据,每个线程都是独立或者32MiB数据

    问题是:
    如果客户端上面开启2个线程,那么每个线程的耗时为50ms
    如果客户端上面开启4个线程,那么每个线程的耗时为100ms
    如果客户端上面开启8个线程,那么每个线程的耗时为200ms
    以此类推耗时成倍上升,我用c语言测试也是一样的结果,我可以理解多线程有损耗,但是这种成倍耗时上升,不清楚是不是正常的

    服务器之间的带宽是万兆,服务器都是56C cpu,256G memory

    golang代码如下:
    10.58.3.21(client):
    “`
    package main

    import (
    “fmt”
    “io”
    “net”
    “sync”
    “time”
    “flag”
    )

    //const concurrency = 32 // 并发 goroutine 数量

    func main() {
    concurrency := flag.Int(“c”, 1, “number of concurrent goroutines”)
    port := flag.Int(“port”, 9090, “server port”)
    host := flag.String(“host”, “127.0.0.1”, “server host”)
    name := flag.String(“name”, “client1”, “server name”)
    flag.Parse()
    var wg sync.WaitGroup

    for i := 0; i < *concurrency; i++ {
    wg.Add(1)
    go func(id int) {
    defer wg.Done()
    start := time.Now() // goroutine 开始时间
    conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d",*host, *port))
    if err != nil {
    fmt.Printf("[%s][Worker %d] Dial error: %v\n", *name, id, err)
    return
    }
    defer conn.Close()
    buf := make([]byte, 4*1024*1024) // 1 MiB buffer
    total := 0
    count := 0
    for {
    count = count+1
    //start1 := time.Now()
    n, err := conn.Read(buf)
    total += n
    if err == io.EOF {
    break
    }
    if err != nil {
    fmt.Printf("[%s][Worker %d] Read error: %v\n", *name, id, err)
    return
    }
    }
    duration := time.Since(start)
    fmt.Printf("[%s][Worker %d] Finished reading %d bytes, duration: %v\n",*name, id, total, duration)
    }(i + 1)
    }

    wg.Wait()
    fmt.Println("All goroutines completed")
    }
    “`

    10.58.3.23:9091(server)代码:
    “`
    package main

    import (
    "fmt"
    "math/rand"
    "net"
    "time"
    )

    const dataSize = 32*1024*1024 // 128 MiB

    func main() {
    ln, err := net.Listen("tcp", "10.58.3.23:9091")
    if err != nil {
    panic(err)
    }
    fmt.Println("Server listening on :9091")

    rand.Seed(time.Now().UnixNano())
    buf := make([]byte, 32*1024*1024) // 1 MiB buffer
    for i := range buf {
    buf[i] = byte(rand.Intn(256))
    }

    for {
    conn, err := ln.Accept()
    if err != nil {
    fmt.Println("Accept error:", err)
    continue
    }

    go handleConn(conn, buf)
    }
    }

    func handleConn(conn net.Conn, buf []byte) {
    defer conn.Close()

    fmt.Println("Client connected:", conn.RemoteAddr())

    start := time.Now()
    sent := 0
    for sent < dataSize {
    n, err := conn.Write(buf) // 写入初始化的数据
    if err != nil {
    fmt.Println("Write error:", err)
    return
    }
    sent += n
    }
    duration := time.Since(start)
    fmt.Printf("Sent 32 MiB to %v, duration: %v\n", conn.RemoteAddr(), duration)
    }
    “`

Leave a comment

您的邮箱地址不会被公开。 必填项已用 * 标注