当前位置: 首页 > news >正文

零入门kubernetes网络实战-22->基于tun设备实现在用户空间可以ping通外部节点(golang版本)

《零入门kubernetes网络实战》视频专栏地址
https://www.ixigua.com/7193641905282875942

本篇文章视频地址(稍后上传)


本篇文章主要是想做一个测试:
实现的目的是

  • 希望在宿主机-1上,在用户空间里使用ping命令发起ping请求,产生的icmp类型的数据包经过tun类型的虚拟网络设备转发,能够ping通宿主机-2上的对外物理网卡eth0;
  • 不仅能够发送请求,还能接收到反馈信息;
  • 实现的效果跟普通的ping请求效果一样;数据包有去有回。

1、原理图

实现原理图,如下图所示:

在这里插入图片描述

该图主要分为两下两部分:

  • 上面部分是宿主机-1里的流程图,
  • 下面部署是宿主机-2里的流通图。

我们要实现的目的是:

  • 在宿主机-1上,发起ping命令请求,使用虚拟网络设备tun19发起;
  • 希望能够ping通宿主机-2上的对外的物理网卡eth0;

分在两条线:

  • 黑色实线是ping请求命令数据包走的路线
  • 黑色虚线是ping反馈数据包走的路线

在宿主机-1里,又分了用户空间和内核空间两部分。

tun-driver就是咱们要编写的程序;

  • 该程序会创建虚拟网络设备tun19,并配置了IP信息。
  • 该程序内部,存在三个组件吧:
    • icmpConn,表明,这是传输icmp类型数据包的链接,类似于tcp链接,udp链接。这是icmp链接。
    • imcpTotun,表明,消息是从icmp链接里读取,将读取的消息发送到tun里,即发送到/dev/net/tun文件描述符里
    • tunToimcp,表明,消息是从文件描述符/dev/net/tun里读取的,将读取的消息发送到icmp链接里的

到此为止,我们介绍了我们要实现的目的是什么,介绍了原理图,数据包的走向,以及我们实现的程序都做了哪些事情;

接下来,看一下我们的代码:

2、方案说明

本次测试,我打算采用两种试验方式;

  • 方案一:先单独创建tun虚拟网络设备(tun19),然后,在编写tun-driver程序
  • 方案二:将创建tun虚拟网络设备的程序(tun19),集成到tun-driver程序里。(其实,就是在方案一的基础上,代码整合了一下)

想把我学习tun设备的过程,给大家复盘一下,

代码不是一开始就写正确的,总得有个调试的过程。

刚好方案一,遇到了一些问题。给大家分享一下。

当然,你可以直接跳过方案一,直接看方案二。

3、方案一:

3.1、创建tun设备的代码

package mainimport ("github.com/vishvananda/netlink"
)const tunName = "tun19"func main() {la := netlink.LinkAttrs{Name:  tunName,Index: 8,MTU:   1500,}tun := netlink.Tuntap{LinkAttrs: la,Mode:      netlink.TUNTAP_MODE_TUN,}l, err := netlink.LinkByName(tunName)if err == nil {// 先将tun虚拟网络设备Down掉netlink.LinkSetDown(l)// 将tun虚拟网络设备删掉netlink.LinkDel(l)}// 每次创建新的tun设备err = netlink.LinkAdd(&tun)if err != nil {panic(err)}l, err = netlink.LinkByName(tunName)ip, err := netlink.ParseIPNet("10.244.2.2/24")addr := &netlink.Addr{IPNet: ip, Label: ""}if err = netlink.AddrAdd(l, addr); err != nil {panic(err)}err = netlink.LinkSetUp(l)if err != nil {panic(err)}
}

这段代码,就是前文介绍的。

在本地编译,上传到测试服务器上:

build:CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.goscp:scp main root@10.211.55.122:/rootall:make build && make scp

执行

make all

登录到远程服务器上

ip a s | grep eth0ip link sh tun19./mainip link sh tun19ip a sh tun19

在这里插入图片描述

3.2、tun-driver 代码原理介绍

3.2.1、golang代码

package mainimport ("bytes""encoding/binary""flag""fmt""golang.org/x/net/icmp""golang.org/x/net/ipv4""net""os""syscall""time""unsafe"
)const (tunDevice  = "/dev/net/tun"ifnameSize = 16
)type ifreqFlags struct {IfrnName  [ifnameSize]byteIfruFlags uint16
}func ioctl(fd int, request, argp uintptr) error {_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), request, argp)if errno != 0 {fmt.Errorf("ioctl failed with '%s'\n", errno)return fmt.Errorf("ioctl failed with '%s'", errno)}return nil
}func fromZeroTerm(s []byte) string {return string(bytes.TrimRight(s, "\000"))
}func OpenTun(name string) (*os.File, string, error) {tun, err := os.OpenFile(tunDevice, os.O_RDWR, 0)if err != nil {fmt.Printf("OpenTun Failed! err:%v", err.Error())return nil, "", err}var ifr ifreqFlagscopy(ifr.IfrnName[:len(ifr.IfrnName)-1], []byte(name+"\000"))ifr.IfruFlags = syscall.IFF_TUN | syscall.IFF_NO_PIerr = ioctl(int(tun.Fd()), syscall.TUNSETIFF, uintptr(unsafe.Pointer(&ifr)))if err != nil {fmt.Printf("OpenTun Failed! err:%v\n", err.Error())return nil, "", err}ifName := fromZeroTerm(ifr.IfrnName[:ifnameSize])return tun, ifName, nil
}var tunName stringfunc checkSum(data []byte) uint16 {var (sum    uint32length int = len(data)index  int)for length > 1 {sum += uint32(data[index])<<8 + uint32(data[index+1])index += 2length -= 2}if length > 0 {sum += uint32(data[index])}sum += sum >> 16return uint16(^sum)
}func main() {flag.StringVar(&tunName, "tunName", "tun19", "Use -tunName xxx")flag.Parse()var err errortunFile, _, err := OpenTun(tunName)if err != nil {fmt.Printf("ICMP Listen Packet Failed! err:%v\n", err.Error())return}defer tunFile.Close()icmpconn, _ := icmp.ListenPacket("ip4:icmp", "0.0.0.0")defer icmpconn.Close()var srcCh = make(chan string, 1)go tunToicmp(icmpconn, tunFile, srcCh)go icmpToTun(icmpconn, tunFile, srcCh)time.Sleep(time.Hour)
}func tunToicmp(icmpconn *icmp.PacketConn, tunFile *os.File, srcCh chan string) {var srcIP stringpacket := make([]byte, 1024*64)size := 0var err errorfor {if size, err = tunFile.Read(packet); err != nil {return}fmt.Printf("Msg Length: %d\n", binary.BigEndian.Uint16(packet[2:4]))fmt.Printf("Msg Protocol: %d (1=ICMP, 6=TCP, 17=UDP)\tsize:%d\n", packet[9], size)b := packet[:size]srcIP = GetSrcIP(b)srcCh <- srcIPdstIP := GetDstIP(b)fmt.Printf("Msg srcIP: %s\tdstIP:%v\n", srcIP, dstIP)var raddr = net.IPAddr{IP: net.ParseIP(dstIP)}b = b[20:size]if size, err = icmpconn.WriteTo(b, &raddr); err != nil {fmt.Println(err.Error())return}fmt.Printf("Send ICMP Packet Success OK! size:%d\n", size)}
}func icmpToTun(icmpconn *icmp.PacketConn, tunFile *os.File, srcCh chan string) {var sb = make([]byte, 1024*64)var addr net.Addrvar size intvar err errorfor {if size, addr, err = icmpconn.ReadFrom(sb); err != nil {continue}srcIP := <-srcChipHeader := createIPv4Header(net.ParseIP(addr.String()), net.ParseIP(srcIP), os.Getpid())iphb, err := ipHeader.Marshal()if err != nil {continue}fmt.Printf("Reply MSG Length: %d\n", binary.BigEndian.Uint16(iphb[2:4]))fmt.Printf("Reply MSG Protocol: %d (1=ICMP, 6=TCP, 17=UDP)\n", iphb[9])dstIP := GetDstIP(iphb)fmt.Printf("Reply src IP: %s\tdstIP:%v\n", addr, dstIP)var tunb = make([]byte, 84)tunb = append(iphb, sb[:size]...)size, err = tunFile.Write(tunb)if err != nil {continue}fmt.Printf("Reply MSG To Tun OK! size:%d\n", size)}
}func createIPv4Header(src, dst net.IP, id int) *ipv4.Header {iph := &ipv4.Header{Version:  ipv4.Version,Len:      ipv4.HeaderLen,TOS:      0x00,TotalLen: ipv4.HeaderLen + 64,ID:       id,Flags:    ipv4.DontFragment,FragOff:  0,TTL:      64,Protocol: 1,Checksum: 0,Src:      src,Dst:      dst,}h, _ := iph.Marshal()iph.Checksum = int(checkSum(h))return iph
}func IsIPv4(packet []byte) bool {flag := packet[0] >> 4return flag == 4
}func GetIPv4Src(packet []byte) net.IP {return net.IPv4(packet[12], packet[13], packet[14], packet[15])
}func GetIPv4Dst(packet []byte) net.IP {return net.IPv4(packet[16], packet[17], packet[18], packet[19])
}func GetSrcIP(packet []byte) string {key := ""if IsIPv4(packet) && len(packet) >= 20 {key = GetIPv4Src(packet).To4().String()}return key
}func GetDstIP(packet []byte) string {key := ""if IsIPv4(packet) && len(packet) >= 20 {key = GetIPv4Dst(packet).To4().String()}return key
}

