一、概述
上文提到实现IP的探测存活以及tcp扫描的实现,这部分来分析实现本机网卡信息获取,以及维护一张mac地址表以及ip扫描端口状态表,同时实现syn扫描功能。
项目来源:https://github.com/XinRoom/go-portScan/blob/main/util/file.go
二、识别本机网卡信息,获取网卡IP、mac、网关地址等信息
1、/core/syn/device.go
文件代码主要是用于获取网络接口信息,包括查找设备、获取设备信息、获取设备的 MAC 地址以及确定路由器等。
- func GetAllDevs() (string, error)
获取所有的网络设备列表并返回其名称、描述和地址
func GetAllDevs() (string, error) {pcapDevices, err := pcap.FindAllDevs()//获取所有网络设备if err != nil {return "", errors.New(fmt.Sprintf("list pcapDevices failed: %s", err.Error()))}var buf strings.Builderfor _, dev := range pcapDevices {buf.WriteString(fmt.Sprint("Dev:", dev.Name, "\tDes:", dev.Description))//获取网卡名称if len(dev.Addresses) > 0 {buf.WriteString(fmt.Sprint("\tAddr:", dev.Addresses[0].IP.String()))//获取第一个IP}buf.WriteString("\n")}return buf.String(), nil }
- func GetDevByIp(ip net.IP) (devName string, err error)
根据给定的 IP 地址获取对应的网络设备名称。
// GetDevByIp get dev name by dev ip (use pcap) func GetDevByIp(ip net.IP) (devName string, err error) {devices, err := pcap.FindAllDevs()if err != nil {return}for _, d := range devices {//遍历匹配每个网络设备的IPfor _, address := range d.Addresses {_ip := address.IP.To4()if _ip != nil && _ip.IsGlobalUnicast() && _ip.Equal(ip) {return d.Name, nil}}}return "", errors.New("can not find dev") }
- func GetIfaceMac(ifaceAddr net.IP) (src net.IP, mac net.HardwareAddr)
这个函数首先获取系统上所有的网络接口信息。然后,它遍历每个网络接口,检查每个接口的地址列表,以确定是否包含了给定的接口 IP 地址。一旦找到包含指定 IP 的接口,它就会返回该 IP 地址以及对应网络接口的 MAC 地址。如果未找到对应的接口 IP 地址,函数将返回两个 nil 值。
// GetIfaceMac get interface mac addr by interface ip (use golang net) func GetIfaceMac(ifaceAddr net.IP) (src net.IP, mac net.HardwareAddr) {// 获取系统上所有的网络接口信息interfaces, _ := net.Interfaces()for _, iface := range interfaces {// 获取当前网络接口的地址列表if addrs, err := iface.Addrs(); err == nil {for _, addr := range addrs {// 检查当前地址是否包含给定的接口 IP 地址if addr.(*net.IPNet).Contains(ifaceAddr) {// 如果包含,返回对应的 IP 地址和网络接口的 MAC 地址return addr.(*net.IPNet).IP, iface.HardwareAddr}}}}// 如果未找到对应的接口 IP 地址,则返回 nilreturn nil, nil }
- func GetMacByGw(gw net.IP) (srcIp net.IP, srcMac net.HardwareAddr, devname string, err error)
这个函数首先使用
GetIfaceMac函数获取网关对应的源 IP 地址和源 MAC 地址。如果未找到与网关关联的源 IP 地址,函数会返回一个错误信息。接着,它将获取的源 IP 地址转换为 IPv4 格式,并获取系统中的所有网络设备信息。随后,函数遍历设备列表,检查每个设备是否存在地址信息并且与源 IP 地址匹配。如果找到匹配的设备,则将该设备的名称赋值给devname,并返回。如果未找到匹配的设备,函数会返回另一个错误信息。func GetMacByGw(gw net.IP) (srcIp net.IP, srcMac net.HardwareAddr, devname string, err error) {// 使用 GetIfaceMac 函数获取网关对应的源 IP 地址和源 MAC 地址srcIp, srcMac = GetIfaceMac(gw)// 如果获取到的源 IP 地址为空,说明无法找到与该网关关联的设备if srcIp == nil {err = errors.New("can not find this dev by gw")return}// 将源 IP 地址转换为 IPv4 格式srcIp = srcIp.To4()// 获取系统中的所有网络设备信息devices, err := pcap.FindAllDevs()if err != nil {return}// 遍历设备列表for _, d := range devices {// 检查当前设备是否存在地址信息并且与源 IP 地址匹配if len(d.Addresses) > 0 && d.Addresses[0].IP.String() == srcIp.String() {// 如果匹配成功,将设备名称赋值为当前设备的名称devname = d.Namereturn}}// 如果未找到与源 IP 地址匹配的设备,则返回相应的错误信息err = errors.New("can not find this dev")return }
- func GetRouterV4(dst net.IP) (srcIp net.IP, srcMac net.HardwareAddr, gw net.IP, devName string, err error)
这个函数的作用是根据给定的目标 IP 地址获取路由相关的信息,包括源 IP 地址、源 MAC 地址、网关 IP 地址、设备名称和错误信息。以下是逐行解释:
func GetRouterV4(dst net.IP) (srcIp net.IP, srcMac net.HardwareAddr, gw net.IP, devName string, err error) {// 获取目标 IP 对应的源 IP 和源 MAC 地址srcIp, srcMac = GetIfaceMac(dst)// 如果源 IP 为空,说明无法找到与目标 IP 相关的设备if srcIp == nil {// 创建一个路由对象并获取路由表信息var r routing.Routerr, err = netroute.New()if err == nil {// 获取与目标 IP 相关的路由信息var iface *net.Interfaceiface, gw, srcIp, err = r.Route(dst)if err == nil {// 如果找到路由,检查对应接口是否存在,如果存在则获取其 MAC 地址,否则再次使用目标 IP 获取源 MAC 地址if iface != nil {srcMac = iface.HardwareAddr} else {_, srcMac = GetIfaceMac(srcIp)}}}// 如果发生错误或者源 MAC 地址为空,尝试获取默认网关if err != nil || srcMac == nil {gw, err = gateway.DiscoverGateway()if err == nil {srcIp, srcMac = GetIfaceMac(gw)}}}// 转换网关和源 IP 地址为 IPv4 格式gw = gw.To4()srcIp = srcIp.To4()// 获取与源 IP 地址相关的设备名称devName, err = GetDevByIp(srcIp)// 如果源 IP 为空,或者发生错误,或者源 MAC 地址为空,则返回相应的错误信息if srcIp == nil || err != nil || srcMac == nil {if err == nil {err = fmt.Errorf("err")}return nil, nil, nil, "", fmt.Errorf("no router, %s", err)}// 返回结果return }
三、维护IP状态表和mac地址表
1、/core/port/syn/watchIpStatus.go
这个代码文件里的结构和方法集合提供了一种有效管理 IP 状态更新的方式,包括记录最后更新时间、检查端口记录以及清理超时数据等功能。
- watchIpStatus结构体
ReceivedPort是一个map[uint16]struct{},用于记录接收到的端口号。LastTime是一个time.Time类型的字段,用于记录最后一次更新的时间戳。type watchIpStatus struct {ReceivedPort map[uint16]struct{}LastTime time.Time }
watchIpStatusTable结构体
watchIpS: 一个映射表,将 IP 地址与其状态关联起来。lock: 用于对共享数据进行读写锁操作的sync.RWMutex实例。isDone: 标记是否完成清理过期数据的操作。// IP状态更新表 type watchIpStatusTable struct {watchIpS map[string]*watchIpStatuslock sync.RWMutexisDone bool }
- func newWatchIpStatusTable(timeout time.Duration) (w *watchIpStatusTable)
- 创建并返回一个新的
watchIpStatusTable实例。- 启动一个单独的 goroutine (
go w.cleanTimeout(timeout)) 来清理过期数据func newWatchIpStatusTable(timeout time.Duration) (w *watchIpStatusTable) {w = &watchIpStatusTable{watchIpS: make(map[string]*watchIpStatus),}go w.cleanTimeout(timeout) //清理过期数据return }
- func (w *watchIpStatusTable) UpdateLastTime(ip string)
- 用于更新指定 IP 的最后更新时间。
- 如果 IP 不存在,则新建一个
watchIpStatus对象,并将其添加到watchIpS映射表中。// UpdateLastTime 新建或者更新LastTime func (w *watchIpStatusTable) UpdateLastTime(ip string) {lastTime := time.Now()w.lock.Lock()wi, ok := w.watchIpS[ip]if ok {wi.LastTime = lastTime} else {//IP不存在则新建一个映射表w.watchIpS[ip] = &watchIpStatus{LastTime: lastTime, ReceivedPort: make(map[uint16]struct{})}}w.lock.Unlock() }
- func (w *watchIpStatusTable) RecordPort(ip string, port uint16)
- 记录给定 IP 收到的指定端口信息。
- 如果 IP 存在于映射表中,则将该端口添加到对应 IP 的
ReceivedPort映射中。// RecordPort 记录收到的端口 func (w *watchIpStatusTable) RecordPort(ip string, port uint16) {w.lock.Lock()wi, ok := w.watchIpS[ip]if ok {wi.ReceivedPort[port] = struct{}{}}w.lock.Unlock() }
- func (w *watchIpStatusTable) HasPort(ip string, port uint16) (has bool)
- 检查给定 IP 是否曾经检测过特定端口。
- 如果 IP 存在于映射表中,并且端口也被记录,则返回
true,否则返回false。// HasPort 判断是否检测过对应端口 func (w *watchIpStatusTable) HasPort(ip string, port uint16) (has bool) {w.lock.RLock()wi, ok := w.watchIpS[ip]if ok {_, has = wi.ReceivedPort[port]}w.lock.RUnlock()return }
- func (w *watchIpStatusTable) HasIp(ip string) (has bool)
- 检查是否正在监视给定的 IP。
- 如果 IP 存在于映射表中,则返回
true,否则返回false。// HasIp 判断是否在监视对应IP func (w *watchIpStatusTable) HasIp(ip string) (has bool) {w.lock.RLock()_, has = w.watchIpS[ip]w.lock.RUnlock()return }
- func (w *watchIpStatusTable) IsEmpty() (empty bool)
- 检查表是否为空。
- 如果映射表中没有任何 IP 记录,则返回
true,否则返回false。// IsEmpty 判断目前表是否为空 func (w *watchIpStatusTable) IsEmpty() (empty bool) {w.lock.RLock()empty = len(w.watchIpS) == 0w.lock.RUnlock()return }
- func (w *watchIpStatusTable) Close()
设置
isDone为true,表示关闭监视表。func (w *watchIpStatusTable) Close() {w.isDone = true }
- func (w *watchIpStatusTable) cleanTimeout(timeout time.Duration)
这个方法
cleanTimeout是一个后台清理过期数据的功能。它使用一个无限循环来持续检查数据中记录的 IP 地址的最后更新时间,如果超过了设定的超时时间,就会将其删除。这里使用了一个无限循环for {}来持续检查。// 清理过期数据 func (w *watchIpStatusTable) cleanTimeout(timeout time.Duration) {var needDel map[string]struct{}for {needDel = make(map[string]struct{})if w.isDone {break}time.Sleep(time.Second)w.lock.RLock()for k, v := range w.watchIpS {if time.Since(v.LastTime) > timeout*time.Millisecond {needDel[k] = struct{}{}}}w.lock.RUnlock()if len(needDel) > 0 {for k := range needDel {w.lock.Lock()delete(w.watchIpS, k)w.lock.Unlock()}}} }
2、/core/port/syn/watchMacCache.go
这个代码文件定义了一个 watchMacCacheTable 结构体,它管理着缓存的 MAC 地址和监听表。其中的方法包括添加、获取和判断是否需要监听某个 IP 地址的 MAC 地址
- watchMacCache结构体
type watchMacCache struct {LastTime time.TimeMac net.HardwareAddr }
- watchMacCacheTable结构体
// Mac缓存和监听表 type watchMacCacheTable struct {watchMacC map[string]*watchMacCachelock sync.RWMutexisDone bool }
- func newWatchMacCacheTable() (w *watchMacCacheTable)
newWatchMacCacheTable()是用于创建新的watchMacCacheTable实例的函数。它初始化了一个空的watchMacCmap,这个map用来存储IP地址和对应的缓存信息。同时,这个函数也启动了一个后台任务,定期清理过期的缓存数据func newWatchMacCacheTable() (w *watchMacCacheTable) {w = &watchMacCacheTable{watchMacC: make(map[string]*watchMacCache),}go w.cleanTimeout()return }
- func (w *watchMacCacheTable) UpdateLastTime(ip string)
UpdateLastTime(ip string)方法用于更新或添加某个IP地址的最后更新时间。如果这个IP地址已经存在于缓存中,它会更新其最后更新时间;否则,它会创建一个新的缓存项并设置最后更新时间。// UpdateLastTime 新建或者更新LastTime func (w *watchMacCacheTable) UpdateLastTime(ip string) {lastTime := time.Now()w.lock.Lock()wi, ok := w.watchMacC[ip] //如果IP存在,则只更新时间if ok {wi.LastTime = lastTime} else {w.watchMacC[ip] = &watchMacCache{LastTime: lastTime}}w.lock.Unlock() }
- func (w *watchMacCacheTable) SetMac(ip string, mac net.HardwareAddr)
SetMac(ip string, mac net.HardwareAddr)方法用于设置某个IP地址对应的MAC地址。它会检查缓存中是否已存在这个IP地址,如果存在则更新其最后更新时间和MAC地址,如果不存在则创建新的缓存项并设置MAC地址。// SetMac 设置Mac地址 func (w *watchMacCacheTable) SetMac(ip string, mac net.HardwareAddr) {lastTime := time.Now()w.lock.Lock()wi, ok := w.watchMacC[ip]//IP存在则更新IP和时间if ok {wi.LastTime = lastTimewi.Mac = mac} else {//不存在则重新建一个mac映射表w.watchMacC[ip] = &watchMacCache{LastTime: lastTime, Mac: mac}wi.Mac = mac}w.lock.Unlock() }
- func (w *watchMacCacheTable) GetMac(ip string) (mac net.HardwareAddr)
GetMac(ip string) (mac net.HardwareAddr)方法用于获取某个IP地址对应的MAC地址。它会根据传入的IP地址在缓存中查找并返回相应的MAC地址。// GetMac 获取Mac地址缓存 func (w *watchMacCacheTable) GetMac(ip string) (mac net.HardwareAddr) {w.lock.RLock()wi, ok := w.watchMacC[ip]if ok {mac = wi.Mac}w.lock.RUnlock()return }
- func (w *watchMacCacheTable) IsNeedWatch(ip string) (has bool)
IsNeedWatch(ip string) (has bool)方法用于判断是否需要监听某个IP地址的MAC地址。它会检查对应IP地址的MAC地址是否为nil,如果是nil,则表示需要监听。// IsNeedWatch 判断是否需要监视 func (w *watchMacCacheTable) IsNeedWatch(ip string) (has bool) {w.lock.RLock()wm, ok := w.watchMacC[ip]has = ok && wm.Mac == nilw.lock.RUnlock()return }
- func (w *watchMacCacheTable) IsEmpty() (empty bool)
IsEmpty() (empty bool)方法用于判断当前缓存表是否为空// IsEmpty 判断目前表是否为空 func (w *watchMacCacheTable) IsEmpty() (empty bool) {w.lock.RLock()empty = len(w.watchMacC) == 0w.lock.RUnlock()return }
- func (w *watchMacCacheTable) Close()
Close()方法用于关闭清理过期数据的后台任务。func (w *watchMacCacheTable) Close() {w.isDone = true }
- func (w *watchMacCacheTable) cleanTimeout()
这个方法是一个后台循环任务,它会定期检查缓存中所有IP地址的最后更新时间。如果某个IP地址的最后更新时间超过了设定的超时时间(这里是10秒),则会将这些过期的IP地址从缓存中删除。这个过程会一直持续,直到
isDone被设置为true。// 清理过期数据 func (w *watchMacCacheTable) cleanTimeout() {var needDel map[string]struct{}for {needDel = make(map[string]struct{})if w.isDone {break}time.Sleep(2 * time.Second)w.lock.RLock()for k, v := range w.watchMacC {if time.Since(v.LastTime) > 10*time.Second {needDel[k] = struct{}{}}}w.lock.RUnlock()if len(needDel) > 0 {for k := range needDel {w.lock.Lock()delete(w.watchMacC, k)w.lock.Unlock()}}} }
四、tcp SYN端口扫描的实现
1、/core/port/syn/syn.go
这段代码是一个TCP SYN扫描器的实现,用于检测主机上开放的TCP端口
- SynScanner 结构体
包含了需要的网络信息和扫描器的配置选项,同时还有一些用于发送和接收数据包的方法。
type SynScanner struct {srcMac, gwMac net.HardwareAddr // macAddrdevName string // eth dev(pcap)// gateway (if applicable), and source IP addresses to use.gw, srcIp net.IP// pcaphandle *pcap.Handle// opts and buf allow us to easily serialize packets in the send() method.opts gopacket.SerializeOptions// Buffer复用bufPool *sync.Pool//option port.OptionopenPortChan chan port.OpenIpPort // inside chanportProbeWg sync.WaitGroupretChan chan port.OpenIpPort // results chanlimiter *limiter.Limiterctx context.ContextwatchIpStatusT *watchIpStatusTable // IpStatusCacheTablewatchMacCacheT *watchMacCacheTable // MacCachesisDone bool }
- func NewSynScanner(firstIp net.IP, retChan chan port.OpenIpPort, option port.Option) (ss *SynScanner, err error)
扫描器的构造函数,用于初始化扫描器并返回一个
SynScanner实例。它初始化了网络接口、获取了网关信息、创建了pcap实例用于抓取和发送数据包,设置了相关的过滤器,同时启动了接收数据包的协程。此外,它还创建了用于监视IP状态和MAC缓存的数据结构。// NewSynScanner 用于创建 SynScanner 对象的函数,firstIp: 用于选择路由; openPortChan: 结果返回通道 func NewSynScanner(firstIp net.IP, retChan chan port.OpenIpPort, option port.Option) (ss *SynScanner, err error) {// 选项验证if option.Rate < 10 {err = errors.New("速率不能小于 10")return}// 设备和网络信息的变量var devName stringvar srcIp net.IPvar srcMac net.HardwareAddrvar gw net.IP// 根据 NextHop 选项指定设备或获取路由器信息if option.NextHop != "" {// 如果指定了 NextHop,基于该网关 IP 获取信息gw = net.ParseIP(option.NextHop).To4()srcIp, srcMac, devName, err = GetMacByGw(gw)} else {// 使用提供的第一个 IP 获取路由器信息srcIp, srcMac, gw, devName, err = GetRouterV4(firstIp)}if err != nil {return}if devName == "" {err = errors.New("获取路由器信息失败:没有设备名称")return}rand.Seed(time.Now().Unix())// 使用必要的详细信息初始化 SynScanner 对象ss = &SynScanner{opts: gopacket.SerializeOptions{FixLengths: true,ComputeChecksums: true,},srcIp: srcIp,srcMac: srcMac,devName: devName,bufPool: &sync.Pool{New: func() interface{} {return gopacket.NewSerializeBuffer()},},option: option,openPortChan: make(chan port.OpenIpPort, cap(retChan)),retChan: retChan,limiter: limiter.NewLimiter(limiter.Every(time.Second/time.Duration(option.Rate)), option.Rate/10),ctx: context.Background(),watchIpStatusT: newWatchIpStatusTable(time.Duration(option.Timeout)),watchMacCacheT: newWatchMacCacheTable(),}// 处理不同的扫描模式if ss.option.FingerPrint || ss.option.Httpx {// 如果启用了指纹识别或 HTTP 探测,启动端口探测处理程序go ss.portProbeHandle()} else {// 否则,在单独的 goroutine 中处理开放端口通道go func() {for t := range ss.openPortChan {ss.portProbeWg.Add(1)ss.retChan <- tss.portProbeWg.Done()}}()}// 为网络包捕获打开 pcap 句柄handle, err := pcap.OpenLive(devName, 1024, false, pcap.BlockForever)if err != nil {return}// 设置包过滤器以减少监控包的数量handle.SetBPFFilter(fmt.Sprintf("ether dst %s && (arp || tcp[tcpflags] == tcp-syn|tcp-ack)", srcMac.String()))ss.handle = handle// 开始监听接收到的包go ss.recv()if gw != nil {// 如果存在网关,则检索其 MAC 地址var dstMac net.HardwareAddrdstMac, err = ss.getHwAddrV4(gw)if err != nil {return}ss.gwMac = dstMac}return }
- func (ss *SynScanner) Scan(dstIp net.IP, dst uint16) (err error)
Scan方法用于扫描指定IP和端口。它首先检查IP是否为IPv4地址,然后更新了要监视的IP状态。接着,构建了需要发送的TCP SYN数据包,发送给目标主机。在发送数据包的过程中,会动态调整发送频率,根据待发送队列的占用情况,来控制发送速率。// Scan 用于扫描此扫描器的目标 IP 地址和端口。 func (ss *SynScanner) Scan(dstIp net.IP, dst uint16) (err error) {if ss.isDone {return io.EOF // 如果扫描已完成,则返回 EOF 错误}// 当队列缓冲区达到一定比例时,调整发送速率if len(ss.openPortChan)*10 >= cap(ss.openPortChan)*8 {if ss.option.Rate/2 != 0 {ss.limiter.SetLimit(limiter.Every(time.Second / time.Duration(ss.option.Rate/2)))}} else if len(ss.openPortChan)*10 >= cap(ss.openPortChan)*9 {ss.limiter.SetLimit(1)} else {ss.limiter.SetLimit(limiter.Every(time.Second / time.Duration(ss.option.Rate)))}dstIp = dstIp.To4()if dstIp == nil {return errors.New("不是 IPv4 地址") // 如果不是 IPv4 地址,则返回错误}// 观察 IP,首先更新其时间ipStr := dstIp.String()ss.watchIpStatusT.UpdateLastTime(ipStr)// 首先获取应该发送数据包的 MAC 地址。var dstMac net.HardwareAddrif ss.gwMac != nil {dstMac = ss.gwMac} else {// 如果是内网 IP,则从缓存中获取 MAC 地址;否则,获取该 IP 的 MAC 地址mac := ss.watchMacCacheT.GetMac(ipStr)if mac != nil {dstMac = mac} else {dstMac, err = ss.getHwAddrV4(dstIp)if err != nil {return}}}// 构建所需的所有网络层eth := layers.Ethernet{SrcMAC: ss.srcMac,DstMAC: dstMac,EthernetType: layers.EthernetTypeIPv4,}ip4 := layers.IPv4{SrcIP: ss.srcIp,DstIP: dstIp,Version: 4,TTL: 128,Id: uint16(40000 + rand.Intn(10000)),Flags: layers.IPv4DontFragment,Protocol: layers.IPProtocolTCP,}tcp := layers.TCP{SrcPort: layers.TCPPort(49000 + rand.Intn(10000)), // 随机源端口,用于确定接收目标端口范围DstPort: layers.TCPPort(dst),SYN: true,Window: 65280,Seq: uint32(500000 + rand.Intn(10000)),Options: []layers.TCPOption{{OptionType: layers.TCPOptionKindMSS,OptionLength: 4,OptionData: []byte{0x05, 0x50}, // 1360},{OptionType: layers.TCPOptionKindNop,},{OptionType: layers.TCPOptionKindWindowScale,OptionLength: 3,OptionData: []byte{0x08},},{OptionType: layers.TCPOptionKindNop,},{OptionType: layers.TCPOptionKindNop,},{OptionType: layers.TCPOptionKindSACKPermitted,OptionLength: 2,},},}tcp.SetNetworkLayerForChecksum(&ip4)// 每次循环迭代发送一个数据包,直到发送完毕ss.send(ð, &ip4, &tcp)return }
- func (ss *SynScanner) Wait()
Wait方法用于等待扫描器完成所有扫描任务。它首先等待IP状态监视表为空,然后等待扫描结果通道中的数据发送完毕。func (ss *SynScanner) Wait() {// Delay 2s for a reply from the last packetfor i := 0; i < 20; i++ {if ss.watchIpStatusT.IsEmpty() {break}time.Sleep(time.Millisecond * 100)}// wait inside chan is emptyfor len(ss.openPortChan) != 0 {time.Sleep(time.Millisecond * 20)}// wait portProbe taskss.portProbeWg.Wait() }
- func (ss *SynScanner) Close()
Close方法用于关闭扫描器,清理相关资源,包括关闭pcap实例、关闭通道,并将SynScanner结构体标记为已完成状态。// Close 清理处理程序和通道。 func (ss *SynScanner) Close() {ss.isDone = true // 标记扫描完成if ss.handle != nil {// 在 Linux 下,如果没有数据包要嗅探,pcap 不能使用 BlockForever 停止// 参考:https://github.com/google/gopacket/issues/890// 参考:https://github.com/google/gopacket/issues/1089if runtime.GOOS == "linux" {// 创建一个 ARP 数据包发送以关闭 pcap,使用自身 MAC 地址和 IP 地址eth := layers.Ethernet{SrcMAC: ss.srcMac,DstMAC: ss.srcMac,EthernetType: layers.EthernetTypeARP,}arp := layers.ARP{AddrType: layers.LinkTypeEthernet,Protocol: layers.EthernetTypeIPv4,HwAddressSize: 6,ProtAddressSize: 4,Operation: layers.ARPReply,SourceHwAddress: []byte(ss.srcMac),SourceProtAddress: []byte(ss.srcIp),DstHwAddress: []byte(ss.srcMac),DstProtAddress: []byte(ss.srcIp),}// 打开一个新的 pcap 句柄,发送 ARP 数据包并关闭句柄handle, _ := pcap.OpenLive(ss.devName, 1024, false, time.Second)buf := ss.bufPool.Get().(gopacket.SerializeBuffer)gopacket.SerializeLayers(buf, ss.opts, ð, &arp)handle.WritePacketData(buf.Bytes())handle.Close()buf.Clear()ss.bufPool.Put(buf)}ss.handle.Close() // 关闭 pcap 句柄}// 关闭观察 MAC 地址表和 IP 状态表if ss.watchMacCacheT != nil {ss.watchMacCacheT.Close()}if ss.watchIpStatusT != nil {ss.watchIpStatusT.Close()}// 清理变量并关闭通道ss.watchMacCacheT = nilss.watchIpStatusT = nilclose(ss.openPortChan)close(ss.retChan) }
- func (ss *SynScanner) Wait()
WaitLimiter方法用于等待速率限制,它调用了limiter.Wait()方法,确保发送速率不超过预设的限制。func (ss *SynScanner) WaitLimiter() error {return ss.limiter.Wait(ss.ctx) }
- func (ss *SynScanner) GetDevName() string
GetDevName方法用于返回扫描器所选设备的名称// GetDevName Get the device name after the route selection func (ss *SynScanner) GetDevName() string {return ss.devName }
- func (ss *SynScanner) portProbeHandle()
portProbeHandle方法是一个协程,用于处理端口扫描结果。它不断从openPortChan通道中获取扫描结果,并针对每个端口进行服务识别和探测。如果启用了服务识别和 HTTP 探测,会调用fingerprint.PortIdentify和fingerprint.ProbeHttpInfo方法,识别端口所提供的服务类型和 HTTP 信息,并根据需要更新_openIpPort的相关信息。最后将结果发送到retChan通道中。func (ss *SynScanner) portProbeHandle() {for openIpPort := range ss.openPortChan {ss.portProbeWg.Add(1)go func(_openIpPort port.OpenIpPort) {if _openIpPort.Port != 0 {if ss.option.FingerPrint {ss.WaitLimiter()_openIpPort.Service, _openIpPort.Banner, _ = fingerprint.PortIdentify("tcp", _openIpPort.Ip, _openIpPort.Port, 2*time.Second)}if ss.option.Httpx && (_openIpPort.Service == "" || _openIpPort.Service == "http" || _openIpPort.Service == "https") {ss.WaitLimiter()_openIpPort.HttpInfo, _ = fingerprint.ProbeHttpInfo(_openIpPort.Ip, _openIpPort.Port, 2*time.Second)if _openIpPort.HttpInfo != nil {if strings.HasPrefix(_openIpPort.HttpInfo.Url, "https") {_openIpPort.Service = "https"} else {_openIpPort.Service = "http"}}}}ss.retChan <- _openIpPortss.portProbeWg.Done()}(openIpPort)} }
- func (ss *SynScanner) getHwAddrV4(arpDst net.IP) (mac net.HardwareAddr, err error)
getHwAddrV4方法用于获取数据包的目标硬件地址,它发送 ARP 请求并等待 ARP 回复以获取目标 IP 的 MAC 地址。在循环中,它会不断发送 ARP 请求,并尝试从缓存中获取 MAC 地址,直到获取到回复或超时为止// getHwAddrV4 获取我们数据包的目标硬件地址。 func (ss *SynScanner) getHwAddrV4(arpDst net.IP) (mac net.HardwareAddr, err error) {ipStr := arpDst.String()// 检查是否需要监视此 IP 的 ARPif ss.watchMacCacheT.IsNeedWatch(ipStr) {return nil, errors.New("此 IP 的 ARP 已在监视中")}ss.watchMacCacheT.UpdateLastTime(ipStr) // 更新 IP 监视时间// 准备发送 ARP 请求的网络层。eth := layers.Ethernet{SrcMAC: ss.srcMac,DstMAC: net.HardwareAddr{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, // 目标 MAC 地址为广播地址EthernetType: layers.EthernetTypeARP,}arp := layers.ARP{AddrType: layers.LinkTypeEthernet,Protocol: layers.EthernetTypeIPv4,HwAddressSize: 6,ProtAddressSize: 4,Operation: layers.ARPRequest, // ARP 请求SourceHwAddress: []byte(ss.srcMac),SourceProtAddress: []byte(ss.srcIp),DstHwAddress: []byte{0, 0, 0, 0, 0, 0}, // 目标硬件地址为空DstProtAddress: []byte(arpDst), // 目标 IP 地址}// 发送 ARP 请求if err = ss.sendArp(ð, &arp); err != nil {return nil, err}start := time.Now() // 记录开始时间var retry int// 循环等待获取 ARP 回复for {mac = ss.watchMacCacheT.GetMac(ipStr) // 获取缓存的 MAC 地址if mac != nil {return mac, nil // 如果找到了 MAC 地址,返回}// 等待 600 毫秒获取 ARP 回复if time.Since(start) > time.Millisecond*600 {return nil, errors.New("获取 ARP 回复超时")}retry += 1// 每 25 次尝试重新发送 ARP 请求if retry%25 == 0 {if err = ss.send(ð, &arp); err != nil {return nil, err}}time.Sleep(time.Millisecond * 10) // 休眠 10 毫秒} }
- func (ss *SynScanner) send(l ...gopacket.SerializableLayer) error
- func (ss *SynScanner) sendArp(l ...gopacket.SerializableLayer) error
send和sendArp方法用于发送数据包到网络。send方法用于发送TCP数据包,而sendArp方法用于发送ARP请求。注意到在sendArp方法中,发送的ARP包长度被硬编码为42字节,可能存在需要校正的情况。// send sends the given layers as a single packet on the network. func (ss *SynScanner) send(l ...gopacket.SerializableLayer) error {buf := ss.bufPool.Get().(gopacket.SerializeBuffer)defer func() {buf.Clear()ss.bufPool.Put(buf)}()if err := gopacket.SerializeLayers(buf, ss.opts, l...); err != nil {return err}return ss.handle.WritePacketData(buf.Bytes()) }// send sends the given layers as a single packet on the network., need fix padding func (ss *SynScanner) sendArp(l ...gopacket.SerializableLayer) error {buf := ss.bufPool.Get().(gopacket.SerializeBuffer)defer func() {buf.Clear()ss.bufPool.Put(buf)}()if err := gopacket.SerializeLayers(buf, ss.opts, l...); err != nil {return err}return ss.handle.WritePacketData(buf.Bytes()[:42]) // need fix padding }
- func (ss *SynScanner) recv()
recv方法负责接收网络上的数据包。它首先解析数据包的各个层级(Ethernet、IPv4、TCP、ARP)并根据协议类型进行处理。对于ARP包,它会解析获取到的IP地址和MAC地址,并更新缓存。对于TCP包,它会匹配源IP和端口,并根据特定条件发送TCP响应包,并将开放端口的信息发送到openPortChan通道中func (ss *SynScanner) recv() {// 初始化网络层数据eth := layers.Ethernet{SrcMAC: ss.srcMac,DstMAC: nil,EthernetType: layers.EthernetTypeIPv4,}ip4 := layers.IPv4{SrcIP: ss.srcIp,DstIP: []byte{}, // 空的目标 IP 地址Version: 4,TTL: 64,Protocol: layers.IPProtocolTCP,}tcp := layers.TCP{SrcPort: 0,DstPort: 0,RST: true,ACK: true,Seq: 1,}// 解码相关的网络层var ipLayer layers.IPv4var tcpLayer layers.TCPvar arpLayer layers.ARPvar ethLayer layers.Ethernetvar foundLayerTypes []gopacket.LayerType// 创建一个包解析器用于解析数据包中的各层信息parser := gopacket.NewDecodingLayerParser(layers.LayerTypeEthernet,ðLayer,&ipLayer,&tcpLayer,&arpLayer,)// 全局变量var err errorvar data []bytevar ipStr stringvar _port uint16for {// 读取下一个数据包data, _, err = ss.handle.ReadPacketData()if err != nil {if err == io.EOF {return // 如果出现 EOF,则退出循环}continue // 继续读取下一个数据包}// 如果操作已完成,则退出循环if ss.isDone {return}// 解码 TCP 或 ARP 数据包err = parser.DecodeLayers(data, &foundLayerTypes)if len(foundLayerTypes) == 0 {continue // 如果未找到任何层信息,继续下一个数据包的解析}// 解析 ARP 数据包if arpLayer.SourceProtAddress != nil {ipStr = net.IP(arpLayer.SourceProtAddress).String()if ss.watchMacCacheT.IsNeedWatch(ipStr) {ss.watchMacCacheT.SetMac(ipStr, arpLayer.SourceHwAddress)}arpLayer.SourceProtAddress = nil // 清除 ARP 解析状态continue}// 匹配 IP 和端口的 TCP 数据包if tcpLayer.DstPort != 0 && tcpLayer.DstPort >= 49000 && tcpLayer.DstPort <= 59000 {ipStr = ipLayer.SrcIP.String()_port = uint16(tcpLayer.SrcPort)if !ss.watchIpStatusT.HasIp(ipStr) { // 检查 IPcontinue} else {if ss.watchIpStatusT.HasPort(ipStr, _port) { // 检查端口continue} else {ss.watchIpStatusT.RecordPort(ipStr, _port) // 记录端口}}// 如果收到 SYN 和 ACK,则将结果发送到 openPortChanif tcpLayer.SYN && tcpLayer.ACK {ss.openPortChan <- port.OpenIpPort{Ip: ipLayer.SrcIP,Port: _port,}// 回复目标eth.DstMAC = ethLayer.SrcMACip4.DstIP = ipLayer.SrcIPtcp.DstPort = tcpLayer.SrcPorttcp.SrcPort = tcpLayer.DstPort// RST && ACKtcp.Ack = tcpLayer.Seq + 1tcp.Seq = tcpLayer.Acktcp.SetNetworkLayerForChecksum(&ip4)ss.send(ð, &ip4, &tcp) // 发送响应}tcpLayer.DstPort = 0 // 清除 TCP 解析状态}} }
2、/core/port/syn/syn_test.go
这份代码中的结构体和方法都是占位符,它们并没有真正的实现功能,而是简单地返回错误或空值。可能是为了暴露接口,并允许使用者在不同情况下自定义更多功能的实现。
type synScanner struct { }: 定义了一个synScanner结构体,但它并未实现port.SynScanner接口。
NewSynScanner函数: 这是一个函数签名,该函数预期创建一个synScanner类型的结构体。它接受三个参数:firstIp用于选择路由,retChan是结果返回通道,option是端口扫描的选项配置。然而,在这个实现中,它只是返回一个nil结构体和一个ErrorNoSyn错误,意味着没有实际功能被实现。
Scan、WaitLimiter、Wait和Close方法: 这些方法是synScanner结构体的方法,但它们只是返回了nil或空的操作。它们用于实现port.SynScanner接口,但在这个实现中并未真正执行任何操作。
GetAllDevs函数: 这个函数用于获取所有设备信息,但类似其他方法,它只是返回一个空字符串和ErrorNoSyn错误。//go:build nosynpackage synimport ("github.com/XinRoom/go-portScan/core/port""net" )type synScanner struct { }// NewSynScanner firstIp: Used to select routes; openPortChan: Result return channel func NewSynScanner(firstIp net.IP, retChan chan port.OpenIpPort, option port.Option) (ss *synScanner, err error) {return nil, ErrorNoSyn }func (ss *synScanner) Scan(dstIp net.IP, dst uint16) error {return nil } func (ss *synScanner) WaitLimiter() error {return nil } func (ss *synScanner) Wait() {} func (ss *synScanner) Close() {}func GetAllDevs() (string, error) {return "", ErrorNoSyn }
3、/core/port/syn/comm.go
这个主要设置syn扫描选项的一些初始值
package synimport ("errors""github.com/XinRoom/go-portScan/core/port" )var ErrorNoSyn = errors.New("no syn support")var DefaultSynOption = port.Option{Rate: 1500,Timeout: 800, }

