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开发之使用广播监听网络状态变化
我们经常需要判断网络状态的变化,如有无网络,所以需要监听网络状态的变化,比如网络断开,网络连接给予友好提示.如何监听网络状态的变化呢,最近工作中需要用到这个,于是就用广播机制来实现了网络状态的监听. ...
随机推荐
- Swift实现判断目录下是否存在指定文件功能
本文主要讲解以下这段名为 isDataJsonFilePathExists 的私有函数的 Swift 代码实现细节,该函数的作用是检查指定文件或文件夹是否存在,其返回值类型为 Bool 类型,如果存在 ...
- go 操作elasticsearch
olivere/elastic 包 github.com/olivere/elastic doc: https://pkg.go.dev/github.com/olivere/elastic?utm_ ...
- golang计时器
timer 计时器 用于在指定的Duration类型时间后调用函数或计算表达式. 如果只是想指定时间之后执行,使用time.Sleep() 使用NewTimer(),可以返回的Timer类型在计时器到 ...
- 原生微信小程序button去掉边框
直接改没反应,需要使用::after更改
- mini-centos7 环境安装部署,各种踩坑。。。
最小Linux系统,安装Java环境 想想就生气,去面试个运维,面试官让我上机装个centos7,还是个最小安装包连界面都没有,只有命令行模式,我都哭了,然后让把一些环境装一下,然后再部署个sprin ...
- 《最新出炉》系列入门篇-Python+Playwright自动化测试-47-自动滚动到元素出现的位置
1.简介 在我们日常工作中或者生活中,经常会遇到我们的页面内容较多,一个屏幕范围无法完整展示内容,我们就需要滚动滚动条去到我们想要的地方,如下图页面,我们虽然在豆瓣首页,但是内容并不完整,如果我们想要 ...
- Django----模型----数据库的基本操作
1.添加记录 方式一:save() 方式二(推荐):create() 2.查询记录 (1)all() 查询所有记录,返回queryset对象. stu_list = Student.objects.a ...
- WPF显示网络图片的几种方法
1.利用数据流 1 Image img; 2 byte[] btyarray = GetImageFromResponse(imageUrl); 3 4 //字节数据转流 5 MemoryStream ...
- iOS直播助手第一个版本总结
经过1个月的努力,终于完成了直播助手iOS11版本的适配,第一个版本也已经提审,趁着这个空档进行一下总结: 打算后续按照目录进行完善 1.iOS直播采集介绍,直播助手iOS11采集使用的方法 2.iO ...
- C语言打印数字前补0
1.要求说明 例如有个数据为a = 0x10,要求打印输出为0x000010. 2.实现 1 #include <stdio.h> 2 3 4 int main() 5 { 6 int a ...