1"""
2Internet Protocol version 4.
3
4RFC 791
5"""
6import logging
7
8from pypacker import pypacker, triggerlist, checksum
9from pypacker.layer3.ip_shared import IP_PROTO_IP6, IP_PROTO_ICMP, IP_PROTO_IGMP, IP_PROTO_TCP,\
10	IP_PROTO_UDP, IP_PROTO_ESP, IP_PROTO_PIM, IP_PROTO_IPXIP, IP_PROTO_SCTP, IP_PROTO_OSPF
11from pypacker.pypacker import FIELD_FLAG_AUTOUPDATE, FIELD_FLAG_IS_TYPEFIELD
12# handler
13from pypacker.layer3 import esp, icmp, igmp, ip6, ipx, ospf, pim
14from pypacker.layer4 import tcp, udp, sctp
15
16
17logger = logging.getLogger("pypacker")
18
19# avoid references for performance reasons
20in_cksum = checksum.in_cksum
21
22# IP options
23# http://www.iana.org/assignments/ip-parameters/ip-parameters.xml
24IP_OPT_EOOL			= 0
25IP_OPT_NOP			= 1
26IP_OPT_SEC			= 2
27IP_OPT_LSR			= 3
28IP_OPT_TS			= 4
29IP_OPT_ESEC			= 5
30IP_OPT_CIPSO			= 6
31IP_OPT_RR			= 7
32IP_OPT_SID			= 8
33IP_OPT_SSR			= 9
34IP_OPT_ZSU			= 10
35IP_OPT_MTUP			= 11
36IP_OPT_MTUR			= 12
37IP_OPT_FINN			= 13
38IP_OPT_VISA			= 14
39IP_OPT_ENCODE			= 15
40IP_OPT_IMITD			= 16
41IP_OPT_EIP			= 17
42IP_OPT_TR			= 18
43IP_OPT_ADDEXT			= 19
44IP_OPT_RTRALT			= 20
45IP_OPT_SDB			= 21
46IP_OPT_UNASSGNIED		= 22
47IP_OPT_DPS			= 23
48IP_OPT_UMP			= 24
49IP_OPT_QS			= 25
50IP_OPT_EXP			= 30
51
52
53class IPOptSingle(pypacker.Packet):
54	__hdr__ = (
55		("type", "B", 0),
56	)
57
58
59class IPOptMulti(pypacker.Packet):
60	"""
61	len = total length (header + data)
62	"""
63	__hdr__ = (
64		("type", "B", 0),
65		("len", "B", 2),
66	)
67
68	def _update_fields(self):
69		self.len = len(self)
70
71
72class IP(pypacker.Packet):
73	__hdr__ = (
74		("v_hl", "B", 69, FIELD_FLAG_AUTOUPDATE),  # = 0x45
75		("tos", "B", 0),
76		("len", "H", 20, FIELD_FLAG_AUTOUPDATE),
77		("id", "H", 0),
78		("frag_off", "H", 0),
79		("ttl", "B", 64),
80		("p", "B", IP_PROTO_TCP, FIELD_FLAG_IS_TYPEFIELD),
81		("sum", "H", 0, FIELD_FLAG_AUTOUPDATE),
82		("src", "4s", b"\x00" * 4),
83		("dst", "4s", b"\x00" * 4),
84		("opts", None, triggerlist.TriggerList)
85	)
86
87	__handler__ = {
88		IP_PROTO_ICMP: icmp.ICMP,
89		IP_PROTO_IGMP: igmp.IGMP,
90		IP_PROTO_TCP: tcp.TCP,
91		IP_PROTO_UDP: udp.UDP,
92		IP_PROTO_IP6: ip6.IP6,
93		IP_PROTO_ESP: esp.ESP,
94		IP_PROTO_PIM: pim.PIM,
95		IP_PROTO_IPXIP: ipx.IPX,
96		IP_PROTO_SCTP: sctp.SCTP,
97		IP_PROTO_OSPF: ospf.OSPF
98	}
99
100	UPDATE_DEPENDANTS = {tcp.TCP, udp.UDP}
101
102	def __get_v(self):
103		return self.v_hl >> 4
104
105	def __set_v(self, value):
106		self.v_hl = (value << 4) | (self.v_hl & 0xF)
107	# version
108	v = property(__get_v, __set_v)
109
110	def __get_hl(self):
111		return self.v_hl & 0x0F
112
113	def __set_hl(self, value):
114		self.v_hl = (self.v_hl & 0xF0) | value
115	# header length
116	hl = property(__get_hl, __set_hl)
117
118	def __get_flags(self):
119		return (self.frag_off & 0xE000) >> 13
120
121	def __set_flags(self, value):
122		self.frag_off = (self.frag_off & ~0xE000) | (value << 13)
123	flags = property(__get_flags, __set_flags)
124
125	def __get_offset(self):
126		return self.frag_off & ~0xE000
127
128	def __set_offset(self, value):
129		self.frag_off = (self.frag_off & 0xE000) | value
130	offset = property(__get_offset, __set_offset)
131
132	def create_fragments(self, fragment_len=1480):
133		"""
134		Create fragment packets from this IP packet with max fragment_len bytes each.
135		This will set the flags and offset values accordingly (see header field off).
136
137		fragment_len -- max length of a fragment (IP header + payload)
138		return -- fragment IP packets created from this packet
139		"""
140		if fragment_len % 8 != 0:
141			raise Exception("fragment_len not multipe of 8 bytes: %r" % fragment_len)
142
143		fragments = []
144		length_ip_total = len(self.bin())
145		payload = self.body_bytes
146		length_ip_header = length_ip_total - len(payload)
147		length_payload = length_ip_total - length_ip_header
148
149		off = 0
150
151		while off < length_payload:
152			payload_sub = payload[off: off + fragment_len]
153
154			ip_frag = IP(id=self.id, p=self.p, src=self.src, dst=self.dst)
155
156			if length_payload - off > fragment_len:
157				# more fragments follow
158				ip_frag.flags = 0x1
159			else:
160				# last fragment
161				ip_frag.flags = 0x0
162
163			ip_frag.offset = int(off / 8)
164			ip_frag.body_bytes = payload_sub
165			fragments.append(ip_frag)
166			off += fragment_len
167
168		return fragments
169
170	# Convenient access for: src[_s], dst[_s]
171	src_s = pypacker.get_property_ip4("src")
172	dst_s = pypacker.get_property_ip4("dst")
173	p_t = pypacker.get_property_translator("p", "IP_PROTO_")
174
175	def _dissect(self, buf):
176		total_header_length = ((buf[0] & 0xF) << 2)
177		options_length = total_header_length - 20  # total IHL - standard IP-len = options length
178
179		if options_length > 0:
180			# logger.debug("got some IP options: %s" % tl_opts)
181			self._init_triggerlist("opts", buf[20: 20 + options_length], self._parse_opts)
182		elif options_length < 0:
183			# invalid header length: assume no options at all
184			raise Exception("Invalid options length: %d" % options_length)
185		# TODO: extract real data length:
186		# There are some cases where padding can not be identified on ethernet -> do it here (eg VSS shit trailer)
187		self._init_handler(buf[9], buf[total_header_length:])
188		return total_header_length
189
190	__IP_OPT_SINGLE = {IP_OPT_EOOL, IP_OPT_NOP}
191
192	@staticmethod
193	def _parse_opts(buf):
194		"""Parse IP options and return them as list."""
195		optlist = []
196		i = 0
197		p = None
198
199		while i < len(buf):
200			# logger.debug("got IP-option type %s" % buf[i])
201			if buf[i] in IP.__IP_OPT_SINGLE:
202				p = IPOptSingle(type=buf[i])
203				i += 1
204			else:
205				olen = buf[i + 1]
206				# logger.debug("IPOptMulti")
207				p = IPOptMulti(type=buf[i], len=olen, body_bytes=buf[i + 2: i + olen])
208				# logger.debug("body bytes: %s" % buf[i + 2: i + olen])
209				i += olen		# typefield + lenfield + data-len
210				# logger.debug("IPOptMulti 2")
211			optlist.append(p)
212		return optlist
213
214	def _update_fields(self):
215		self._update_higherlayer_id()
216
217		if self.len_au_active:
218			self.len = len(self)
219		if self.v_hl_au_active:
220			# Update header length. NOTE: needs to be a multiple of 4 Bytes.
221			# logger.debug("updating: %r" % self._packet)
222				# options length need to be multiple of 4 Bytes
223			self.hl = int(self.header_len / 4) & 0xF
224		if self.sum_au_active:
225			# length changed so we have to recalculate checksum
226			# logger.debug(">>> IP: calculating sum, current: %0X" % self.sum)
227			# reset checksum for recalculation,  mark as changed / clear cache
228			self.sum = 0
229			# logger.debug(">>> IP: bytes for sum: %s" % self.header_bytes)
230			self.sum = in_cksum(self._pack_header())
231			# logger.debug("IP: new hl: %d / %d" % (self._packet.hdr_len, hdr_len_off))
232			# logger.debug("new sum: %0X" % self.sum)
233
234	def direction(self, other):
235		# logger.debug("checking direction: %s<->%s" % (self, next))
236		direction = 0
237		if self.src == other.src and self.dst == other.dst:
238			direction |= pypacker.Packet.DIR_SAME
239		if self.src == other.dst and self.dst == other.src:
240			direction |= pypacker.Packet.DIR_REV
241		if direction == 0:
242			direction = pypacker.Packet.DIR_UNKNOWN
243		return direction
244
245	def reverse_address(self):
246		self.src, self.dst = self.dst, self.src
247
248# Type of service (ip_tos), RFC 1349 ("obsoleted by RFC 2474")
249IP_TOS_DEFAULT			= 0x00			# default
250IP_TOS_LOWDELAY			= 0x10			# low delay
251IP_TOS_THROUGHPUT		= 0x08			# high throughput
252IP_TOS_RELIABILITY		= 0x04			# high reliability
253IP_TOS_LOWCOST			= 0x02			# low monetary cost - XXX
254IP_TOS_ECT			= 0x02			# ECN-capable transport
255IP_TOS_CE			= 0x01			# congestion experienced
256
257# IP precedence (high 3 bits of ip_tos), hopefully unused
258IP_TOS_PREC_ROUTINE		= 0x00
259IP_TOS_PREC_PRIORITY		= 0x20
260IP_TOS_PREC_IMMEDIATE		= 0x40
261IP_TOS_PREC_FLASH		= 0x60
262IP_TOS_PREC_FLASHOVERRIDE	= 0x80
263IP_TOS_PREC_CRITIC_ECP		= 0xA0
264IP_TOS_PREC_INTERNETCONTROL	= 0xC0
265IP_TOS_PREC_NETCONTROL		= 0xE0
266
267# Fragmentation flags (ip_off)
268IP_RF				= 0x4			# reserved
269IP_DF				= 0x2			# don't fragment
270IP_MF				= 0x1			# more fragments (not last frag)
271
272# Time-to-live (ip_ttl), seconds
273IP_TTL_DEFAULT			= 64			# default ttl, RFC 1122, RFC 1340
274IP_TTL_MAX			= 255			# maximum ttl
275