1#!/usr/bin/python
2#
3# Copyright 2015 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17import random
18
19from scapy import all as scapy
20from socket import *
21
22import net_test
23
24TCP_FIN = 1
25TCP_SYN = 2
26TCP_RST = 4
27TCP_PSH = 8
28TCP_ACK = 16
29
30TCP_WINDOW = 14400
31
32PING_IDENT = 0xff19
33PING_PAYLOAD = "foobarbaz"
34PING_SEQ = 3
35PING_TOS = 0x83
36
37# For brevity.
38UDP_PAYLOAD = net_test.UDP_PAYLOAD
39
40
41def _RandomPort():
42  return random.randint(1025, 65535)
43
44def _GetIpLayer(version):
45  return {4: scapy.IP, 6: scapy.IPv6}[version]
46
47def _SetPacketTos(packet, tos):
48  if isinstance(packet, scapy.IPv6):
49    packet.tc = tos
50  elif isinstance(packet, scapy.IP):
51    packet.tos = tos
52  else:
53    raise ValueError("Can't find ToS Field")
54
55def UDP(version, srcaddr, dstaddr, sport=0):
56  ip = _GetIpLayer(version)
57  # Can't just use "if sport" because None has meaning (it means unspecified).
58  if sport == 0:
59    sport = _RandomPort()
60  return ("UDPv%d packet" % version,
61          ip(src=srcaddr, dst=dstaddr) /
62          scapy.UDP(sport=sport, dport=53) / UDP_PAYLOAD)
63
64def UDPWithOptions(version, srcaddr, dstaddr, sport=0):
65  if version == 4:
66    packet = (scapy.IP(src=srcaddr, dst=dstaddr, ttl=39, tos=0x83) /
67              scapy.UDP(sport=sport, dport=53) /
68              UDP_PAYLOAD)
69  else:
70    packet = (scapy.IPv6(src=srcaddr, dst=dstaddr,
71                         fl=0xbeef, hlim=39, tc=0x83) /
72              scapy.UDP(sport=sport, dport=53) /
73              UDP_PAYLOAD)
74  return ("UDPv%d packet with options" % version, packet)
75
76def SYN(dport, version, srcaddr, dstaddr, sport=0, seq=-1):
77  ip = _GetIpLayer(version)
78  if sport == 0:
79    sport = _RandomPort()
80  if seq == -1:  # Can't use None because it means unspecified.
81    seq = random.getrandbits(32)
82  return ("TCP SYN",
83          ip(src=srcaddr, dst=dstaddr) /
84          scapy.TCP(sport=sport, dport=dport,
85                    seq=seq, ack=0,
86                    flags=TCP_SYN, window=TCP_WINDOW))
87
88def RST(version, srcaddr, dstaddr, packet):
89  ip = _GetIpLayer(version)
90  original = packet.getlayer("TCP")
91  was_syn_or_fin = (original.flags & (TCP_SYN | TCP_FIN)) != 0
92  return ("TCP RST",
93          ip(src=srcaddr, dst=dstaddr) /
94          scapy.TCP(sport=original.dport, dport=original.sport,
95                    ack=original.seq + was_syn_or_fin, seq=None,
96                    flags=TCP_RST | TCP_ACK, window=TCP_WINDOW))
97
98def SYNACK(version, srcaddr, dstaddr, packet):
99  ip = _GetIpLayer(version)
100  original = packet.getlayer("TCP")
101  return ("TCP SYN+ACK",
102          ip(src=srcaddr, dst=dstaddr) /
103          scapy.TCP(sport=original.dport, dport=original.sport,
104                    ack=original.seq + 1, seq=None,
105                    flags=TCP_SYN | TCP_ACK, window=None))
106
107def ACK(version, srcaddr, dstaddr, packet, payload=""):
108  ip = _GetIpLayer(version)
109  original = packet.getlayer("TCP")
110  was_syn_or_fin = (original.flags & (TCP_SYN | TCP_FIN)) != 0
111  ack_delta = was_syn_or_fin + len(original.payload)
112  desc = "TCP data" if payload else "TCP ACK"
113  flags = TCP_ACK | TCP_PSH if payload else TCP_ACK
114  return (desc,
115          ip(src=srcaddr, dst=dstaddr) /
116          scapy.TCP(sport=original.dport, dport=original.sport,
117                    ack=original.seq + ack_delta, seq=original.ack,
118                    flags=flags, window=TCP_WINDOW) /
119          payload)
120
121def FIN(version, srcaddr, dstaddr, packet):
122  ip = _GetIpLayer(version)
123  original = packet.getlayer("TCP")
124  was_syn_or_fin = (original.flags & (TCP_SYN | TCP_FIN)) != 0
125  ack_delta = was_syn_or_fin + len(original.payload)
126  return ("TCP FIN",
127          ip(src=srcaddr, dst=dstaddr) /
128          scapy.TCP(sport=original.dport, dport=original.sport,
129                    ack=original.seq + ack_delta, seq=original.ack,
130                    flags=TCP_ACK | TCP_FIN, window=TCP_WINDOW))
131
132def GRE(version, srcaddr, dstaddr, proto, packet):
133  if version == 4:
134    ip = scapy.IP(src=srcaddr, dst=dstaddr, proto=net_test.IPPROTO_GRE)
135  else:
136    ip = scapy.IPv6(src=srcaddr, dst=dstaddr, nh=net_test.IPPROTO_GRE)
137  packet = ip / scapy.GRE(proto=proto) / packet
138  return ("GRE packet", packet)
139
140def ICMPPortUnreachable(version, srcaddr, dstaddr, packet):
141  if version == 4:
142    # Linux hardcodes the ToS on ICMP errors to 0xc0 or greater because of
143    # RFC 1812 4.3.2.5 (!).
144    return ("ICMPv4 port unreachable",
145            scapy.IP(src=srcaddr, dst=dstaddr, proto=1, tos=0xc0) /
146            scapy.ICMPerror(type=3, code=3) / packet)
147  else:
148    return ("ICMPv6 port unreachable",
149            scapy.IPv6(src=srcaddr, dst=dstaddr) /
150            scapy.ICMPv6DestUnreach(code=4) / packet)
151
152def ICMPPacketTooBig(version, srcaddr, dstaddr, packet):
153  if version == 4:
154    return ("ICMPv4 fragmentation needed",
155            scapy.IP(src=srcaddr, dst=dstaddr, proto=1) /
156            scapy.ICMPerror(type=3, code=4, unused=1280) / str(packet)[:64])
157  else:
158    udp = packet.getlayer("UDP")
159    udp.payload = str(udp.payload)[:1280-40-8]
160    return ("ICMPv6 Packet Too Big",
161            scapy.IPv6(src=srcaddr, dst=dstaddr) /
162            scapy.ICMPv6PacketTooBig() / str(packet)[:1232])
163
164def ICMPEcho(version, srcaddr, dstaddr):
165  ip = _GetIpLayer(version)
166  icmp = {4: scapy.ICMP, 6: scapy.ICMPv6EchoRequest}[version]
167  packet = (ip(src=srcaddr, dst=dstaddr) /
168            icmp(id=PING_IDENT, seq=PING_SEQ) / PING_PAYLOAD)
169  _SetPacketTos(packet, PING_TOS)
170  return ("ICMPv%d echo" % version, packet)
171
172def ICMPReply(version, srcaddr, dstaddr, packet):
173  ip = _GetIpLayer(version)
174  # Scapy doesn't provide an ICMP echo reply constructor.
175  icmpv4_reply = lambda **kwargs: scapy.ICMP(type=0, **kwargs)
176  icmp = {4: icmpv4_reply, 6: scapy.ICMPv6EchoReply}[version]
177  packet = (ip(src=srcaddr, dst=dstaddr) /
178            icmp(id=PING_IDENT, seq=PING_SEQ) / PING_PAYLOAD)
179  # IPv6 only started copying the tclass to echo replies in 3.14.
180  if version == 4 or net_test.LINUX_VERSION >= (3, 14):
181    _SetPacketTos(packet, PING_TOS)
182  return ("ICMPv%d echo reply" % version, packet)
183
184def NS(srcaddr, tgtaddr, srcmac):
185  solicited = inet_pton(AF_INET6, tgtaddr)
186  last3bytes = tuple([ord(b) for b in solicited[-3:]])
187  solicited = "ff02::1:ff%02x:%02x%02x" % last3bytes
188  packet = (scapy.IPv6(src=srcaddr, dst=solicited) /
189            scapy.ICMPv6ND_NS(tgt=tgtaddr) /
190            scapy.ICMPv6NDOptSrcLLAddr(lladdr=srcmac))
191  return ("ICMPv6 NS", packet)
192
193def NA(srcaddr, dstaddr, srcmac):
194  packet = (scapy.IPv6(src=srcaddr, dst=dstaddr) /
195            scapy.ICMPv6ND_NA(tgt=srcaddr, R=0, S=1, O=1) /
196            scapy.ICMPv6NDOptDstLLAddr(lladdr=srcmac))
197  return ("ICMPv6 NA", packet)
198
199