Go 如何对多个网络命令空间中的端口进行监听
Go 如何对多个网络命令空间中的端口进行监听
需求为 对多个命名空间内的端口进行监听和代理。
刚开始对 netns 的理解不够深刻,以为必须存在一个新的线程然后调用 setns(2) 切换过去,如果有新的 netns 那么需要再新建一个线程切换过去使用,这样带来的问题就是线程数量和 netns 的数量为 1:1,资源占用会比较多。
当时没有想到别的好办法,Go 里面也不能创建线程,只能想到使用一个 C 进程来实现这个功能,这里就多了 通信交互/协议解析处理/资源占用 的成本。
新方案
后面在 stackoverflow 中闲逛看到一篇文章 https://stackoverflow.com/questions/28846059/can-i-open-sockets-in-multiple-network-namespaces-from-my-python-code,看到了关键点 在套接字创建之前,切换到对应的命名空间,并不需要创建线程。
这样就可以一个线程下对多个命名空间的端口进行监听,可以减少线程本身资源的占用以及额外的管理成本。
原来 C 实现的改造比较好实现,删除创建线程那一步差不多就可以了。如何更进一步使用 Go 实现,减少维护的成本?
使用 Go 进行实现
保证套接字创建时在某个命名空间内,就可以完成套接字后续的操作,不必使用一个线程来持有一个命名空间,建立一个典型的 TCP 服务如下
- 获取并且保存默认网络命名空间
- 加锁防止多个网络命名空间同时切换,将 goroutine 绑定到当前的线程上防止被调度
- 获取需要操作的网络命名空间,并且切换过去 setns
- 监听套接字 net.Listen
- 切换到默认的命名空间(还原)
- 释放当前线程的绑定,释放锁
实现对 TCP 的监听
使用 github.com/vishvananda/netns 这个库对网络命名空间进行操作,一个同时在 默认/ns1/ns2 三个命名空间内监听 8000 端口的例子如下:
命名空间创建命令
ip netns add ns1
ip netns add ns2
package main
import (
"net"
"runtime"
"sync"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/vishvananda/netns"
)
var (
mainNetnsHandler netns.NsHandle
mainNetnsMutex sync.Mutex
)
func mustInitMainNetnsHandler() {
nh, err := netns.Get()
if err != nil {
panic(err)
}
mainNetnsHandler = nh
}
func ListenInsideNetns(ns, network, address string) (net.Listener, error) {
if ns == "" {
return net.Listen(network, address)
}
var set bool
mainNetnsMutex.Lock()
runtime.LockOSThread()
defer func() {
if set {
err := netns.Set(mainNetnsHandler)
if err != nil {
logrus.WithError(err).Warn("Fail to back to main netns")
}
}
runtime.UnlockOSThread()
mainNetnsMutex.Unlock()
}()
nh, err := netns.GetFromName(ns)
if err != nil {
return nil, errors.Wrap(err, "netns.GetFromName")
}
defer nh.Close()
err = netns.Set(nh)
if err != nil {
return nil, errors.Wrap(err, "netns.Set")
}
set = true
return net.Listen(network, address)
}
func serve(listener net.Listener) error {
for {
conn, err := listener.Accept()
if err != nil {
return err
}
logrus.WithFields(logrus.Fields{"local": conn.LocalAddr(), "remote": conn.RemoteAddr()}).Info("New conn")
conn.Write([]byte("hello"))
conn.Close()
}
}
func main() {
mustInitMainNetnsHandler()
wg := sync.WaitGroup{}
wg.Add(3)
go func() {
defer wg.Done()
lis, err := ListenInsideNetns("", "tcp", ":8000")
if err != nil {
panic(err)
}
logrus.WithFields(logrus.Fields{"netns": "", "addr": lis.Addr()}).Info("Listen on")
serve(lis)
}()
go func() {
defer wg.Done()
lis, err := ListenInsideNetns("ns1", "tcp", ":8000")
if err != nil {
panic(err)
}
logrus.WithFields(logrus.Fields{"netns": "ns1", "addr": lis.Addr()}).Info("Listen on")
serve(lis)
}()
go func() {
defer wg.Done()
lis, err := ListenInsideNetns("ns2", "tcp", ":8000")
if err != nil {
panic(err)
}
logrus.WithFields(logrus.Fields{"netns": "ns2", "addr": lis.Addr()}).Info("Listen on")
serve(lis)
}()
wg.Wait()
}
UDP/SCTP 的监听
UDP 监听和 TCP 无异,Go 会做好调度不会产生新线程。
SCTP 如果是使用库 github.com/ishidawataru/sctp,那么需要注意这个库就是简单的 fd 封装,并且其 Accept() 是一个阻塞的动作,在 for 循环内调用 Accept() 会导致 Go runtime 会创建一个新线程来防止阻塞。
解决方案如下,直接操作 fd
- 设置非阻塞
- 手动使用 epoll 封装(必须是 epoll,select/poll 在几百个fd的情况下性能很差,无连接的情况负载都很高)。
获取 fd 的方式如下
type sctpWrapListener struct {
*sctp.SCTPListener
fd int
}
func listenSCTP(network, address string) (*sctpWrapListener, error) {
addr, err := parseSCTPAddr(address)
if err != nil {
return nil, err
}
sctpFd := 0
sc := sctp.SocketConfig{
InitMsg: sctp.InitMsg{NumOstreams: sctp.SCTP_MAX_STREAM},
Control: func(network, address string, c syscall.RawConn) error {
return c.Control(func(fd uintptr) {
err := syscall.SetNonblock(int(fd), true)
if err != nil {
syscall.Close(int(fd))
return
}
sctpFd = int(fd)
})
},
}
l, err := sc.Listen(network, addr)
if err != nil {
return nil, err
}
return &sctpWrapListener{SCTPListener: l, fd: sctpFd}, nil
}
实际应用的数据参考
打开的文件如下
root@localhost:~# lsof -p $(pidof fake_name) | tail
fake_name 1599860 root 1203u sock 0,8 0t0 20374830 protocol: UDP
fake_name 1599860 root 1204u pack 20375161 0t0 ALL type=SOCK_RAW
fake_name 1599860 root 1205u sock 0,8 0t0 20374831 protocol: SCTPv6
fake_name 1599860 root 1206u sock 0,8 0t0 20375156 protocol: TCP
fake_name 1599860 root 1207u sock 0,8 0t0 20375157 protocol: UDP
fake_name 1599860 root 1208u sock 0,8 0t0 20375158 protocol: SCTPv6
fake_name 1599860 root 1209u pack 20381769 0t0 ALL type=SOCK_RAW
fake_name 1599860 root 1210u sock 0,8 0t0 20381764 protocol: TCP
fake_name 1599860 root 1211u sock 0,8 0t0 20381765 protocol: UDP
fake_name 1599860 root 1212u sock 0,8 0t0 20381766 protocol: SCTPv6
root@localhost:~# lsof -p $(pidof fake_name) | wc -l
1216
业务机器CPU为 4 核心,创建的线程如下
root@localhost:~# ll /proc/$(pidof fake_name)/task
total 0
dr-xr-xr-x 13 root root 0 Jul 3 14:51 ./
dr-xr-xr-x 9 root root 0 Jul 3 14:51 ../
dr-xr-xr-x 7 root root 0 Jul 3 14:51 1599860/
dr-xr-xr-x 7 root root 0 Jul 3 14:57 1599861/
dr-xr-xr-x 7 root root 0 Jul 3 14:57 1599862/
dr-xr-xr-x 7 root root 0 Jul 3 14:57 1599863/
dr-xr-xr-x 7 root root 0 Jul 3 14:57 1599864/
dr-xr-xr-x 7 root root 0 Jul 3 14:57 1599865/
dr-xr-xr-x 7 root root 0 Jul 3 14:57 1600021/
dr-xr-xr-x 7 root root 0 Jul 3 14:57 1600033/
dr-xr-xr-x 7 root root 0 Jul 3 14:57 1600056/
dr-xr-xr-x 7 root root 0 Jul 3 14:57 1600058/
dr-xr-xr-x 7 root root 0 Jul 3 14:57 1602524/
root@localhost:~# ll /proc/$(pidof fake_name)/task | wc -l
14
Go 如何对多个网络命令空间中的端口进行监听的更多相关文章
- linux网络连接的查看和端口的监听
网络软件都是由客户端和服务端组成,由服务端在服务器上监听指定的端口处理接收到的数据,而客户端是向服务器端监听的端口发送数据,并由服务器端对该数据进行处理,然后将处理结果返回给客户端. 那么我们在lin ...
- oracle 11G创建表空间、用户、配置监听和TNS
最近总在安装各种版本的oralce数据库做测试,11G,32位的,64位的,12C的,每次都折腾表空间,用户.tns啥的,这里记录下,再也不用现用现百度找了 一.创建表空间.用户 在plsql工具中 ...
- 解开一个疑惑,为什么LVS开放的端口,使用netstat或ss命令,不能查找到其监听的端口呢?
RT, 这个疑问,本周一直在心里,今天找到一个说法. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 另外LVS规则算是内核方法,用netstat -ntulp也显 ...
- 深入理解 Neutron -- OpenStack 网络实现(4):网络名字空间
问题导读1.如何查看网络名字空间?2.网络名字空间开头的名字有什么规律?3.dhcp服务是如何实现的?4.router的实现是通过iptables进行的是否正确?5.SNAT和DNAT规则有什么作用? ...
- 网络名称空间 实例研究 veth处于不同网络的路由问题
相关命令详细介绍参见 http://www.cnblogs.com/Dream-Chaser/p/7077105.html .问题: 两个网络名称空间中的两个接口veth0和veth1,如何配置net ...
- Linux网络命令与脚本使用
作为系统管理员,经常需要诊断和解决网络问题,而配置.监控与保护网络有助于发现问题并在事情范围扩大前得意解决,并且网络的性能与安全也是管理与诊断网络的重要部分.这里总结一下常用与Linux网络管理的命令 ...
- Linux常用命令之网络命令
write命令 write命令用于向指定登录用户终端上发送信息.通过write命令可传递信息给另一位登入系统的用户,当输入完毕后,键入EOF表示信息结束,write命令就会将信息传给对方.如果接收信息 ...
- [Linux] 017 网络命令与挂载命令
1. 网络命令:write 命令名称:write 命令所在路径:/usr/bin/write 执行权限:所有用户 语法:write [用户名] 功能描述:给用户发信息,以 Ctrl-d 保存结束 范例 ...
- (办公)记事本_linux网络命令
参考谷粒学院的linux视频教程:http://www.gulixueyuan.com/course/300/task/7091/show 阿里云ECS云服务器更换公网IP的方法:https://yq ...
- Android开发之使用广播监听网络状态变化
我们经常需要判断网络状态的变化,如有无网络,所以需要监听网络状态的变化,比如网络断开,网络连接给予友好提示.如何监听网络状态的变化呢,最近工作中需要用到这个,于是就用广播机制来实现了网络状态的监听. ...
随机推荐
- sqli-labs-master 导入导出 + 第七关
1.load_file()导出文件 load_file(file_name):读取文件并返回该文件内容作为一个字符串. 使用条件: A:必须有权限读取并且文件完全可读 B:预读取文件必修在服务器上 C ...
- 02 elasticsearch学习笔记-ES核心概念
一. 前序 sh Elasticsearch是一个基于Apache Lucene(TM)的开源搜索引擎.无论在开源还是专有领域,Lucene 可以被认为是迄今为止最先进.性能最好的.功能最全的搜索引擎 ...
- ubuntu下安装php pdo扩展和导入数据库
默认安装的php不存在pdo扩展,因此在使用到的时候会报错,直接使用这个命令 apt-get install php-mysql 就可以成功安装pdo扩展 安装完数据库后需要导入sql语句,先进入数据 ...
- List<T> 根据对象中的属性处理数据
一.创建测试数据 UserEntity user1 = UserEntity.builder().id(1).name("张三").sex(0).build(); UserEnti ...
- 如何在 Ubuntu 服务器上安装桌面环境 (GUI)
先以VNC方式远程登录服务器 执行命令 sudo apt update && sudo apt upgrade # 选择1---使用tasksel安装 sudo apt install ...
- Unity Shader中的常见流控制指令
在Shader中处理流控制语句时,常加上一些宏去处理流控制指令.例如: [UNITY_UNROLL] for (int i = 0; i < 10; i++) { //do something. ...
- 4G 信令中的 PCO 字段
目录 文章目录 目录 Protocol Configuration Option Protocol Configuration Option PCO(Protocol Configuration Op ...
- docker 修改运行容器环境变量,如何修改容器中的环境变量env使长期有效
@ 目录 前言 第一步:查看Docker Root目录 第二步:查到容器的长id(container id) 第三步:停止容器 第四步:编辑修改环境变量env 第五步:重载服务的配置文件 第六步:重启 ...
- VSCode:让VSCode终端面板显示到右边
很简单,右击下方终端面板,选中"将视图移动到侧面板": 可以看到终端成功显示到右边了:
- Istio(六):Istio弹性(超时&重试)和故障注入
目录 一.模块概览 二.系统环境 三.弹性(超时&重试) 3.1 弹性 四.故障注入 4.1 故障注入 五.实战:观察错误注入 5.1 在 Grafana.Zipkin 和 Kiali 中观察 ...