暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Go语言的IPv4/IPv6服务

拖地先生 2019-12-16
951

最近新做了个功能,在华为服务器上运行go的http服务时,看到监听的端口是ipv6

 而在阿里云机器上,则是ipv4

 恰好该端口telnet不通,误以为是这个原因(其实是华为服务器默认有安全组,只开放5901-5910段),顺便就研究了一下go在Linux上对IPv4/IPv6服务的方式。

 

先说结论


Go的 net.Listen()
函数,如果不强行指定 IPv4 或 IPv6 ,在双栈系统上(VPS 同时支持 IPv4 和 IPv6)默认只会监听 IPv6 地址。这不影响客户端使用 IPv4 地址来访问。

 

Linux相关



使用man 7 ipv6
来查看如下,"The port space of IPv6 is shared with IPv4",ipv4地址在ipv6上有map ::FFF:<ipv4 address>。

IPv4 connections can be handled with the v6 API by using the v4-mapped-on-v6 address type;
thus a program needs to support only this API type to support both protocols.
This is handled transparently by the address handling functions in the C library.


IPv4 and IPv6 share the local port space.
When you get an IPv4 connection or packet to a IPv6 socket,
its source address will be mapped to v6 and it will be mapped to v6.


The address notation for IPv6 is a group of 8 4-digit hexadecimal numbers, separated with a ':'.
"::" stands for a string of 0 bits.
Special addresses are ::1 for loopback and ::FFFF:<IPv4 address> for IPv4-mapped-on-IPv6.
 

在 linux 上有个内核参数 net.ipv6.bindv6only
,默认为关闭状态,这样 IPv6 的 socket 也就可以解析映射到同一个网卡的 IPv4 请求了
。如果服务需要同时提供 IPv4 和 IPv6 的访问能力,只需要监听一个 IPv6 的 socket 即可。如果不希望 IPv4 可以访问 IPv6 的服务,就把 net.ipv6.bindv6only
置为 1:

$ cat /proc/sys/net/ipv6/bindv6only
1

但是,开启了这个参数后,go服务依然可以使用 IPv4 的地址来访问。

 

Go语言



内核参数没有生效,问题多半是发生在 syscall
的调用上,祭出 strace
大杀器:


可以看到在 220 行 Golang 把准备 listen 的 socket 选项置为了 0。查看代码 src/net/ipsock_posix.go


func favoriteAddrFamily(network string, laddr, raddr sockaddr, mode string) (family int, ipv6only bool) {
switch network[len(network)-1] {
case '4':
return syscall.AF_INET, false
case '6':
return syscall.AF_INET6, true
}


if mode == "listen" && (laddr == nil || laddr.isWildcard()) {
if supportsIPv4map() || !supportsIPv4() {
return syscall.AF_INET6, false
}
if laddr == nil {
return syscall.AF_INET, false
}
return laddr.family(), false
}


if (laddr == nil || laddr.family() == syscall.AF_INET) &&
(raddr == nil || raddr.family() == syscall.AF_INET) {
return syscall.AF_INET, false
}
return syscall.AF_INET6, false
}

Go自己定义了 IPV6_V6ONLY 这个行为,至于这么做的原因在官方 Github 也有一些讨论:net: Listen is unfriendly to multiple address families, endpoints and subflows。

 

如何更准确地控制



在上层可以使用 http.ListenAndServe
来选择,如:

http.ListenAndServe(":8083", nil)  // tcp
http.ListenAndServe("[2604:180:3:dd3::276e]:8083", nil) // 具体指定 tcp6

如果觉得具体指定 IPv6地址太麻烦,可以用 net.Listen
  重构 ListenAndServe
,在该函数里指定 network ,可选参数:

"tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only),
"udp", "udp4" (IPv4-only), "udp6" (IPv6-only),
"ip", "ip4" (IPv4-only), "ip6" (IPv6-only),
"unix", "unixgram" and "unixpacket"

常用:

tcp     自动适配,优先IPv6
tcp4 仅使用IPv4
tcp6 仅使用IPv6

示例如下:

package main


import (
"fmt"
"net"
"net/http"
)


type helloHandler struct{}


func (h *helloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, world!"))
}


func main() {
var err error
http.Handle("/", &helloHandler{})
//err = http.ListenAndServe(":8083", nil) // IPv4 或 IPv6
//err = http.ListenAndServe("[2604:180:3:dd3::276e]:8083", nil) // 具体指定,仅 IPv6
err = ListenAndServe(":8083", nil) // 重构 ListenAndServe 函数
if err != nil {
fmt.Println(err)
}


}


type tcpKeepAliveListener struct {
*net.TCPListener
}


func ListenAndServe(addr string, handler http.Handler) error {
srv := &http.Server{Addr: addr, Handler: handler}
addr = srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp6", addr) // 仅指定 IPv6
if err != nil {
return err
}
return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}

客户端可以用下面的命令行检测 IPv6 服务:

curl "http://[2604:180:3:dd3::276e]:8083"
curl -g -6 'http://[2604:180:3:dd3::276e]:8083/'
telnet -6 2604:180:3:dd3::276e 8083




拖地先生,从事互联网技术工作,在这里每周两篇文章,一起聊聊日常的技术点滴和软素质模型。

往期推荐:

平均响应1000ms到200ms,PHP和Go那家强?

崩溃率从1%到0.02%,iOS稳定性解决之道

七招优化Android包体减少30%

两件事让你在职场成为靠谱的人

技术产品职业瓶颈?29份腾讯通道材料教你成长

高压下如何保证工作质量,有你需要知道的方法论

什么是本质

Football Manager教你学管理

技术工作的一二三之快餐

技术工作的一二三之内功

技术工作的一二三之职业规划

技术工作的一二三之价值观方法论

如果对你有帮助,让大家也看看呗~

文章转载自拖地先生,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论