Skip to content

Commit e0184b4

Browse files
zhaojh329gl-zhaojianhui
authored andcommitted
feat(packet): Add module for low-level packet construction and parsing
Signed-off-by: Jianhui Zhao <[email protected]>
1 parent 0e8334a commit e0184b4

12 files changed

+1289
-99
lines changed

CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ install(
191191
)
192192

193193
install(
194-
FILES time.lua sys.lua file.lua dns.lua socket.lua mqtt.lua
194+
FILES time.lua sys.lua file.lua dns.lua socket.lua packet.lua mqtt.lua
195195
websocket.lua sync.lua nl.lua genl.lua ip.lua nl80211.lua
196196
DESTINATION ${LUA_INSTALL_PREFIX}/eco
197197
)

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ Lua-eco also provides some modules for you to build applications quickly:
4141
* `nl80211`: Show/manipulate wireless devices and their configuration.
4242
* `termios`: Bind unix API for terminal/serial I/O.
4343
* `ssh`: Bind libssh2.
44+
* `packet`: For low-level packet construction and parsing.
4445

4546
Would you like to try it? Kinda interesting.
4647

@@ -98,3 +99,4 @@ end, 'eco2')
9899
<*> lua-eco-websocket........................... websocket support for lua-eco
99100
<*> lua-eco-netlink............................... netlink support for lua-eco
100101
<*> lua-eco-nl80211............................... nl80211 support for lua-eco
102+
<*> lua-eco-packet................................. packet support for lua-eco

README_ZH.md

+2
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ Lua-eco 还提供了一些有用的模块,方便您快速构建应用程序:
4444
* `nl80211`: 显示/操作无线设备及其配置。
4545
* `termios`: 绑定 unix 接口用于操作终端和串口。
4646
* `ssh`: 绑定 libssh2.
47+
* `packet`: 分析和构造任意网络报文.
4748

4849
想试试吗?很有趣的!
4950

@@ -99,3 +100,4 @@ end, 'eco2')
99100
<*> lua-eco-websocket........................... websocket support for lua-eco
100101
<*> lua-eco-netlink............................... netlink support for lua-eco
101102
<*> lua-eco-nl80211............................... nl80211 support for lua-eco
103+
<*> lua-eco-packet................................. packet support for lua-eco

examples/socket/icmp/ping.lua

+14-49
Original file line numberDiff line numberDiff line change
@@ -6,46 +6,13 @@
66
--]]
77

88
local socket = require 'eco.socket'
9+
local packet = require 'eco.packet'
910
local time = require 'eco.time'
1011

11-
local ICMP_HEADER_LEN = 8
12-
1312
local dest_ip = '127.0.0.1'
1413
local local_id = math.random(0, 65535)
15-
local local_seq = 1
1614
local local_data = 'hello'
17-
18-
local function build_icmp_req()
19-
local data = {
20-
string.char(socket.ICMP_ECHO), -- type
21-
'\0', -- code
22-
'\0\0', -- checksum
23-
'\0\0', -- id: the kernel will assign it with local port
24-
string.char(local_seq >> 8, local_seq & 0xff), -- sequence
25-
local_data
26-
}
27-
28-
local_seq = local_seq + 1
29-
30-
return table.concat(data)
31-
end
32-
33-
local function parse_icmp_resp(data)
34-
if #data < ICMP_HEADER_LEN then
35-
return nil, 'invalid icmp resp'
36-
end
37-
38-
local icmp_type = data:byte(1)
39-
local id_hi = data:byte(5)
40-
local id_lo = data:byte(6)
41-
local id = (id_hi << 8) + id_lo
42-
43-
local seq_hi = data:byte(7)
44-
local seq_lo = data:byte(8)
45-
local seq = (seq_hi << 8) + seq_lo
46-
47-
return icmp_type, id, seq, #data - ICMP_HEADER_LEN
48-
end
15+
local local_seq = 1
4916

