linux随机监听端口:0是受什么影响的

内核

先看一下内核


Linux workpc 3.10.0-1127.el7.x86_64 #1 SMP Tue Mar 31 23:36:51 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux

问题

正常监听随机端口


ln, err := net.Listen("tcp", ":0")

if err != nil {
    log.Fatalln(err)
}
log.Printf("listen port %s", ln.Addr())
for {
    _, err := ln.Accept()
    if err != nil {
        // handle error
    }
    // go handleConnection(conn)
}

但是有时候发现监听失败,但是在测试机上没失败过,生产服务器上很容易失败


listen tcp :0: bind: address already in use

猜测

端口受内核参数net.ipv4.ip_local_port_range影响,但是这个参数在之前的使用者都是拿来作为客户端的时候控制对外连接的端口范围,防止需要监听的端口被占用和扩大端口范围,增加对外连接数

今天去官方看了下kernel文档


ip_local_port_range - 2 INTEGERS
    Defines the local port range that is used by TCP and UDP to
    choose the local port. The first number is the first, the
    second the last local port number.
    If possible, it is better these numbers have different parity
    (one even and one odd value).
    Must be greater than or equal to ip_unprivileged_port_start.
    The default values are 32768 and 60999 respectively.

大概意思是说它是定义TCP和UDP使用的本地端口范围,没说是作为客户端还是服务端。

这里我猜测随机获取可用端口的时候,也是按这个范围来返回的

验证

下面做个实验验证一下


# echo "30000 30010" | sudo tee /proc/sys/net/ipv4/ip_local_port_range
30000 30010

# cat /proc/sys/net/ipv4/ip_local_port_range
30000    30010

现在控制30000到30010


package main

import (
    "fmt"
    "log"
    "net"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    for i := 30000; i <= 30010; i++ {
                //先注释这里
        //if i == 30001 {
        //    continue
        //}
        wg.Add(1)
        go func(i int) {
            ln, err := net.Listen("tcp", ":"+fmt.Sprintf("%d", i))
            wg.Done()

            if err != nil {
                return
            }
            for {
                _, err := ln.Accept()
                if err != nil {
                    // handle error
                }
                // go handleConnection(conn)
            }
        }(i)
    }
    wg.Wait()
    log.Println("success listen")

    ln, err := net.Listen("tcp", ":0")

    if err != nil {
        log.Fatalln(err)
    }
    log.Printf("listen port %s", ln.Addr())
    for {
        _, err := ln.Accept()
        if err != nil {
            // handle error
        }
        // go handleConnection(conn)
    }
    return

}

现在设置范围是30000-30010的端口,然后随机监听端口

提示listen tcp :0: bind: address already in use

然后将注释去掉



package main

import (
    "fmt"
    "log"
    "net"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    for i := 30000; i <= 30010; i++ {
                //去掉注释
        if i == 30001 {
            continue
        }
        wg.Add(1)
        go func(i int) {
            ln, err := net.Listen("tcp", ":"+fmt.Sprintf("%d", i))
            wg.Done()

            if err != nil {
                return
            }
            for {
                _, err := ln.Accept()
                if err != nil {
                    // handle error
                }
                // go handleConnection(conn)
            }
        }(i)
    }
    wg.Wait()
    log.Println("success listen")

    ln, err := net.Listen("tcp", ":0")

    if err != nil {
        log.Fatalln(err)
    }
    log.Printf("listen port %s", ln.Addr())
    for {
        _, err := ln.Accept()
        if err != nil {
            // handle error
        }
        // go handleConnection(conn)
    }
    return

}

去掉注释,执行,一致是监听30001端口


2021/03/11 08:53:39 success listen
2021/03/11 08:53:39 listen port [::]:30001

再将端口范围改大


# echo "30000 30011" | sudo tee /proc/sys/net/ipv4/ip_local_port_range
30000 30011
# cat /proc/sys/net/ipv4/ip_local_port_range
30000    30011

30000-30010端口全部被占用,这个时候如果随机监听端口,如果最后监听是30011端口,那表示系统随机监听端口就是受ip_local_port_range这个参数影响的


package main

import (
    "fmt"
    "log"
    "net"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    for i := 30000; i <= 30010; i++ {
        // if i == 30001 {
        //     continue
        // }
        wg.Add(1)
        go func(i int) {
            ln, err := net.Listen("tcp", ":"+fmt.Sprintf("%d", i))
            wg.Done()

            if err != nil {
                return
            }
            for {
                _, err := ln.Accept()
                if err != nil {
                    // handle error
                }
                // go handleConnection(conn)
            }
        }(i)
    }
    wg.Wait()
    log.Println("success listen")

    ln, err := net.Listen("tcp", ":0")

    if err != nil {
        log.Fatalln(err)
    }
    log.Printf("listen port %s", ln.Addr())
    for {
        _, err := ln.Accept()
        if err != nil {
            // handle error
        }
        // go handleConnection(conn)
    }
    return

}


2021/03/11 08:56:14 success listen
2021/03/11 08:56:14 listen port [::]:30011

最后结论:随机监听端口是受ip_local_port_range这个内核参数影响的。

回到刚才的问题,生产环境容易出现,测试不容易出现,因为生产环境上可能还有其他程序在对外连接,可能内核参数设置过小,导致没有可用的端口了,所以随机监听端口容易失败。

解决方案

解决方案也很容易

  1. 调大ip_local_port_range范围
  2. 不用随机端口的方式去监听,而是在程序里直接监听,如果这个端口号失败,直接加1,继续监听,直到成功为止
添加新评论