首页 > 其他分享 >Go 如何对多个网络命令空间中的端口进行监听

Go 如何对多个网络命令空间中的端口进行监听

时间:2024-07-03 18:08:42浏览次数:20  
标签:nil err 端口 xr 线程 Go netns root 监听

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 服务如下

  1. 获取并且保存默认网络命名空间
  2. 加锁防止多个网络命名空间同时切换,将 goroutine 绑定到当前的线程上防止被调度
  3. 获取需要操作的网络命名空间,并且切换过去 setns
  4. 监听套接字 net.Listen
  5. 切换到默认的命名空间(还原)
  6. 释放当前线程的绑定,释放锁

实现对 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

  1. 设置非阻塞
  2. 手动使用 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

标签:nil,err,端口,xr,线程,Go,netns,root,监听
From: https://www.cnblogs.com/shuqin/p/18282324

相关文章

  • django西餐厅管理系统-计算机毕业设计源码10873
    摘要在现代餐饮行业中,高效的管理系统对于西餐厅的成功运营至关重要。为了满足西餐厅日益增长的管理需求,设计并实现了一款基于Python的西餐厅管理系统。Python作为一种简洁而易读的编程语言,具有广泛的应用领域,包括Web开发。结合Django这一强大的Web框架,我们可以快速构建......
  • Go设置GOPROXY国内加速
    在Linux或macOS上面需要运行下面命令(或者,可以把以下命令写到.bashrc或.bash_profile文件中):#启用GoModules功能goenv-wGO111MODULE=on#配置GOPROXY环境变量,以下三选一#1.七牛CDNgoenv-wGOPROXY=https://goproxy.cn,direct#2.阿里云goenv......
  • Go标准库:container/list
    Go标准库:container/list原创 孟斯特 孟斯特 2024-07-0316:03 北京 听全文在Go语言的标准库中,container/list包提供了一个双向链表的实现,这对于需要频繁插入和删除操作的场景非常有用。双向链表是一种线性数据结构,它由一系列节点组成,每个节点包含数据和两个指针,分别......
  • go当中的线程存储与使用
    1、settls从引导代码中可以看到,在执行settls前将m.tls放入了DI。go/src/runtime/asm_amd64.s:159TEXTruntime·rt0_go(SB),NOSPLIT|NOFRAME|TOPFRAME,$0....LEAQruntime·m0+m_tls(SB),DICALLruntime·settls(SB)....tls定义是无符号整形指针ty......
  • go基本操作
    1.gowsl环境搭建注意事项:ubuntu必须安装在系统盘(C盘)VSode插件下载:koroFileHeader自动添加注释:VScode自动添加注释_vscode自动注释-CSDN博客go中文下载地址:Go下载-Go语言中文网-Golang中文社区golang开发环境下载:Allreleases-TheGoProgrammingLanguagego中文......
  • Golang面试:泛型
    Go语言在1.18版本中引入了泛型(Generics),这是Go语言发展中的一个重要里程碑。泛型允许你编写更通用和可复用的代码,而无需牺牲类型安全性。以下是对Go中泛型的详细介绍,包括其语法、使用场景和示例代码。1.泛型的基本概念泛型允许你定义可以处理多种数据类型的函数和数据结构,而无需......
  • Golang开发:构建支持并发的网络爬虫
    Golang开发:构建支持并发的网络爬虫随着互联网的快速发展,获取网络数据成为了许多应用场景中的关键需求。网络爬虫作为一种自动化获取网络数据的工具,也因此迅速崛起。而为了应对日益庞大的网络数据,开发支持并发的爬虫成为了必要的选择。本文将介绍如何使用Golang编写一个支持......
  • Django 自定义用户表
    当默认的用户表中字段不足以满足我们的业务需求时,可以自己继承和重写用户表,增加想要的字段。1.自定义用户表模型fromdjango.dbimportmodelsfromdjango.contrib.auth.modelsimportAbstractUser#重新定义用户表classUserProfile(AbstractUser):avatar=model......
  • django使用jinja2模板
    1.使用Django默认模板TEMPLATES=[{'BACKEND':'django.template.backends.django.DjangoTemplates','DIRS':[BASE_DIR/'templates'],#使用路径表达式'APP_DIRS':True,'OPTIO......
  • 差异基因富集分析(R语言——GO&KEGG&GSEA)
    接着上次的内容,上篇内容给大家分享了基因表达量怎么做分组差异分析,从而获得差异基因集,想了解的可以去看一下,这篇主要给大家分享一下得到显著差异基因集后怎么做一下通路富集。1.准备差异基因集我就直接把上次分享的拿到这边了。我们一般都把差异基因分为上调基因和下调基因分......