接下来,对代码进行分析

3.2.2、 main流程分析

在这里插入图片描述

3.2.3、tunToicmp流程分析

3.2.3.1、主要流程说明

第111-133行:内部死循环的执行任务。
那么,任务的主要过程是:
在这里插入图片描述

3.2.3.2、IP报文头结构说明

看一下112行,作用是从/dev/net/tun文件描述符里读取数据到packet切片里

而packet属于字节切片

前面文章已经介绍过了,从/dev/net/tun文件里读取的数据,tun设备默认会给原数据包添加一个IP报文头;

如下形式:
在这里插入图片描述

那么, 接下来的问题就是,如何解析IP报文头?

从IP报文头里获取我们想要的信息,如ICMP数据包的目的地址是哪里?

IP报文头的结构形式,如下所示:

在这里插入图片描述

  • IP报文头一共20个字节,不包括可选项
  • 这20个字节,分为了5行来说明、解释(行与行之间的空白行不算)
  • 一个byte字节=8bit,每一行都是4个字节,32bit
  • 看第一行第一个小框,
  • 版本占用了4bit,当值为4时,即代表IPv4
  • 头部长度占用了4bit,值为4字节的倍数,4bit位的全部是1的话,也就是15,因此,头部长度占用的最大字节数是15*4=60字节;
    • 比方说,二进制0101,占用了4bit,十进制是5,当前IP报文头占用的字节数是5*4=20个字节。
  • 版本和头部长度合起来,就是第0个字节,即packet[0]
  • packet[1],表示服务类型;
  • packet[2], packet[3]:总长
  • packet[4], packet[5]:表示标识
  • packet[6], packet[7]: 表示标记和分段偏移
  • packet[8]: 表示 生命周期
  • packet[9]: 表示 协议(如,ICMP,tcp, udp)
  • packet[10], packet[11]:表示头部校验和
  • packet[12], packet[13], packet[14], packet[15]:表示 源地址,源IP
  • packet[16], packet[17], packet[18], packet[19]:表示 目的地址,目的IP

从IP报文头结构中,我们可以获取到目的地址,即packet[16], packet[17], packet[18], packet[19]

代码中的115行,116行,118行非必须代码,是为给大家演示一下,才写的。

3.2.3.3、通过icmp连接将数据发送到目的地址

  • 看117行:size表示从/dev/net/tun里读取了多少字节,
    • 通过这条语句获取到有效的数据
  • 看119行:首先说明这条语句非必须的。
    • 在后续的版本的中,已经删除了。(代码非一次性写好的)
    • 因为把icmp数据包发送给了另一个宿主机上的eth0,eth0会反馈消息到icmpTotun函数里,
    • 在此函数里,需要将从icmp链接里接收到的数据添加上一个自定义的IP报文头,
    • 写到/dev/net/tun文件里,此时,需要知道srcIP;因此,在这里传输过去的。
    • 但是一般情况下,srcIP就是tun19的IP,是固定死的。因此,没有必要写。

到目前为止,我们已经将数据从tun19设备里发送到了另一台宿主机的对外网卡eth0上了。

3.2.4、icmpTotun流程分析

3.2.4.1、主流程分析

整体看一下这块代码,就是一个for循环,在死循环的执行逻辑;

主要逻辑,如下图所示:

