关于 Chrome 出现 ERR_UNSAFE_PORT 错误

TiDB 内置了一个 Web 服务(10080 端口),用 Chrome 访问时提示:

无法访问此网站网址为 http://xxx:10080/ 的网页可能暂时无法连接,或者它已永久性地移动到了新网址。
ERR_UNSAFE_PORT

关于 ERR_UNSAFE_PORT 错误,也只在网上找到一些零碎的答案,说 Chrome 为了安全禁封掉了这些端口,建议更换服务端的监听端口。出于好奇,我顺着 Chromium 源码找到返回 ERR_UNSAFE_PORT 的函数:

// file: net/http/http_stream_factory_job.cc

int HttpStreamFactory::Job::DoStart() {
  ...
  if (!IsPortAllowedForScheme(destination_.port(),
                              request_info_.url.scheme_piece())) {
    return ERR_UNSAFE_PORT;
  }

  ...
  return OK;
}

以上,关键在 IsPortAllowedForScheme 函数,找到函数实现:

// file: net/base/port_util.cc

bool IsPortAllowedForScheme(int port, base::StringPiece url_scheme) {
  // Reject invalid ports.
  if (!IsPortValid(port))
    return false;

  LogAlpacaPort(port);

  // Allow explicitly allowed ports for any scheme.
  if (g_explicitly_allowed_ports.Get().count(port) > 0)
    return true;

  // Finally check against the generic list of restricted ports for all
  // schemes.
  for (int restricted_port : kRestrictedPorts) {
    if (restricted_port == port)
      return false;
  }

  return true;
}

注意最后的 for 循环的对象 kRestrictedPorts,再找到 kRestrictedPorts 的定义:

const int kRestrictedPorts[] = {
    1,      // tcpmux
    7,      // echo
    9,      // discard
    11,     // systat
    13,     // daytime
    15,     // netstat
    17,     // qotd
    19,     // chargen
    20,     // ftp data
    21,     // ftp access
    22,     // ssh
    23,     // telnet
    25,     // smtp
    37,     // time
    42,     // name
    43,     // nicname
    53,     // domain
    69,     // tftp
    77,     // priv-rjs
    79,     // finger
    87,     // ttylink
    95,     // supdup
    101,    // hostriame
    102,    // iso-tsap
    103,    // gppitnp
    104,    // acr-nema
    109,    // pop2
    110,    // pop3
    111,    // sunrpc
    113,    // auth
    115,    // sftp
    117,    // uucp-path
    119,    // nntp
    123,    // NTP
    135,    // loc-srv /epmap
    137,    // netbios
    139,    // netbios
    143,    // imap2
    161,    // snmp
    179,    // BGP
    389,    // ldap
    427,    // SLP (Also used by Apple Filing Protocol)
    465,    // smtp+ssl
    512,    // print / exec
    513,    // login
    514,    // shell
    515,    // printer
    526,    // tempo
    530,    // courier
    531,    // chat
    532,    // netnews
    540,    // uucp
    548,    // AFP (Apple Filing Protocol)
    554,    // rtsp
    556,    // remotefs
    563,    // nntp+ssl
    587,    // smtp (rfc6409)
    601,    // syslog-conn (rfc3195)
    636,    // ldap+ssl
    989,    // ftps-data
    990,    // ftps
    993,    // ldap+ssl
    995,    // pop3+ssl
    1719,   // h323gatestat
    1720,   // h323hostcall
    1723,   // pptp
    2049,   // nfs
    3659,   // apple-sasl / PasswordServer
    4045,   // lockd
    5060,   // sip
    5061,   // sips
    6000,   // X11
    6566,   // sane-port
    6665,   // Alternate IRC [Apple addition]
    6666,   // Alternate IRC [Apple addition]
    6667,   // Standard IRC [Apple addition]
    6668,   // Alternate IRC [Apple addition]
    6669,   // Alternate IRC [Apple addition]
    6697,   // IRC + TLS
    10080,  // Amanda
};

kRestrictedPorts 数组里存放了一些已知服务的端口,当通过 HTTP(S) 协议访问这些端口时都会报 ERR_UNSAFE_PORT 错误。为了弄清目的,我试着在 git log 里找到答案,如下:

$ git log --grep 10080 port_util.cc

commit d6e884b99ffa8ad5f85ae13c5aa78dd297572f19
Author: Adam Rice <[email protected]>
Date:   Tue Apr 13 09:35:40 2021 +0000

    Block port 10080

    See Fetch Standard issue https://github.com/whatwg/fetch/issues/1191
    for justification and background, and
    https://groups.google.com/a/chromium.org/g/blink-dev/c/CNpLNXx2wf0/m/Ehi3cx1YAQAJ
    for the intent-to-ship thread.

    BUG=1197299

    Change-Id: Ie664726307fef0d8578ffbaea9793b74d722b58a
    Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2814044
    Commit-Queue: Adam Rice <[email protected]>
    Reviewed-by: Ryan Sleevi <[email protected]>
    Cr-Commit-Position: refs/heads/master@{#871865}

接着顺着 commit 里记录的 issue 链接(https://github.com/whatwg/fetch/issues/1191 )去找,原来 Chrome 是为了防止 NAT Slipstreaming 攻击而封锁的端口。