golang tun设备创建并监听
linux tun设备文件地址为/dev/net/tun.直接打开即可(关闭文件描述符创建的tun虚拟接口自动注销)
fd,err:=syscall.Open("/dev/net/tun",syscall.O_RDWR,0640)
//关闭
syscall.Close(fd)
初始化
- 配置ip地址
- 启动虚拟网卡
ip addr add xxx.xxx.xxx.xxx/24 dev tuname
ip link set dev tuname up
读取ip包
tun 是位于l3网络层,拿到手就是热乎的ip包,直接读文件描述符就ok
var(
buff []byte=make([]byte,1024)
lang int
err error
)
lang,err=syscall.Read(fd,buff)
开始
查看本机网络接口
正常情况下,有2个接口,一个lo 本地,一个enxxxx的物理接口,当然在这里有几个不重要,大概了解下就可以了
创建一个名称为tun01的设备过后(不做初始化)
初始化后,这设备接口已经可以正常通了(你甚至可以直接使用这个接口的地址进行tcp/udp通信)
这个时候呢,这个go程序这边直接读文件描述符是没数据(最开始有几个大小为48的数据包,那是初始化产生的),也不可能会有数据。经过tun01 接口才能读出数据,但是都这步都没配路由表策略,怎么会有数据走这个接口过.这个时候肯定少不了ip4伴侣ip4 报头(这部分不了解的可以查看我[golang 监听ip包]这边文章,golang ip4头结构体里面都有)
这时候你想接受到数据,你得把你有流量来往的数据的网络设备接口导到你这个网络设备接口。查看路由策略(默认策略在main表中)。这个表看数据流入的看法是先不看default,如果ip4 dst 地址前3个对上了,那么就经过那一行对应的网络设备,一个都没对上,走default via 的那个地址,dev xxxx是用于发送这个ip包的网络设备(也就是说你default那一行乱搞,顶多是没网,你同局域网或者在非default网段的网络还是能上的),这里使用ip route改路由策略不要怕改路由表改错了带来什么影响导致重装系统,直接重启一下,一切都恢复如初
更改default 数据走向至tun01
演示代码
package main
/*
#include <net/if.h>
#include <linux/if_tun.h>
#include <sys/socket.h>
#include <sys/types.h>
*/
import "C"
import (
"encoding/binary"
"fmt"
"os"
"os/exec"
"os/signal"
"strconv"
"syscall"
"unsafe"
)
func Raw2String(src uint32) string {
raw := make([]byte, 4)
binary.LittleEndian.PutUint32(raw, src)
return strconv.FormatUint(uint64(raw[0]), 10) + "." + strconv.FormatUint(uint64(raw[1]), 10) + "." + strconv.FormatUint(uint64(raw[2]), 10) + "." + strconv.FormatUint(uint64(raw[3]), 10)
}
// ip包必选,ip6自行根据wireshark进行编写,此处ip4为例
type IPHeader struct {
Version_And_Len uint8 //前4个bit为version(4 ip4,6 ip6),后bit个字节为首部length xxxx xxxx
DiffernetialtedService uint8
Tot_Len uint16
Id uint16
Flag_And_Seek uint16 //前3bit 为flag后面13bit为seek
TTL uint8
Protocol uint8
CheckSum uint16
Source uint32
Dest uint32
}
type Pointer[T any] struct {
T *T
buff []byte
}
func NewPointer[T any]() *Pointer[T] {
var t T
var ans = &Pointer[T]{buff: make([]byte, unsafe.Sizeof(t))} //获取类型占用内存字节数
ans.T = (*T)(unsafe.Pointer(&ans.buff[0])) //将指针关联过去
return ans
}
func (s *Pointer[T]) Bytes() []byte {
return s.buff
}
func Open_Tun(tuname string, ipadd string) int {
fd, err := syscall.Open("/dev/net/tun", syscall.O_RDWR, 0640)
if fd < 0 {
fmt.Fprintln(os.Stderr, "open fd failed "+err.Error())
return -1
}
var (
ifr C.struct_ifreq
)
//网络设备接口注册
copy(ifr.ifr_ifrn[:len(tuname)], []byte(tuname))
flags := NewPointer[uint16]()
*flags.T = syscall.IFF_TUN | syscall.IFF_UP | syscall.IFF_MULTICAST
copy(ifr.ifr_ifru[:2], flags.Bytes())
ans, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), syscall.TUNSETIFF, uintptr(unsafe.Pointer(&ifr)))
if int32(ans) < 0 {
fmt.Fprintln(os.Stderr, err.Error())
syscall.Close(fd)
return -1
}
//设备接口初始化
cmd := exec.Command("ip", "addr", "add", "192.168.99.99/24", "dev", tuname)
cmd.Run()
cmd = exec.Command("ip", "link", "set", "dev", tuname, "up")
cmd.Run()
return fd
}
func main() {
fd := Open_Tun("tun01", "10.0.0.2")
if fd > 0 {
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGINT)
go func() {
var (
ipheader *IPHeader
// size int
err error
buff []byte = make([]byte, 1024)
)
for {
_, err = syscall.Read(fd, buff)
if err == nil {
if buff[0] == 0x45 { //只看ip4
ipheader = (*IPHeader)(unsafe.Pointer(&buff[0]))
fmt.Printf("protocol %d src %s dst %s\n", ipheader.Protocol, Raw2String(ipheader.Source), Raw2String(ipheader.Dest))
}
}
}
}()
<-ch
syscall.Close(fd)
}
}