在这里插入图片描述

3.2.4.2、从ICMP链接里读取消息

  • 看143行: 从ICMP链接里读取另一个宿主机eth0反馈的ping的消息,
    • 将读取到的消息存储到sb字节切片里
    • 这个消息肯定是ICMP的回包,就不需要解析。ICMP的回包中,type为0,表明是ICMP的回包。

3.2.4.3、自定义构建IP报文头

什么情况下构建IP报文头和什么情况下不需要构建呢?
从两个方面说,一个是虚拟网卡tun19,一个是/dev/net/tun文件描述符;而且跟数据包的走向有关系;如下:

  • 如果数据包从tun19网卡进入的话,不需要自己添加IP报文头,tun19网卡内部会自动添加的
  • 如果数据包从tun19网卡出去的话,tun19网卡内部会自动删除IP报文头
  • 如果数据包从/dev/net/tun文件描述符里读取数据的话,读取到的数据包里已经包含IP报文头了
  • 如果数据包写入/dev/net/tun文件描述符的话,写入之前需要给数据包添加上IP报文头才才能写入

详细的看代码:

  • 看下147行:就是获取源IP,本条语句非必须,在后续的版本中,已经删除了。
  • 看下149-153行:自定义构建了一个IP报文头:初始化了版本号,协议,源IP,目的IP等等
  • 看154,155行:这两个非必须的,只是为了给大家演示,添加的。可以删除
  • 看156行:获取目的IP,即获取的是tun设备的IP,tun19的IP

3.2.4.4、重新组合数据包

将143行的切片跟150行的自定义IP报文头进行组合,将报文头放到切片的首部;

组成成新的回复数据包。

3.2.4.5、将数据包写入到/dev/net/tun里

将最新组合而成的切片写入到/dev/net/tun文件描述符里。

即tun19虚拟网卡就收到另一个宿主机eth0回馈的数据包了。

3.3、本地编译,上传到服务器

接下来,在本地环境Mac上编译下,上传到测试服务器上
Makefile内容

build:CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.goscp:scp main root@10.211.55.122:/rootall:make build && make scp

执行

make all

在这里插入图片描述

3.4、服务器上测试

分别打开两个shell终端,如下图所示:

  • 左侧的shell终端,执行tun-driver程序
  • 右侧的shell终端,执行ping 10.211.55.123 -I tun19命令

看一下运行结果:

  • 左侧打印了出了信息
  • 右侧缺没有任何信息,然后,ctrl+c终端测试,发现ping一共发送了5个数据包,但是,全部丢失。

在这里插入图片描述

再次,查看一下tun19的网卡情况
在这里插入图片描述

3.5、问题分析

通过测试我们发现执行此命令ping 10.211.55.123 -I tun19的时候
没有反馈结果,
那么,分析的思路是:

  • 先排查虚拟网卡tun19、对外的物理网卡eth0,以及另一个宿主机上的eth0网卡是否收到相应的包
  • 排查tun-driver程序是否有问题,打印日志是否符合要求
  • 排查iptables防火墙规则是否满足条件
    • 是否存在DROP策略
    • 转发规则是否打开了?(经测试,这个参数为0也可以的;默认值为1)
      • more /proc/sys/net/ipv4/ip_forward
      • 查看结果是否为1

好,接下来,先抓包分一下

3.5.1、抓包

通过抓包,来判断一下,网卡是否收到数据;如果没有收到数据肯定有问题。

3.5.1.1、分析tun19网卡是否有问题

针对tun19网卡有两种方式

3.5.1.1.1、方式一:可以查看tun19网卡的统计信息来判断

通过如下命令:

ifconfig -v tun19

查看TX,RX是否有变化

在这里插入图片描述

3.5.1.1.2、方式二:可以针对tun19网卡进行抓包来分析
tcpdump -nn icmp -i tun19

在这里插入图片描述

其实,在测试的初期是只有请求数据包,并没有回复数据包,只是不知道如何回复到当时的场景了。

3.5.1.2、分析10.211.55.122节点上的对外网卡eth0是否收到数据

tcpdump -nn icmp -i eth0

在这里插入图片描述

3.5.1.3、分析10.211.55.123节点上的对外网卡eth0是否收到数据

其实,通过上面的抓包,已经说明了,

123节点上的eth0是可以接收到ping命令的请求的,并且此网卡也对ping命令进行回复。

我们可以抓一下,并且通过wireshark简单看一下:

tcpdump -nn icmp -i eth0 -w icmp.pcap

在这里插入图片描述

icmp.pcap用WireShark打开

  • ping命令请求数据包
    在这里插入图片描述
  • ping命令回复数据包分析
    在这里插入图片描述

整个测试试验中,涉及到了三个网卡:
122节点上的虚拟网卡tun19,eth0,以及123节点上的eth0网卡

通过抓包我们可以看出来,这三个网卡都可以正常接收到数据包,应该是没有问题的

但是,执行ping 10.211.55.123 -I tun19的时候,确实没有返回值

其实,查看tun19网卡的时候,已经说明了,tun19网卡可以正常接收到123节点的反馈的数据包

也就是说,反馈的数据包从tun19网卡出来后,ping没有收到?

是这一段线路出了问题。

数据包从网卡到应用程序之间的路径是由主机防火墙来规定的。

有可能是防火墙规则给限制住了。

因此,接下来,只能查看防火墙了。

3.5.2、iptables防火墙规则分析

通过对tun19虚拟网卡进行抓包,发现:

tun19网卡已经接收到反馈的数据包了,

接下来,看一下:数据包流向图:

在这里插入图片描述

问题如下:
在这里插入图片描述

涉及到两个链:

  • PREROUTING
  • INPUT

涉及到表

  • raw表
  • mangle表
  • nat表
  • filter表

接下来,可以依次查看响应的链。
为了节省篇幅,不再一一展示了。
直接添加日志。
添加日志方式

iptables -nvL -t raw iptables -t raw -A PREROUTING -p icmp -j TRACEiptables -t raw -A OUTPUT -p icmp -j TRACEiptables -nvL -t raw 

在这里插入图片描述

按照数据包的走向,依次根据表查看规则链

tail -f /var/log/messages | grep raw | grep PREROUTING | grep DST=10.244.2.2

在这里插入图片描述

