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