5017
local s, err = socket.icmp()
5118
if not s then
@@ -60,34 +27,32 @@ s:bind(nil, local_id)
6027
print(string.format('PING %s, %d bytes of data.', dest_ip, #local_data))
6128

6229
while true do
63-
local _, err = s:sendto(build_icmp_req(), dest_ip, 0)
30+
local pkt = packet.icmp(socket.ICMP_ECHO, 0, 0, local_seq, 'hello')
31+
local_seq = local_seq + 1
32+
33+
local _, err = s:sendto(pkt, dest_ip, 0)
6434
if err then
6535
print('send fail:', err)
6636
break
6737
end
6838

6939
local start = time.now()
7040

71-
local resp, peer = s:recvfrom(1024, 5.0)
72-
if not resp then
41+
local data, peer = s:recvfrom(1024, 5.0)
42+
if not data then
7343
print('recv fail:', peer)
7444
break
7545
end
7646

77-
local elapsed = time.now() - start
78-
79-
local icmp_type, id, seq, n = parse_icmp_resp(resp)
47+
pkt = packet.from_icmp(data)
8048

81-
if icmp_type then
82-
if icmp_type == socket.ICMP_ECHOREPLY then
83-
if id == local_id then
84-
print(string.format('%d bytes from %s: icmp_seq=%d time=%.3f ms', n, peer.ipaddr, seq, elapsed * 1000))
85-
end
86-
else
87-
print('Got ICMP packet with type ' .. icmp_type)
49+
if pkt.type == socket.ICMP_ECHOREPLY then
50+
if pkt.id == local_id then
51+
local elapsed = time.now() - start
52+
print(string.format('%d bytes from %s: icmp_seq=%d time=%.3f ms', #pkt.data, peer.ipaddr, pkt.sequence, elapsed * 1000))
8853
end
8954
else
90-
print(id)
55+
print('Got ICMP packet with type ' .. pkt.type)
9156
end
9257

9358
time.sleep(1.0)

examples/socket/icmp/ping6.lua

+13-48
Original file line numberDiff line numberDiff line change
@@ -6,47 +6,14 @@
66
--]]
77

88
local socket = require 'eco.socket'
9+
local packet = require 'eco.packet'
910
local time = require 'eco.time'
1011

11-
local ICMP6_HEADER_LEN = 8
12-
1312
local dest_ip = '::1'
1413
local local_id = math.random(0, 65535)
1514
local local_seq = 1
1615
local local_data = 'hello'
1716

18-
local function build_icmp_req()
19-
local data = {
20-
string.char(socket.ICMPV6_ECHO_REQUEST), -- type
21-
'\0', -- code
22-
'\0\0', -- checksum
23-
'\0\0', -- id: the kernel will assign it with local port
24-
string.char(local_seq >> 8, local_seq & 0xff), -- sequence
25-
local_data
26-
}
27-
28-
local_seq = local_seq + 1
29-
30-
return table.concat(data)
31-
end
32-
33-
local function parse_icmp_resp(data)
34-
if #data < ICMP6_HEADER_LEN then
35-
return nil, 'invalid icmp resp'
36-
end
37-
38-
local icmp_type = data:byte(1)
39-
local id_hi = data:byte(5)
40-
local id_lo = data:byte(6)
41-
local id = (id_hi << 8) + id_lo
42-
43-
local seq_hi = data:byte(7)
44-
local seq_lo = data:byte(8)
45-
local seq = (seq_hi << 8) + seq_lo
46-
47-
return icmp_type, id, seq, #data - ICMP6_HEADER_LEN
48-
end
49-
5017
local s, err = socket.icmp6()
5118
if not s then
5219
print(err)
@@ -60,34 +27,32 @@ s:bind(nil, local_id)
6027
print(string.format('PING %s, %d bytes of data.', dest_ip, #local_data))
6128

6229
while true do
63-
_, err = s:sendto(build_icmp_req(), dest_ip, 0)
30+
local pkt = packet.icmp6(socket.ICMPV6_ECHO_REQUEST, 0, 0, local_seq, 'hello')
31+
local_seq = local_seq + 1
32+
33+
_, err = s:sendto(pkt, dest_ip, 0)
6434
if err then
6535
print('send fail:', err)
6636
break
6737
end
6838

6939
local start = time.now()
7040

71-
local resp, peer = s:recvfrom(1024, 5.0)
72-
if not resp then
41+
local data, peer = s:recvfrom(1024, 5.0)
42+
if not data then
7343
print('recv fail:', peer)
7444
break
7545
end
7646

77-
local elapsed = time.now() - start
78-
79-
local icmp_type, id, seq, n = parse_icmp_resp(resp)
47+
pkt = packet.from_icmp6(data)
8048

81-
if icmp_type then
82-
if icmp_type == socket.ICMPV6_ECHO_REPLY then
83-
if id == local_id then
84-
print(string.format('%d bytes from %s: icmp_seq=%d time=%.3f ms', n, peer.ipaddr, seq, elapsed * 1000))
85-
end
86-
else
87-
print('Got ICMP packet with type ' .. icmp_type)
49+
if pkt.type == socket.ICMPV6_ECHO_REPLY then
50+
if pkt.id == local_id then
51+
local elapsed = time.now() - start
52+
print(string.format('%d bytes from %s: icmp_seq=%d time=%.3f ms', #pkt.data, peer.ipaddr, pkt.sequence, elapsed * 1000))
8853
end
8954
else
90-
print(id)
55+
print('Got ICMP packet with type ' .. pkt.type)
9156
end
9257

9358
time.sleep(1.0)

examples/socket/packet/arp-scan.lua

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#!/usr/bin/env eco
2+
3+
local socket = require 'eco.socket'
4+
local packet = require 'eco.packet'
5+
local link = require 'eco.ip'.link
6+
local sync = require 'eco.sync'
7+
local time = require 'eco.time'
8+
local sys = require 'eco.sys'
9+
10+
sys.signal(sys.SIGINT, function()
11+
print('\nGot SIGINT, now quit')
12+
eco.unloop()
13+
end)
14+
15+
local function usage()
16+
print('Usage:', arg[0], 'eth0', '192.168.1.1/24')
17+
os.exit(1)
18+
end
19+
20+
if #arg < 2 then
21+
usage()
22+
end
23+
24+
local function get_dev_mac(dev)
25+
local res, err = link.get(dev)
26+
if not res then
27+
error('get link:' .. err)
28+
end
29+
30+
return res.address
31+
end
32+
33+
local function send_arp(sock, dev, sender_mac, destination)
34+
local arp_pkt = packet.arp(socket.ARPOP_REQUEST, sender_mac, nil, nil, destination)
35+
local eth_pkt = packet.ether(sender_mac, 'ff:ff:ff:ff:ff:ff', socket.ETH_P_ARP, arp_pkt)
36+
37+
sock:sendto(eth_pkt, { ifname = dev })
38+
end
39+
40+
local function generate_ips(subnet)
41+
local base_ip, cidr = subnet:match('([%d%.]+)/(%d+)')
42+
if not base_ip then
43+
return nil
44+
end
45+
46+
local base = socket.ntohl(socket.inet_aton(base_ip))
47+
local mask = (~0 << (32 - cidr)) & 0xffffffff
48+
local network = base & mask
49+
local broadcast = network | ((-(mask & 0xffffffff) - 1) & 0xffffffff)
50+
local curr_ip = network
51+
52+
local ips = {}
53+
local cnt = 0
54+
55+
while curr_ip < broadcast do
56+
local ip = socket.inet_ntoa(socket.htonl(curr_ip))
57+
ips[ip]= true
58+
cnt = cnt + 1
59+
curr_ip = curr_ip + 1
60+
end
61+
62+
return ips, cnt
63+
end
64+
65+
local device = arg[1]
66+
local destination = arg[2]
67+
68+
local sender_mac = get_dev_mac(device)
69+
70+
local ips, cnt = generate_ips(destination)
71+
if not ips or cnt < 1 then
72+
usage()
73+
end
74+
75+
local wg = sync.waitgroup()
76+
77+
wg:add(cnt)
78+
79+
local sock, err = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(socket.ETH_P_ARP))
80+
if not sock then
81+
error(err)
82+
end
83+
84+
eco.run(function()
85+
while true do
86+
local data, addr = sock:recvfrom(4096)
87+
if not data then
88+
error(addr)
89+
end
90+
91+
local l2 = packet.from_ether(data)
92+
if l2.proto == socket.ETH_P_ARP then
93+
local l3 = l2:next()
94+
if l3 and l3.op == socket.ARPOP_REPLY then
95+
if ips[l3.sip] then
96+
print(l3.sha, l3.sip)
97+
wg:done()
98+
end
99+
end
100+
end
101+
end
102+
end)
103+
104+
eco.run(function()
105+
for ip in pairs(ips) do
106+
send_arp(sock, device, sender_mac, ip)
107+
time.sleep(0.01)
108+
end
109+
end)
110+
111+
wg:wait(5)
112+
os.exit(0)

0 commit comments

Comments
 (0)