注意:打印日志可能不会马上显示,可能需要等待10秒左右
tail -f /var/log/messages | grep mangle | grep PREROUTING | grep DST=10.244.2.2tail -f /var/log/messages | grep nat | grep PREROUTING | grep DST=10.244.2.2tail -f /var/log/messages | grep mangle | grep INPUT | grep DST=10.244.2.2tail -f /var/log/messages | grep nat | grep INPUT | grep DST=10.244.2.2tail -f /var/log/messages | grep filter | grep INPUT | grep DST=10.244.2.2

其实,通过上面的命令去查询的话,也不是很靠谱的。

可能是这样的,一个数据包要不要经过某个表的某个链

是根据这个数据包自带的包状态来判断的。

比方说,这个数据包没有涉及到nat功能,可能不会经过nat表,因此,查询nat表时,就不会有响应的日志。

可能raw表,filter会必须经过的。

有点遗憾,感觉没有通过iptables日志完全查出来是什么原因,但至少也进一步定位了问题所在。

数据包从tun19网卡出来后,经过了raw表。

在后续的表中,出了问题。

3.5.3、反向路由校验

在即将放弃的时候,突然想起了一个内核参数net.ipv4.conf.all.rp_filter
尝试的修改了几次,发现OK了!

3.5.3.1、什么是反向路由校验

所谓反向路由校验,

  • 就是在一个网卡收到数据包后,

  • 把源地址和目标地址对调后查找路由出口,

  • 从而得到反身后路由出口。

  • 然后根据反向路由出口进行过滤。

  • 当rp_filter的值为1时,

    • 要求反向路由的出口必须与数据包的入口网卡是同一块,否则就会丢弃数据包。
  • 当rp_filter的值为2时,

    • 要求反向路由必须是可达的,如果反路由不可达,则会丢弃数据包。

3.5.3.2、rp_filter (Reverse Path Filtering)参数介绍

  • 定义了网卡对接收到的数据包进行反向路由验证的规则。
  • 存在三个值:0、1、2,具体含意如下:
    • 0:关闭反向路由校验
    • 1:开启严格的反向路由校验。
      • 对每个进来的数据包,校验其反向路由是否是最佳路由
      • 如果反向路由不是最佳路由,则直接丢弃该数据包。
    • 2:开启松散的反向路由校验。
      • 对每个进来的数据包,校验其源地址是否可达,即反向路由是否能通(通过任意网口),
      • 如果反向路径不通,则直接丢弃该数据包。

默认值应该是1,严格进行反向路由校验

具体可以参考下面的网址
Linux内核参数 rp_filter

3.5.3.3、ping不通的原因可能是

仅仅是猜测:
数据包经过了两个网卡,一个是虚拟网卡tun19, 一个是eth0
当对数据包进行反向路由校验时,从tun19网卡到eth0网卡失败了,它校验的时候,可能没有添加IP报文头。

3.6、设置反向路由校验,重新测试

3.6.1、设置反向路由校验参数

既然问题原因已经找到了,设置参数

sysctl -w net.ipv4.conf.all.rp_filter=2sysctl net.ipv4.conf.all.rp_filter

在这里插入图片描述

3.6.2、重新测试

ping 10.211.55.123 -I tun19

在这里插入图片描述

再次,查看iptables日志

tail -f /var/log/messages | grep DST=10.244.2.2

在这里插入图片描述

从上面的日志中可以看出来

即使在ping通的情况下,数据包也只经过了raw表,filter表
RPEROUTING链,以及INPUT链。

可能没有涉及到nat、mangle功能,因此没有nat、mangle相关日志。

3.6.3、为什么会存在大量的DUP!呢?

如果你去查百度的话,会查到一堆。

最后,在测试时无意中发现是代码的问题。

在这里插入图片描述

重新测试

在这里插入图片描述

好,到这里我们的试验目标已经完全成功了。

其实,测试用了好长时间。结局还是圆满的。

4、方案二

4.1、golang代码

