1"""
2Transmission Control Protocol (TCP)
3
4RFC 675 - Specification of Internet Transmission Control Program, December 1974 Version
5RFC 793 - TCP v4
6RFC 1122 - includes some error corrections for TCP
7RFC 1323 - TCP-Extensions
8RFC 1379 - Extending TCP for Transactions—Concepts
9RFC 1948 - Defending Against Sequence Number Attacks
10RFC 2018 - TCP Selective Acknowledgment Options
11RFC 4614 - A Roadmap for TCP Specification Documents
12RFC 5681 - TCP Congestion Control
13RFC 6298 - Computing TCP's Retransmission Timer
14RFC 6824 - TCP Extensions for Multipath Operation with Multiple Addresses
15"""
16import logging
17import struct
18
19from pypacker import pypacker, triggerlist, checksum
20from pypacker.pypacker import FIELD_FLAG_AUTOUPDATE, FIELD_FLAG_IS_TYPEFIELD
21from pypacker.structcbs import unpack_H, pack_ipv4_header, pack_ipv6_header
22
23from pypacker.layer4 import ssl
24from pypacker.layer567 import bgp, http, mqtt, rtp, sip, telnet, tpkt, pmap
25
26# avoid references for performance reasons
27in_cksum = checksum.in_cksum
28
29logger = logging.getLogger("pypacker")
30
31# TCP control flags
32TH_FIN		= 0x01		# end of data
33TH_SYN		= 0x02		# synchronize sequence numbers
34TH_RST		= 0x04		# reset connection
35TH_PUSH		= 0x08		# push
36TH_ACK		= 0x10		# acknowledgment number set
37TH_URG		= 0x20		# urgent pointer set
38TH_ECE		= 0x40		# ECN echo, RFC 3168
39TH_CWR		= 0x80		# congestion window reduced
40
41TCP_PORT_MAX	= 65535		# maximum port
42TCP_WIN_MAX	= 65535		# maximum (unscaled) window
43
44# TCP Options (opt_type) - http://www.iana.org/assignments/tcp-parameters
45TCP_OPT_EOL		= 0		# end of option list
46TCP_OPT_NOP		= 1		# no operation
47TCP_OPT_MSS		= 2		# maximum segment size
48TCP_OPT_WSCALE		= 3		# window scale factor, RFC 1072
49TCP_OPT_SACKOK		= 4		# SACK permitted, RFC 2018
50TCP_OPT_SACK		= 5		# SACK, RFC 2018
51TCP_OPT_ECHO		= 6		# echo (obsolete), RFC 1072
52TCP_OPT_ECHOREPLY	= 7		# echo reply (obsolete), RFC 1072
53TCP_OPT_TIMESTAMP	= 8		# timestamp, RFC 1323
54TCP_OPT_POCONN		= 9		# partial order conn, RFC 1693
55TCP_OPT_POSVC		= 10		# partial order service, RFC 1693
56TCP_OPT_CC		= 11		# connection count, RFC 1644
57TCP_OPT_CCNEW		= 12		# CC.NEW, RFC 1644
58TCP_OPT_CCECHO		= 13		# CC.ECHO, RFC 1644
59TCP_OPT_ALTSUM		= 14		# alt checksum request, RFC 1146
60TCP_OPT_ALTSUMDATA	= 15		# alt checksum data, RFC 1146
61TCP_OPT_SKEETER		= 16		# Skeeter
62TCP_OPT_BUBBA		= 17		# Bubba
63TCP_OPT_TRAILSUM	= 18		# trailer checksum
64TCP_OPT_MD5		= 19		# MD5 signature, RFC 2385
65TCP_OPT_SCPS		= 20		# SCPS capabilities
66TCP_OPT_SNACK		= 21		# selective negative acks
67TCP_OPT_REC		= 22		# record boundaries
68TCP_OPT_CORRUPT		= 23		# corruption experienced
69TCP_OPT_SNAP		= 24		# SNAP
70TCP_OPT_TCPCOMP		= 26		# TCP compression filter
71TCP_OPT_MAX		= 27
72
73
74class TCPOptSingle(pypacker.Packet):
75	__hdr__ = (
76		("type", "B", 0),
77	)
78
79	type_t = pypacker.get_property_translator("type", "TCP_OPT_")
80
81
82class TCPOptMulti(pypacker.Packet):
83	"""
84	len = total length (header + data)
85	"""
86	__hdr__ = (
87		("type", "B", 0),
88		("len", "B", 2, FIELD_FLAG_AUTOUPDATE)
89	)
90
91	type_t = pypacker.get_property_translator("type", "TCP_OPT_")
92
93	def _update_fields(self):
94		if self.len_au_active:
95			self.len = len(self)
96
97TCP_PROTO_TELNET	= 23
98TCP_PROTO_TPKT		= 102
99TCP_PROTO_PMAP		= 111
100TCP_PROTO_BGP		= 179
101TCP_PROTO_SSL		= 443
102TCP_PROTO_MQTT		= (1883, 8883)
103TCP_PROTO_HTTP		= (80, 8008, 8080)
104TCP_PROTO_RTP 		= (5004, 5005)
105TCP_PROTO_SIP		= (5060, 5061)
106
107
108def cb_get_flag_description(value, value_name):
109	descr = [name_d for value_d, name_d in value_name.items() if value_d & value != 0]
110	return " | ".join(descr)
111
112
113class TCP(pypacker.Packet):
114	__hdr__ = (
115		("sport", "H", 0xDEAD),
116		("dport", "H", 0, FIELD_FLAG_IS_TYPEFIELD),
117		("seq", "I", 0xDEADBEEF),
118		("ack", "I", 0),
119		("off_x2", "B", ((5 << 4) | 0), FIELD_FLAG_AUTOUPDATE),  # 10*4 Byte
120		("flags", "B", TH_SYN),  # acces via (obj.flags & TH_XYZ)
121		("win", "H", TCP_WIN_MAX),
122		("sum", "H", 0, FIELD_FLAG_AUTOUPDATE),
123		("urp", "H", 0),
124		("opts", None, triggerlist.TriggerList)
125	)
126
127	# 4 bits | 4 bits
128	# offset | reserved
129	# offset * 4 = header length
130	def __get_off(self):
131		return self.off_x2 >> 4
132
133	def __set_off(self, value):
134		self.off_x2 = (value << 4) | (self.off_x2 & 0xF)
135	off = property(__get_off, __set_off)
136
137	# return real header length based on header info
138	def __get_hlen(self):
139		return self.off * 4
140
141	# set real header length based on header info (should be n*4)
142	def __set_hlen(self, value):
143		self.off = int(value / 4)
144
145	hlen = property(__get_hlen, __set_hlen)
146	flags_t = pypacker.get_property_translator("flags", "TH_", cb_get_description=cb_get_flag_description)
147
148	__handler__ = {
149		TCP_PROTO_BGP: bgp.BGP,
150		TCP_PROTO_TELNET: telnet.Telnet,
151		TCP_PROTO_TPKT: tpkt.TPKT,
152		TCP_PROTO_PMAP: pmap.Pmap,
153		TCP_PROTO_MQTT: mqtt.MQTTBase,
154		TCP_PROTO_HTTP: http.HTTP,
155		TCP_PROTO_SSL: ssl.SSL,
156		TCP_PROTO_RTP: rtp.RTP,
157		TCP_PROTO_SIP: sip.SIP
158	}
159
160	def _update_fields(self):
161		# TCP-checksum needs to be updated on one of the following:
162		# - this layer itself or any higher layer changed
163		# - changes to the IP-pseudoheader
164		update = True
165		# update header length. NOTE: needs to be a multiple of 4 Bytes.
166		# options length need to be multiple of 4 Bytes
167		if self._header_changed and self.off_x2_au_active:
168			self.off = int(self.header_len / 4) & 0xF
169
170		# we need some IP as lower layer
171		if self._lower_layer is None:
172			return
173
174		#self._update_higherlayer_id()
175
176		try:
177			# changes to IP-layer, don't mind if this isn't IP
178			if not self._lower_layer._header_changed:
179				# pseudoheader didn't change, further check for changes in layers
180				update = self._changed()
181		except:
182			# Assume not an IP packet: we can't calculate the checksum
183			update = False
184
185		if update and self.sum_au_active:
186			self._calc_sum()
187
188	def _dissect(self, buf):
189		# update dynamic header parts. buf: 1010???? -clear reserved-> 1010 -> *4
190		ol = ((buf[12] >> 4) << 2) - 20	 # dataoffset - TCP-standard length
191
192		if ol > 0:
193			# parse options, add offset-length to standard-length
194			opts_bytes = buf[20: 20 + ol]
195			self._init_triggerlist("opts", opts_bytes, self._parse_opts)
196		elif ol < 0:
197			raise Exception("invalid header length")
198
199		ports = [unpack_H(buf[0:2])[0], unpack_H(buf[2:4])[0]]
200
201		try:
202			# source or destination port should match
203			htype = [x for x in ports if x in self._id_handlerclass_dct[TCP]][0]
204			#logger.debug("TCP: trying to set handler, type: %d = %s" %
205			#(type, self._id_handlerclass_dct[TCP][type]))
206			self._init_handler(htype, buf[20 + ol:])
207		except:
208			# no type found
209			pass
210		return 20 + ol
211
212	__TCP_OPT_SINGLE = {TCP_OPT_EOL, TCP_OPT_NOP}
213
214	@staticmethod
215	def _parse_opts(buf):
216		"""Parse TCP options using buf and return them as List."""
217		optlist = []
218		i = 0
219
220		while i < len(buf):
221			# logger.debug("got TCP-option type %s" % buf[i])
222			if buf[i] in TCP.__TCP_OPT_SINGLE:
223				p = TCPOptSingle(type=buf[i])
224				i += 1
225			else:
226				olen = buf[i + 1]
227				# p = TCPOptMulti(type=buf[i], len=olen, body_bytes=buf[i + 2: i + olen])
228				p = TCPOptMulti(buf[i: i + olen])
229				i += olen     # typefield + lenfield + data-len
230			optlist.append(p)
231		# logger.debug("tcp: parseopts finished, length: %d" % len(optlist))
232		return optlist
233
234	def _calc_sum(self):
235		"""Recalculate the TCP-checksum. This won't reset changed state."""
236		# TCP and underwriting are freaky bitches: we need the IP pseudoheader
237		# to calculate their checksum.
238		try:
239			# we need src/dst for checksum-calculation
240			src, dst = self._lower_layer.src, self._lower_layer.dst
241			self.sum = 0
242			# logger.debug("TCP sum recalc: IP=%d / %s / %s" % (len(src), src, dst))
243
244			tcp_bin = self.header_bytes + self.body_bytes
245			# IP-pseudoheader, check if version 4 or 6
246			if len(src) == 4:
247				s = pack_ipv4_header(src, dst, 6, len(tcp_bin))  # 6 = TCP
248			else:
249				s = pack_ipv6_header(src, dst, 6, len(tcp_bin))  # 6 = TCP
250
251			# Get checksum of concatenated pseudoheader+TCP packet
252			# logger.debug("pseudoheader: %r" % s)
253			# logger.debug("tcp_bin: %r" % tcp_bin)
254			# assign via non-shadowed variable to trigger re-packing
255			self.sum = in_cksum(s + tcp_bin)
256			# logger.debug(">>> new checksum: %0X" % self._sum)
257		except:
258			# Not an IP packet as lower layer (src, dst not present) or invalid src/dst
259			# logger.debug("could not calculate checksum: %r" % e)
260			pass
261
262	def direction(self, other):
263		direction = 0
264		# logger.debug("checking direction: %s<->%s" % (self, other))
265		if self.sport == other.sport and self.dport == other.dport:
266			direction |= pypacker.Packet.DIR_SAME
267		if self.sport == other.dport and self.dport == other.sport:
268			direction |= pypacker.Packet.DIR_REV
269		if direction == 0:
270			direction = pypacker.Packet.DIR_UNKNOWN
271		return direction
272
273	def reverse_address(self):
274		self.sport, self.dport = self.dport, self.sport
275
276	ra_segments = pypacker.get_ondemand_property("ra_segments", lambda: {})
277
278	def ra_collect(self, pkt_list):
279		"""
280		Collect TCP segments which have the same direction as this packet.
281		Concatenated segments can be retrieved via ra_bin(). Does not check for missing
282		segments.
283		return -- bytes_cnt, [True|False]: amount of bytes added (sum of body bytes),
284			final packet found (RST or FIN). True indicates that ra_bin() can be called.
285		"""
286		if type(pkt_list) is not list:
287			pkt_list = [pkt_list]
288
289		bts_cnt = 0
290
291		for segment in pkt_list:
292			if self.direction(segment) != pypacker.Packet.DIR_SAME or len(segment.body_bytes) == 0:
293				continue
294
295			seq_store = segment.seq
296			# final packet found: connection is going to be terminated
297			if (segment.flags & TH_FIN) != 0 or (segment.flags & TH_RST) != 0:
298				return 0, True
299
300			if seq_store < self.seq:
301				logger.warning("seq of new segment is lower than start")
302				seq_store += 0xFFFF
303
304			self.ra_segments[seq_store] = segment.body_bytes
305			bts_cnt += len(segment.body_bytes)
306
307		return bts_cnt, False
308
309	def ra_bin(self):
310		"""
311		Assemble retrieved TCP segments in sorted order (body bytes of TCP segments) and
312		flushes the internal buffer.
313		"""
314		self.ra_segments[self.seq] = self.body_bytes
315		sorted_list = sorted(self.ra_segments.items(), key=lambda t: t[0])
316		bts_lst = [value for key, value in sorted_list]
317		self.ra_segments.clear()
318		return b"".join(bts_lst)
319