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开发之使用广播监听网络状态变化
我们经常需要判断网络状态的变化,如有无网络,所以需要监听网络状态的变化,比如网络断开,网络连接给予友好提示.如何监听网络状态的变化呢,最近工作中需要用到这个,于是就用广播机制来实现了网络状态的监听. ...
随机推荐
- SpringBoot-RSA加密
前言 最近由于工作业务的需要,需要对指定的字段信息进行非对称加解密:由于需要加密的内容过于庞大:自己执行程序会出现:Data must not be longer than 117 bytes 的异常 ...
- idea修改项目中某个模块名称
1.修改模块名称 2.修改文件夹名称 3.修改本模块里面pom的名称 4.修改其他模块里面引用的名称
- MSP 通过 Splashtop SOS 远程支持非托管设备
RMM 是 MSP 的绝佳工具.它们使 MSP 可以通过集中控制台来管理其所有客户计算机,通常使他们能够远程访问任何计算机以提供远程支持. 但是,这样做的一个很大限制是--并非所有客户设备都在 MSP ...
- Akima算法
测量数据的内插已有各种方法,如线性内插.多项式内插.样条函数插值等,但这里的Akima插值法具有独特的优点. 线性内插只顾及其附近两点的影响. 多项式内插时,低阶多项式由于参数较少,内插精度很低,而使 ...
- angular自定义属性指令
在angular中有三种类型的指令: 组件–拥有模板的指令: 结构性指令–通过添加和移除DOM元素改变DOM布局的指令; 属性型指令–改变元素.组件或其他指令的外观和行为的指令: 组件是这三种指令中最 ...
- java学习之旅(day.20)
注解和反射 注释comment:给人看 注解annotation:不仅可以给人看,还能给程序看,甚至能被其他程序读取 注解入门 什么是注解 注解的作用: 不是程序本身,可以对程序作出解释(这一点和注释 ...
- npm创建项目
创建项目 创建项目目录 首先新建一个文件夹,这里存放着我们的项目. 创建项目文件 这里不使用任何项目模板,相当于使用空模板. 进入这个文件夹,再cmd中运行npm init. 然后按照提示输入pack ...
- js RGB转HSV
function rgb2hsv (r,g,b) { var computedH = 0; var computedS = 0; var computedV = 0; //remove spaces ...
- docker应用基础
docker相关 镜像 镜像仓库使用的Docker默认的Docker Hub. 搜索仓库的镜像 docker search 默认按评分排序,offical [ok] 表示是官方镜像 $ docker ...
- kubernets之pod的生命周期容器启动后钩子以及容器结束前钩子
一 先来介绍容器启动后钩子 1.1 容器启动后钩子,并不是容器启动之后才会执行的操作,而是在容器启动过程中,异步的和容器进行启动的一种钩子它有2种表现形式,包括我们后面提到的容器结束前钩子一样 在一 ...