package mainimport ("bytes""encoding/binary""fmt""github.com/vishvananda/netlink""golang.org/x/net/icmp""golang.org/x/net/ipv4""net""os""syscall""time""unsafe"
)const (tunName    = "tun19"tunDevice  = "/dev/net/tun"ifnameSize = 16eth0       = "10.211.55.122"tunIP      = "10.244.1.3"
)type ifreqFlags struct {IfrnName  [ifnameSize]byteIfruFlags uint16
}func ioctl(fd int, request, argp uintptr) error {_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), request, argp)if errno != 0 {fmt.Errorf("ioctl failed with '%s'\n", errno)return fmt.Errorf("ioctl failed with '%s'", errno)}return nil
}func fromZeroTerm(s []byte) string {return string(bytes.TrimRight(s, "\000"))
}func OpenTun(name string) (*os.File, string, error) {tun, err := os.OpenFile(tunDevice, os.O_RDWR|syscall.O_NONBLOCK, 0)if err != nil {fmt.Printf("OpenTun Failed! err:%v", err.Error())return nil, "", err}var ifr ifreqFlagscopy(ifr.IfrnName[:len(ifr.IfrnName)-1], []byte(name+"\000"))ifr.IfruFlags = syscall.IFF_TUN | syscall.IFF_NO_PIerr = ioctl(int(tun.Fd()), syscall.TUNSETIFF, uintptr(unsafe.Pointer(&ifr)))if err != nil {fmt.Printf("OpenTun Failed! err:%v\n", err.Error())return nil, "", err}ifName := fromZeroTerm(ifr.IfrnName[:ifnameSize])return tun, ifName, nil
}func checkSum(data []byte) uint16 {var (sum    uint32length int = len(data)index  int)for length > 1 {sum += uint32(data[index])<<8 + uint32(data[index+1])index += 2length -= 2}if length > 0 {sum += uint32(data[index])}sum += sum >> 16return uint16(^sum)
}func main() {fmt.Printf("======>Now----Test----Tun<===1232===\n")tunFile, err := createTun()if err != nil {fmt.Printf("ICMP Listen Packet Failed! err:%v\n", err.Error())return}defer tunFile.Close()icmpConn, _ := icmp.ListenPacket("ip4:icmp", eth0)defer icmpConn.Close()go tunToIcmp(icmpConn, tunFile)go icmpToTun(icmpConn, tunFile)time.Sleep(time.Hour)
}func tunToIcmp(icmpconn *icmp.PacketConn, tunFile *os.File) {var srcIP stringpacket := make([]byte, 1024*64)size := 0var err errorfor {if size, err = tunFile.Read(packet); err != nil {return}fmt.Printf("Msg Length: %d\n", binary.BigEndian.Uint16(packet[2:4]))fmt.Printf("Msg Protocol: %d (1=ICMP, 6=TCP, 17=UDP)\tsize:%d\n", packet[9], size)b := packet[:size]srcIP = GetSrcIP(b)dstIP := GetDstIP(b)fmt.Printf("Msg srcIP: %s\tdstIP:%v\n", srcIP, dstIP)var raddr = net.IPAddr{IP: net.ParseIP(dstIP)}b = b[20:size]if size, err = icmpconn.WriteTo(b, &raddr); err != nil {fmt.Println(err.Error())return}fmt.Printf("Write Msg To Icmp Conn OK! size:%d\n", size)}
}func icmpToTun(icmpconn *icmp.PacketConn, tunFile *os.File) {var sb = make([]byte, 1024*64)var addr net.Addrvar size intvar err errorfor {if size, addr, err = icmpconn.ReadFrom(sb); err != nil {continue}ipHeader := createIPv4Header(net.ParseIP(addr.String()), net.ParseIP(tunIP), os.Getpid())iphb, err := ipHeader.Marshal()if err != nil {continue}fmt.Printf("Reply MSG Length: %d\n", binary.BigEndian.Uint16(iphb[2:4]))fmt.Printf("Reply MSG Protocol: %d (1=ICMP, 6=TCP, 17=UDP)\n", iphb[9])dstIP := GetDstIP(iphb)fmt.Printf("Reply src IP: %s\tdstIP:%v\n", addr, dstIP)var rep = make([]byte, 84)rep = append(iphb, sb[:size]...)size, err = tunFile.Write(rep)if err != nil {continue}fmt.Printf("Write Msg To /dev/net/tun OK! size:%d\ttime:%v\n", size, time.Now())}
}func createIPv4Header(src, dst net.IP, id int) *ipv4.Header {iph := &ipv4.Header{Version:  ipv4.Version,Len:      ipv4.HeaderLen,TOS:      0x00,TotalLen: ipv4.HeaderLen + 64,ID:       id,Flags:    ipv4.DontFragment,FragOff:  0,TTL:      64,Protocol: 1,Checksum: 0,Src:      src,Dst:      dst,}h, _ := iph.Marshal()iph.Checksum = int(checkSum(h))return iph
}func IsIPv4(packet []byte) bool {flag := packet[0] >> 4return flag == 4
}func GetIPv4Src(packet []byte) net.IP {return net.IPv4(packet[12], packet[13], packet[14], packet[15])
}func GetIPv4Dst(packet []byte) net.IP {return net.IPv4(packet[16], packet[17], packet[18], packet[19])
}func GetSrcIP(packet []byte) string {key := ""if IsIPv4(packet) && len(packet) >= 20 {key = GetIPv4Src(packet).To4().String()}return key
}func GetDstIP(packet []byte) string {key := ""if IsIPv4(packet) && len(packet) >= 20 {key = GetIPv4Dst(packet).To4().String()}return key
}func createTun() (*os.File, error) {err := addTun()if err != nil {return nil, err}err = configTun()if err != nil {return nil, err}tunFile, _, err := OpenTun(tunName)if err != nil {return nil, err}return tunFile, nil
}func addTun() error {la := netlink.LinkAttrs{Name:  tunName,Index: 8,MTU:   1500,}tun := netlink.Tuntap{LinkAttrs: la,Mode:      netlink.TUNTAP_MODE_TUN,}l, err := netlink.LinkByName(tunName)if err == nil {netlink.LinkSetDown(l)netlink.LinkDel(l)}err = netlink.LinkAdd(&tun)if err != nil {return err}return nil
}func configTun() error {l, err := netlink.LinkByName(tunName)if err != nil {return err}ip, err := netlink.ParseIPNet(fmt.Sprintf("%s/%d", tunIP, 24))if err != nil {return err}addr := &netlink.Addr{IPNet: ip, Label: ""}if err = netlink.AddrAdd(l, addr); err != nil {return err}err = netlink.LinkSetUp(l)if err != nil {return err}return nil
}

本代码里,已经集成了虚拟网卡tun19的创建,配置了。

直接使用即可。

4.2、本地编译,上传到服务器上

build:CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o tun-driver main.goscp:scp tun-driver root@10.211.55.122:/rootall:make build && make scp

执行

make all

即可

4.3、反向路由规则校验

sysctl -w net.ipv4.conf.all.rp_filter=2sysctl net.ipv4.conf.all.rp_filter

在这里插入图片描述

4.4、测试

登录到远程服务器10.211.55.122上
root目录下

进行测试

./tun-driver
ping 10.211.55.123 -I tun19 -c 1

在这里插入图片描述

5、总结

本次试验,完成了在用户空间发送请求,经过tun类型的设备,实现请求的跨主机通信。

当然,在上面的测试用例中,你可以将ICMP改成udp来做,只不过在10.211.55.123节点上也需要开启一个udp服务来接收122节点上发送过来的数据包,然后,在将数据包转发给123节点上的eth0

6、参考

https://www.itdaan.com/blog/2017/03/09/e9d4766e2982.html

84字节,是如何计算处理的?(ctrl+f全局搜索一下84, 即可发现)
https://blog.csdn.net/Rong_Toa/article/details/86665176

https://www.freesion.com/search

云原生虚拟化:一文读懂网络虚拟化之 tun/tap 网络设备

Linux虚拟网络设备——tun/tap

TUN/TAP 学习总结(二) —— Linux TUN demo

c语言版本的tun读
https://blog.haohtml.com/archives/31687

如何读取二层,三层,四层数据包

图解:Ping 命令的工作原理


点击 下面 返回 专栏目录

<<零入门kubernetes网络实战>>技术专栏之文章目录

相关文章:

零入门kubernetes网络实战-22->基于tun设备实现在用户空间可以ping通外部节点(golang版本)

《零入门kubernetes网络实战》视频专栏地址 https://www.ixigua.com/7193641905282875942 本篇文章视频地址(稍后上传) 本篇文章主要是想做一个测试&#xff1a; 实现的目的是 希望在宿主机-1上&#xff0c;在用户空间里使用ping命令发起ping请求&#xff0c;产生的icmp类型的…...

web安全——Mybatis防止SQL注入 ssrf漏洞利用 DNS污染同源策略

目录 0x01 Mybatis防止SQL注入 0x02 sqlmap中报错注入判断 0x03 ssrf漏洞利用 0x04 SSRF重绑定 0x05 DNS污染...

smp_init过程解析

