[Go][Python]命令行工具-IP扫描
简介
前段时间帮朋友调试内网离线环境,其中有个小问题是要扫描局域网下有哪些存活主机,一开始用python脚本实现的,写起来比较简单,性能也还行。后来捣鼓了下又重写一个。
Python版本
并发线程
最开始就是用Python实现的,代码量也不大,临时用用也还不错。
import socket
from concurrent.futures import ThreadPoolExecutor
def scan_ip_port(ip: str, port: int, timeout: int = 1) -> bool:
"""
尝试连接指定的 IP 和端口,返回是否成功连接。
"""
try:
# 创建一个 socket 对象
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout) # 设置超时时间
# 尝试连接
result = sock.connect_ex((ip, port))
sock.close()
# 如果返回 0,表示连接成功
return result == 0
except Exception:
# print(f"Error scanning {ip}:{port}")
return False
def scan_network(base_ip: str, port: int = 80, max_workers=500):
"""
扫描局域网内存活的 IP 地址,通过检测指定端口是否开放。
"""
active_ips = []
# 使用线程池并发扫描
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = []
for i in range(1, 255):
ip = f"{base_ip}.{i}"
# 提交任务到线程池
future = executor.submit(scan_ip_port, ip, port)
futures.append((ip, future))
# 获取结果
for ip, future in futures:
if future.result():
print(f"{ip} is active (port {port} is open)")
active_ips.append(ip)
# else:
# print(f"{ip} is inactive or port {port} is closed")
return active_ips
if __name__ == "__main__":
# 假设局域网的 IP 段是 192.168.0.x
base_ip = "192.168.0"
port = 80
active_ips = scan_network(base_ip, port)
print(f"Active IPs: {active_ips}")
用time工具运行获取执行时间和内存消耗
$ /usr/bin/time -f 'Elapsed Time: %e s Max RSS: %M kbytes' python3 ipscan.py
192.168.0.101 is active (port 80 is open)
192.168.0.106 is active (port 80 is open)
192.168.0.107 is active (port 80 is open)
Active IPs: ['192.168.0.101', '192.168.0.106', '192.168.0.107']
Elapsed Time: 1.08 s Max RSS: 28220 kbytes
并发异步
让DeepSeek改写成异步方式
import asyncio
import socket
from asyncio import Semaphore
async def async_scan_ip_port(ip: str, port: int, timeout: int = 1, sem: Semaphore = None) -> bool:
"""
异步版本端口扫描 (使用asyncio协程实现)
"""
async with sem: # 通过信号量控制并发量
try:
# 创建异步连接
reader, writer = await asyncio.wait_for(
asyncio.open_connection(ip, port),
timeout=timeout
)
writer.close()
await writer.wait_closed()
return True
except (socket.error, asyncio.TimeoutError, OSError):
return False
except Exception as e:
print(f"Unexpected error with {ip}:{port} - {str(e)}")
return False
async def async_scan_network(base_ip: str, port: int = 80, max_concurrency=500) -> list:
"""
异步扫描核心逻辑
"""
sem = Semaphore(max_concurrency) # 控制最大并发量
tasks = []
# 创建扫描任务
for i in range(1, 255):
ip = f"{base_ip}.{i}"
task = asyncio.create_task(async_scan_ip_port(ip, port, sem=sem))
tasks.append((ip, task))
# 收集结果
active_ips = []
for ip, task in tasks:
try:
if await task:
print(f"{ip} is active (port {port} is open)")
active_ips.append(ip)
except Exception as e:
print(f"Task error for {ip}: {str(e)}")
return active_ips
if __name__ == "__main__":
# 运行事件循环
base_ip = "192.168.0"
port = 80
active_ips = asyncio.run(async_scan_network(base_ip, port))
print(f"Active IPs: {active_ips}")
同样记录下运行时间和内存消耗。运行时间差不多,但内存消耗更低。
$ /usr/bin/time -f 'Elapsed Time: %e s Max RSS: %M kbytes' python3 ipscan_async.py
192.168.0.101 is active (port 80 is open)
192.168.0.106 is active (port 80 is open)
192.168.0.107 is active (port 80 is open)
Active IPs: ['192.168.0.101', '192.168.0.106', '192.168.0.107']
Elapsed Time: 1.05 s Max RSS: 21508 kbytes
Go
本来打算支持ICMP协议扫描的,但是尝试后有点Bug,所以只实现了TCP扫描。
├── cmd
│ ├── icmp.go
│ ├── root.go
│ └── tcp.go
├── go.mod
├── go.sum
├── main.go
└── pkg
├── icmp.go
├── tcp.go
└── utils.go
pkg/utils.go用于解析CIDR地址,获取对应的IP列表
package pkg
import "net"
func inc(ip net.IP) {
for i := len(ip) - 1; i >= 0; i-- {
ip[i]++
if ip[i] > 0 {
break
}
}
}
func GetIpList(cidr string) ([]string, error) {
_, ipNet, err := net.ParseCIDR(cidr)
if err != nil {
return nil, err
}
var ips []string
for ip := ipNet.IP.Mask(ipNet.Mask); ipNet.Contains(ip); inc(ip) {
ips = append(ips, ip.String())
}
// 移除网络地址和广播地址, eg: 192.168.0.0 and 192.168.0.255
if len(ips) > 2 {
ips = ips[1 : len(ips)-1]
}
return ips, nil
}
pkg/tcp.go实现了Go连接测试
package pkg
import (
"fmt"
"net"
"time"
)
func SendTCP(ip string, port int, timeout int) error {
// 直接使用ip:port方式可能对ipv6不生效, 所以使用JoinHostPort
conn, err := net.DialTimeout("tcp", net.JoinHostPort(ip, fmt.Sprintf("%d", port)), time.Duration(timeout)*time.Second)
if err != nil {
// 忽略错误
// return fmt.Errorf("failed to dial tcp: %v", err)
return nil
}
defer conn.Close()
fmt.Printf("Connect to %s:%d successfully\n", ip, port)
return nil
}
cmd/tcp.go用于解析命令行参数
package cmd
import (
"fmt"
"ipscan/pkg"
"sync"
"github.com/spf13/cobra"
)
var (
port int
)
// tcpCmd represents the tcp command
var tcpCmd = &cobra.Command{
Use: "tcp",
Short: "使用TCP协议进行网络探测",
Long: `使用TCP协议进行网络探测`,
Example: `
./ipscan tcp 192.168.0.0/24
./ipscan tcp 192.168.0.0/24 -p 443 # 指定端口号为 443
`,
Run: func(cmd *cobra.Command, args []string) {
for _, arg := range args {
ips, err := pkg.GetIpList(arg)
if err != nil {
fmt.Println(err)
continue
}
timeout, err := cmd.Flags().GetInt("timeout")
if err != nil {
fmt.Println(err)
continue
}
wg := &sync.WaitGroup{}
for _, ip := range ips {
wg.Add(1)
go func(ip string) {
defer wg.Done()
err := pkg.SendTCP(ip, port, timeout)
if err != nil {
fmt.Println(err)
}
}(ip)
}
wg.Wait()
}
},
}
func init() {
rootCmd.AddCommand(tcpCmd)
tcpCmd.Flags().IntVarP(&port, "port", "p", 80, "Port to scan")
}
cmd/root.go
package cmd
import (
"os"
"github.com/spf13/cobra"
)
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "ipscan",
Short: "局域网存活IP扫描器",
Long: `扫描网段下所有存活的IP地址`,
}
func Execute() {
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
}
}
func init() {
rootCmd.PersistentFlags().IntP("timeout", "t", 1, "timeout")
}
main.go
package main
import "ipscan/cmd"
func main() {
cmd.Execute()
}
TODO
本文来自博客园,作者:花酒锄作田,转载请注明原文链接:https://www.cnblogs.com/XY-Heruo/p/18788151

浙公网安备 33010602011771号