当你看到这样的log&#xff0c;会不会很慌张&#xff1f;竟然由CPU没有启动成功&#xff0c;除了什么故障&#xff1f;本文将结合我遇到的一个问题&#xff0c;将启动过程中bringup secondary cpu的过程分析一下。smp_init代码如下&#xff1a;602 void __init smp_init(void) …...

判断推理之逻辑判断

考点一翻译推理&#xff08;一&#xff09;题目特征题干或选项中出现多组“逻辑关联词”&#xff0c;如&#xff1a;“如果……那么……”、“只要……就……”、“只有……才……”、“所有”、“有些”、“……且……”、“ ……或……”等典型逻辑关联词。&#xff08;二&am…...

2023金三银四常见Handler面试总结,附带答案

以下的Handler的面试题都是在面试过程中总结出来比较常见的面试题&#xff0c;现在分享给大家&#xff0c;希望可以帮助你们&#xff01;1.Handler的实现原理从四个方面看Handler、Message、MessageQueue 和 Looper Handler:负责消息的发送和处理 Message:消息对象&#xff0c;…...

为什么机器人操作系统ROS1、ROS2这么难学

AI回复&#xff1a;ROS1、ROS2是机器人操作系统&#xff0c;涉及到很多复杂的概念和技术&#xff0c;需要有一定的编程和机器人知识基础才能学习。此外&#xff0c;ROS1和ROS2的架构和设计也有很大的不同&#xff0c;需要花费一定的时间和精力去学习和适应。但是&#xff0c;一…...

day01

授课老师 &#xff1a;陶国荣 联系方式 &#xff1a; taogrtedu.cn 授课阶段 &#xff1a; Web前端基础 授课内容 &#xff1a; HTML CSS JavaScript 文章目录一、讲师和课程介绍二、Web前端介绍1. 什么是网页2. 网页的组成3. 网页的优势4. 开发前的准备三、 HTML语法介绍…...

第四十章 linux-并发解决方法五(顺序锁seqlock)

第四十章 linux-并发解决方法四&#xff08;顺序锁seqlock&#xff09; 文章目录第四十章 linux-并发解决方法四&#xff08;顺序锁seqlock&#xff09;顺序锁的设计思想是&#xff0c;对某一共享数据读取时不加锁&#xff0c;写的时候加锁。为了保证读取的过程中不会因为写入名…...

【SPSS】交叉设计方差分析和协方差分析详细操作教程(附案例实战)

🤵‍♂️ 个人主页:@艾派森的个人主页 ✍🏻作者简介:Python学习者 🐋 希望大家多多支持,我们一起进步!😄 如果文章对你有帮助的话, 欢迎评论 💬点赞👍🏻 收藏 📂加关注+ 目录 方差分析概述 交叉设计方差分析...

playwright--核心概念和Selector定位

文章目录前言一、浏览器二、浏览器上下文三、页面和框架四、Selectors1、data-test-id selector2、CSS and XPath selector3、text 文本selector4、id定位selector5、Selector 组合定位五、内置Selector前言 Playwright提供了一组API可自动化操作Chromium&#xff0c;Firefox和…...

响应式操作实战案例

Project Reactor 框架 在Spring Boot 项目 Maven 中添加依赖管理。 <dependency><groupId>io.projectreactor</groupId><artifactId>reactor-core</artifactId> </dependency><dependency><groupId>io.projectreactor</g…...

NetApp AFF A900:针对任务关键型应用程序的解决方案

NetApp AFF A900&#xff1a;适用于数据中心的解决方案 AFF A 系列中的 AFF A900 高端 NVMe 闪存存储功能强大、安全可靠、具有故障恢复能力&#xff0c;提供您为任务关键型企业级应用程序提供动力并保持数据始终可用且安全所需的一切。 AFF A900&#xff1a;针对任务关键型应…...

使用Houdini输出四面体网格并输出tetgen格式

我们的目标是从houdini输出生成的四面体&#xff0c;希望是tetgen格式的。 众所周知&#xff0c;houdini是不能直接输出四面体的。 有三方案去解决&#xff1a; 输出点云ply文件&#xff0c;然后利用tetgen生成网格。输出Hounidi内置的.geo格式文件&#xff0c;然后写个脚本…...

组合预测 | MATLAB实现EMD-KPCA-LSTM、EMD-LSTM、LSTM多输入单输出回归预测对比

组合预测 | MATLAB实现EMD-KPCA-LSTM、EMD-LSTM、LSTM多输入单输出回归预测对比 目录 组合预测 | MATLAB实现EMD-KPCA-LSTM、EMD-LSTM、LSTM多输入单输出回归预测对比预测效果基本介绍模型描述程序设计参考资料预测效果 基本介绍 MATLAB实现EMD-KP...

【C语言】操作符详解总结(万字)

操作符详解1. 操作符分类2. 算术操作符3. 移位操作符3.1 整数的二进制是怎么形成的3.2 左移操作符3.3 右移操作符4. 位操作符5. 赋值操作符6. 单目操作符6.1 单目操作符介绍6.2 sizeof 和 数组7. 关系操作符8. 逻辑操作符9. 条件操作符9.1 练习19.2 练习210. 逗号表达式11. 下标…...

mac系统手册(帮助/说明)

文章目录1. mac自带的帮助文档2. Mac使用技巧&#xff08;提示&#xff09;2.1 聚焦搜索2.2 截图&#xff08;录制屏幕&#xff09;2.3 调出右键菜单2.4 快速查看2.5 翻译2.5.1 词典解释2.5.2 翻译&#xff08;字、词和句&#xff09;3. macOS使用手册3.1 在聚焦中进行计算和转…...

VLC播放器Demo(录像,截图等功能),Android播放器Demo可二次开发。

VLC播放器Demo&#xff08;录像&#xff0c;截图等功能&#xff09;&#xff0c;可二次开发。 GitHub地址:https://github.com/ILoveLin/VlcRecordPlayer GitHub地址:https://github.com/ILoveLin/VlcRecordPlayer GitHub地址:https://github.com/ILoveLin/VlcRecordPlayer …...

WeSpeaker支持C++部署链路

WeSpeaker正式更新C部署链路&#xff0c;推理引擎使用OnnxRuntime&#xff0c;支持从语音中提取Speaker Embedding信息&#xff0c;代码详见WeSpeaker/runtime[1]。 Libtorch和onnx的选择? Speaker Embedding提取任务流程简单&#xff0c;并且声纹模型&#xff08;如ResNet\E…...

window vscode编辑appsmith源码

前言 本来最开始用的idea打开wsl中的appsmith&#xff0c;卡得一批。最后没办法&#xff0c;用自己的电脑装成ubuntu server&#xff0c;然后vscode的远程开发对appsmith源码进行编辑。如果自己电脑内存16个G或者更大可能打开wsl中的估计会还好&#xff0c;我公司电脑只有8g所…...

操作系统面试题

操作系统一、简介篇1.解释一下什么是操作系统2.操作系统的主要功能3.软件访问硬件的几种方式4.操作系统的主要目的是什么5.为什么Linux系统下的应用程序不能直接在Windows下运行6.什么是用户态和内核态7.用户态和内核态如何切换8.什么是内核二、进程和线程篇1.多处理系统的优势…...

Kafka入门(七)

下面聊聊Kafka的配置参数&#xff0c;包括生产者的配置参数、Broker的配置参数、消费者的配置参数。 1、生产者配置参数 acks 该参数控制了生产者的消息发送确认机制&#xff0c;用于指定分区中必须有多少个副本成功接收到消息后生产者才会认为这条消息写入是成功的&#xff0c…...

微服务介绍

微服务 微服务架构发展 微服务这个概念最早是在2011年5月威尼斯的一个软件架构会议上讨论提出的&#xff0c;用于描述一些作为通用架构风格的设计原则&#xff1b;2012年3月在波兰举行的Degree Conference大会&#xff0c;james lewis做演讲&#xff0c;讨论了微服务一些原则…...

搭建SpringBoot多模块微服务项目脚手架(三)

搭建SpringBoot多模块微服务项目脚手架(三) 文章目录搭建SpringBoot多模块微服务项目脚手架(三)1.概述项目结构2.接口返回统一信息模板2.1.封装返回统一信息思路介绍2.2.封装json数据格式1.导入依赖2.封装code码3.封装json格式模板4.使用统一返回信息3.接口统一请求信息模板3.1…...

对vue3中reactive、toref、torefs、ref的详细理解

reactive&#xff1a;将平常的一个对象转换成响应式对象。所谓的响应式对象就是当页面点击修改此对象时&#xff0c;页面无需刷新而在页面上的其他地方有用到这个对象的地方会自动同步修改过来例如&#xff1a; <template><div class"container"><di…...

C++ Primer Plus 第6版 读书笔记(6) 第 6 章 分支语句和逻辑运算符

第 6 章 分支语句和逻辑运算符 C是在 C 语言基础上开发的一种集面向对象编程、泛型编程和过程化编程于一体的编程语言&#xff0c;是C语言的超集。本书是根据2003年的ISO/ANSI C标准编写的&#xff0c;通过大量短小精悍的程序详细而全面地阐述了 C的基本概念和技术&#xff0c;…...

Java Class 加密工具 ClassFinal

Jar包加密工具 ClassFinal介绍环境依赖使用说明下载加密命令行示例maven插件方式无密码模式机器绑定启动加密后的jar启动参数给密码不加密码参数直接启动1. 密码文件获取2. 交互输入参考资料介绍 ClassFinal 是一款 java class 文件安全加密工具&#xff0c;支持直接加密jar包…...

【蓝桥杯集训·每日一题】AcWing 3555. 二叉树

文章目录一、题目1、原题链接2、题目描述二、解题报告1、思路分析2、时间复杂度3、代码详解三、知识风暴最近公共祖先一、题目 1、原题链接 3555. 二叉树 2、题目描述 给定一个 n 个结点&#xff08;编号 1∼n&#xff09;构成的二叉树&#xff0c;其根结点为 1 号点。 进行 m…...

【JavaScript运行原理之V8引擎】V8引擎解析JavaScript代码原理

1. 编程语言的执行 高级语言最终都需要编译为低级语言才能被硬件执行&#xff0c;越高级的语言中间的转换时间越长&#xff0c;效率越低&#xff0c;越低级的语言执行素的越快&#xff0c;但是由于缺少高级语言便捷的语法特性所以很难编写代码。 2. 大杂烩JS 它是作者在1995…...

C++11:智能指针

文章目录1. 介绍1.1 动态内存与智能指针2. 使用2.1 创建2.2 使用3. 原理3.1 RAII3.2 像指针一样使用3.3 支持智能指针对象拷贝auto_ptrRAII4. 标准库中的智能指针4.1 unique_ptr模拟实现4.2 shared_ptr引用计数模拟实现定制删除器4.3 weak_ptrshared_ptr造成的循环引用问题与sh…...

ccc-pytorch-RNN(7)

文章目录一、RNN简介二、RNN关键结构三、RNN的训练方式四、时间序列预测五、梯度弥散和梯度爆炸问题一、RNN简介 RNN&#xff08;Recurrent Neural Network&#xff09;中文循环神经网络&#xff0c;用于处理序列数据。它与传统人工神经网络和卷积神经网络的输入和输出相互独立…...

docker安装(linux)

安装需要的软件包 yum install -y yum-utils 设置stable镜像仓库&#xff08;使用阿里云镜像&#xff09; yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo 更新yum软件包索引 yum makecache fast 安装DOCKER 引擎 yum -y…...

【数据库概论】10.1 事务及其作用

事务是一系列的数据库操作&#xff0c;是数据库应用程序的基本逻辑单元 10.1 事务的基本概念 1.事务 事务是用户定义的一个数据库操作序列&#xff0c;是一个具有原子性的操作&#xff0c;不可再分&#xff0c;一个事务内的操作要么全做、要么都不做。一般来说&#xff0c;一…...

通讯录(C++实现)

系统需求通讯录是一个可以记录亲人、好友信息的工具。本章主要利用C来实现一个通讯录管理系统系统中需要实现的功能如下:添加联系人:向通讯录中添加新人&#xff0c;信息包括&#xff08;姓名、性别、年龄、联系电话、家庭住址&#xff09;最多记录1000人显示联系人:显示通讯录…...

轻松掌握C++的模板与类模板,将Tamplate广泛运用于我们的编程生活

C提高编程 本阶段主要针对C泛型编程和STL技术做详细讲解&#xff0c;探讨C更深层的使用 泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。 模板 1.模板的概念 模板就是建立通用的模具&#xff0c;大大提高复用性 例如&#xff1a; 2.函数模板 C另一种编程思想称…...

pandas 数据预处理+数据概览 处理技巧整理(持续更新版)

这篇文章主要是整理下使用pandas的一些技巧&#xff0c;因为经常不用它&#xff0c;这些指令忘得真的很快。前段时间在数模美赛中已经栽过跟头了&#xff0c;不希望以后遇到相关问题的时候还去网上查&#xff08;主要是太杂了&#xff09;。可能读者跟我有一样的问题&#xff0…...

mmdetectionV2.x版本 训练自己的VOC数据集

mmdetection目录下创建data文件夹&#xff0c;路劲如图所示&#xff0c;不带yololabels 修改配置文件 mmdet/datasets/voc.py 配置图片格式 mmdet/datasets/xml_style.py 如果图片是jpg则改成jpg&#xff0c;是png格式就改成png&#xff0c;这里我不需要改&#xff0c;本…...

Shell - crontab 定时 git 拉取并执行 maven 打包

目录 一.引言 二.踩坑与实践 1.原始代码 2.mvn package 未执行与解决 [导入环境变量] 3.git pull 未执行与解决 [添加绝对路径] 三.总结 一.引言 git 任务部署在通道机&#xff0c;每天6点需要定时更新 jar 包并打包上线&#xff0c;所以需要在 linux 服务器上&#xff…...

408考研计算机之计算机组成与设计——知识点及其做题经验篇目3:指令的寻址方式

上篇文章我们讲到&#xff0c;指令的基本格式&#xff0c;一条指令通常包括操作码字段和地址码字段两部分&#xff1a; 操作码字段地址码字段并且我们还讲到根据操作数地址码的数目不同&#xff0c;可将指令分为零一二三四地址指令。感兴趣的小伙伴们可以看看小编的上一篇文章…...

前端包管理工具:npm,yarn、cnpm、npx、pnpm

包管理工具npm Node Package Manager&#xff0c;也就是Node包管理器&#xff1b; 但是目前已经不仅仅是Node包管理器了&#xff0c;在前端项目中我们也在使用它来管理依赖的包&#xff1b; 比如vue、vue-router、vuex、express、koa、react、react-dom、axios、babel、webpack…...

推荐系统 FM因式分解

reference&#xff1a;知乎 FM算法解析 LR算法没有二阶交叉 如果是id类特征&#xff0c;这里的x是0/1&#xff0c;raw的特征输入就是float&#xff0c;当然&#xff0c;在我的理解里&#xff0c;一般会把raw的特征进行分桶&#xff0c;还是映射到0/1特征&#xff0c;不然这个w…...

Maven基础入门

文章目录Maven简介Maven 工作模式1.仓库2.坐标Maven的基本使用1.常用命令2.生命周期依赖管理1.依赖配置2.依赖传递3.可选依赖4.排除依赖5.依赖范围IDEA配置MavenMaven简介 Apache Maven 是一个项目管理和构建工具&#xff0c;它基于项目对象模型(POM)的概念&#xff0c;通过一…...

传输层协议 TCP UDP

目录 协议前菜 端口号 ​编辑端口号范围划分 认识知名端口号(Well-Know Port Number) netstat pidof 传输层协议 UDP协议 UDP协议端格式 UDP的特点 面向数据报 UDP的缓冲区 UDP使用注意事项 基于UDP的应用层协议 TCP协议 TCP协议概念 TCP协议段格式 标志…...

一点就分享系列(实践篇6——上篇)【迟到补发】Yolo-High_level系列算法开源项目融入V8 旨在研究和兼容使用【持续更新】

一点就分享系列&#xff08;实践篇5-补更篇&#xff09;[迟到补发]—Yolo系列算法开源项目融入V8旨在研究和兼容使用[持续更新] 题外话 去年我一直复读机式强调High-level在工业界已经饱和的情况&#xff0c;目的是呼吁更多人看准自己&#xff0c;不管是数字孪生交叉领域&#…...

buu RSA 1 (Crypto 第一页)

题目描述&#xff1a; 两个文件&#xff0c;都用记事本打开&#xff0c;记住用记事本打开 pub.key: -----BEGIN PUBLIC KEY----- MDwwDQYJKoZIhvcNAQEBBQADKwAwKAIhAMAzLFxkrkcYL2wch21CM2kQVFpY97 /AvKr1rzQczdAgMBAAE -----END PUBLIC KEY-----flag.enc: A柪YJ^ 柛x秥?y…...

Python 二分查找:bisect库的使用

✅作者简介&#xff1a;人工智能专业本科在读&#xff0c;喜欢计算机与编程&#xff0c;写博客记录自己的学习历程。 &#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&…...

性能优化之HBase性能调优

HBase是Hadoop生态系统中的一个组件&#xff0c;是一个分布式、面向列存储的内存型开源数据库&#xff0c;可以支持数百万列&#xff08;MySQL4张表在HBase中对应1个表&#xff0c;4个列&#xff09;、超过10亿行的数据存储。可用作&#xff1a;冷热数据分离HBase适合作为冷数据…...

图像金字塔,原理、实现及应用

什么是图像金字塔 图像金字塔是对图像的一种多尺度表达&#xff0c;将各个尺度的图像按照分辨率从小到大&#xff0c;依次从上到下排列&#xff0c;就会形成类似金字塔的结构&#xff0c;因此称为图像金字塔。 常见的图像金字塔有两类&#xff0c;一种是高斯金字塔&#xff0…...

08-Oracle游标管理(定义,打开、获取数据及关闭游标)

目标 1.确定何时需要显示游标2.声明、打开和关闭显示游标3.从显示游标中提取数据4.了解与游标有关的属性5.使用游标FOR循环检索游标中的数据6.在游标FOR循环的子查询中声明游标7.评估使用逻辑运算符结合在一起的布尔条件游标 1、在使用一个PL/SQL块来执行DML语句或只返回一行结…...

Python判断字符串是否包含特定子串的7种方法

目录1、使用 in 和 not in2、使用 find 方法3、使用 index 方法4、使用 count 方法5、通过魔法方法6、借助 operator7、使用正则匹配转自&#xff1a;https://cloud.tencent.com/developer/article/1699719我们经常会遇这样一个需求&#xff1a;判断字符串中是否包含某个关键词…...

aop实现接口访问频率限制

引言 项目开发中我们有时会用到一些第三方付费的接口&#xff0c;这些接口的每次调用都会产生一些费用&#xff0c;有时会有别有用心之人恶意调用我们的接口&#xff0c;造成经济损失&#xff1b;或者有时需要对一些执行时间比较长的的接口进行频率限制&#xff0c;这里我就简…...