pkt.c 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. /*
  2. * pkt.c
  3. * ptunnel is licensed under the BSD license:
  4. *
  5. * Copyright (c) 2004-2011, Daniel Stoedle <daniels@cs.uit.no>,
  6. * Yellow Lemon Software. All rights reserved.
  7. *
  8. * Copyright (c) 2017-2019, Toni Uhlig <matzeton@googlemail.com>
  9. *
  10. * Redistribution and use in source and binary forms, with or without
  11. * modification, are permitted provided that the following conditions are met:
  12. *
  13. * - Redistributions of source code must retain the above copyright notice,
  14. * this list of conditions and the following disclaimer.
  15. *
  16. * - Redistributions in binary form must reproduce the above copyright notice,
  17. * this list of conditions and the following disclaimer in the documentation
  18. * and/or other materials provided with the distribution.
  19. *
  20. * - Neither the name of the Yellow Lemon Software nor the names of its
  21. * contributors may be used to endorse or promote products derived from this
  22. * software without specific prior written permission.
  23. *
  24. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  25. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  26. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  27. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  28. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  29. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  30. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  31. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  32. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  33. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  34. * POSSIBILITY OF SUCH DAMAGE.
  35. *
  36. * Contacting the author:
  37. * You can get in touch with me, Daniel Stødle (that's the Norwegian letter oe,
  38. * in case your text editor didn't realize), here: <daniels@cs.uit.no>
  39. *
  40. * The official ptunnel website is here:
  41. * <http://www.cs.uit.no/~daniels/PingTunnel/>
  42. *
  43. * Note that the source code is best viewed with tabs set to 4 spaces.
  44. */
  45. #ifndef WIN32
  46. #include <netinet/in.h>
  47. #include <arpa/inet.h>
  48. #include <pthread.h>
  49. #endif
  50. #include <sys/time.h>
  51. #include "ptunnel.h"
  52. #include "pkt.h"
  53. #include "pdesc.h"
  54. #include "options.h"
  55. #include "utils.h"
  56. static proxy_desc_t * handle_incoming_tunnel_request(unsigned bytes,
  57. struct sockaddr_in * addr,
  58. int icmp_sock,
  59. icmp_echo_packet_t * const pkt,
  60. ping_tunnel_pkt_t * const pt_pkt)
  61. {
  62. struct timeval tt;
  63. struct in_addr in_addr;
  64. uint32_t init_state;
  65. proxy_desc_t * cur;
  66. pt_log(kLog_info, "Incoming tunnel request from %s.\n", inet_ntoa(*(struct in_addr *)&addr->sin_addr));
  67. gettimeofday(&tt, 0);
  68. if (tt.tv_sec < seq_expiry_tbl[pt_pkt->id_no]) {
  69. pt_log(kLog_verbose, "Dropping request: ID was recently in use.\n");
  70. return NULL;
  71. }
  72. in_addr.s_addr = pt_pkt->dst_ip;
  73. pt_log(kLog_info,
  74. "Starting new session to %s:%d with ID %d\n",
  75. inet_ntoa(in_addr),
  76. ntohl(pt_pkt->dst_port),
  77. pt_pkt->id_no);
  78. if ((opts.restrict_dst_ip && opts.given_dst_ip && opts.given_dst_ip != pt_pkt->dst_ip) ||
  79. (opts.restrict_dst_port && (uint32_t)-1 != opts.given_dst_port &&
  80. opts.given_dst_port != ntohl(pt_pkt->dst_port))) {
  81. pt_log(kLog_info, "Destination administratively prohibited!\n");
  82. return NULL;
  83. }
  84. if (opts.password) {
  85. init_state = kProto_authenticate;
  86. } else {
  87. init_state = kProto_data;
  88. }
  89. cur = (proxy_desc_t *)create_and_insert_proxy_desc(
  90. pt_pkt->id_no, pkt->identifier, 0, addr, pt_pkt->dst_ip, ntohl(pt_pkt->dst_port), init_state, kProxy_flag);
  91. if (!cur) {
  92. /* if failed, abort. Logging is done in create_insert_proxy_desc */
  93. pt_log(kLog_error, "Failed to create proxy descriptor!\n");
  94. return NULL;
  95. }
  96. if (pt_pkt->data_len > 0) {
  97. handle_data(pkt, bytes, cur);
  98. }
  99. if (init_state == kProto_authenticate) {
  100. pt_log(kLog_debug, "Sending authentication challenge..\n");
  101. /* Send challenge */
  102. cur->challenge = generate_challenge();
  103. memcpy(cur->buf, cur->challenge, sizeof(challenge_t));
  104. queue_packet(icmp_sock, cur, cur->buf, sizeof(challenge_t), 0, 0, kProto_authenticate | cur->type_flag);
  105. }
  106. return cur;
  107. }
  108. static void handle_auth_request(unsigned bytes,
  109. int icmp_sock,
  110. icmp_echo_packet_t * const pkt,
  111. proxy_desc_t * const cur,
  112. challenge_t * const challenge)
  113. {
  114. if (!opts.password) {
  115. pt_log(kLog_error,
  116. "This proxy requires a password! "
  117. "Please supply one usin g the -x switch.\n");
  118. send_termination_msg(cur, icmp_sock);
  119. cur->should_remove = 1;
  120. return;
  121. }
  122. #ifdef ENABLE_SHA512
  123. if (opts.force_sha512) {
  124. pt_log(kLog_debug, "Got authentication challenge - sending SHA512 response\n");
  125. generate_response_sha512(&challenge->plain, &challenge->digest);
  126. } else
  127. #endif
  128. {
  129. pt_log(kLog_debug, "Got authentication challenge - sending MD5 response\n");
  130. generate_response_md5(&challenge->plain, &challenge->digest);
  131. }
  132. memcpy(cur->buf, challenge, sizeof(challenge_t));
  133. queue_packet(icmp_sock, cur, cur->buf, sizeof(challenge_t), 0, 0, kProto_authenticate | cur->type_flag);
  134. /* We have authenticated locally.
  135. * It's up to the proxy now if it accepts our response or not..
  136. */
  137. cur->authenticated = 1;
  138. handle_data(pkt, bytes, cur);
  139. }
  140. static void handle_auth_response(unsigned bytes,
  141. int icmp_sock,
  142. icmp_echo_packet_t * const pkt,
  143. proxy_desc_t * const cur,
  144. challenge_t * const challenge)
  145. {
  146. pt_log(kLog_debug,
  147. "Received remote %s challenge response.\n",
  148. (challenge->digest.hash_type == HT_SHA512 ? "SHA512" : "MD5"));
  149. if ((!opts.force_sha512 && challenge->digest.hash_type == HT_MD5 &&
  150. validate_challenge_md5(cur->challenge, &challenge->digest)) ||
  151. #ifdef ENABLE_SHA512
  152. (challenge->digest.hash_type == HT_SHA512 && validate_challenge_sha512(cur->challenge, &challenge->digest)) ||
  153. #endif
  154. cur->authenticated) {
  155. pt_log(kLog_verbose, "Remote end authenticated successfully.\n");
  156. /* Authentication has succeeded, so now we can proceed
  157. * to handle incoming TCP data.
  158. */
  159. cur->authenticated = 1;
  160. cur->state = kProto_data;
  161. /* Insert the packet into the receive ring, to avoid
  162. * confusing the reliab ility mechanism.
  163. */
  164. handle_data(pkt, bytes, cur);
  165. } else {
  166. pt_log(kLog_info, "Remote end failed authentication.\n");
  167. send_termination_msg(cur, icmp_sock);
  168. cur->should_remove = 1;
  169. }
  170. }
  171. static void header_byteorder_ntoh(icmp_echo_packet_t * const icmp_pkt, ping_tunnel_pkt_t * const pt_pkt)
  172. {
  173. pt_pkt->state = ntohl(pt_pkt->state);
  174. icmp_pkt->identifier = ntohs(icmp_pkt->identifier);
  175. icmp_pkt->seq = ntohs(icmp_pkt->seq);
  176. pt_pkt->id_no = ntohs(pt_pkt->id_no);
  177. pt_pkt->seq_no = ntohs(pt_pkt->seq_no);
  178. }
  179. static proxy_desc_t * get_proxy_descriptor(uint16_t id_no)
  180. {
  181. proxy_desc_t * cur;
  182. /* Find the relevant connection, if it exists */
  183. pthread_mutex_lock(&chain_lock);
  184. for (cur = chain; cur; cur = cur->next) {
  185. if (cur->id_no == id_no) {
  186. break;
  187. }
  188. }
  189. pthread_mutex_unlock(&chain_lock);
  190. return cur;
  191. }
  192. /* handle_proxy_packet:
  193. * Processes incoming ICMP packets for the proxy. The packet can come either from the
  194. * packet capture lib, or from the actual socket or both.
  195. * Input: A buffer pointing at the start of an IP header, the buffer length and the proxy
  196. * descriptor chain.
  197. */
  198. void handle_packet(char * buf, unsigned bytes, int is_pcap, struct sockaddr_in * addr, int icmp_sock)
  199. {
  200. ip_packet_t * ip_pkt = NULL;
  201. icmp_echo_packet_t * pkt;
  202. ping_tunnel_pkt_t * pt_pkt;
  203. proxy_desc_t * cur;
  204. int pkt_flag;
  205. enum pkt_flag type_flag, proxy_flag;
  206. challenge_t * challenge;
  207. proxy_flag = kProxy_flag;
  208. if (bytes < sizeof(icmp_echo_packet_t) + sizeof(ping_tunnel_pkt_t)) {
  209. pt_log(kLog_verbose,
  210. "Skipping this packet - too short. "
  211. "Expect: %lu+%lu = %lu ; Got: %u\n",
  212. sizeof(icmp_echo_packet_t),
  213. sizeof(ping_tunnel_pkt_t),
  214. sizeof(icmp_echo_packet_t) + sizeof(ping_tunnel_pkt_t),
  215. bytes);
  216. return;
  217. }
  218. if (opts.udp || opts.unprivileged) {
  219. pkt = (icmp_echo_packet_t *)buf;
  220. pt_pkt = (ping_tunnel_pkt_t *)pkt->data;
  221. } else {
  222. ip_pkt = (ip_packet_t *)buf;
  223. pkt = (icmp_echo_packet_t *)ip_pkt->data;
  224. pt_pkt = (ping_tunnel_pkt_t *)pkt->data;
  225. }
  226. if (ntohl(pt_pkt->magic) != opts.magic) {
  227. pt_log(kLog_verbose, "Ignored incoming packet. Magic value 0x%X mismatch.\n", ntohl(pt_pkt->magic));
  228. return;
  229. }
  230. header_byteorder_ntoh(pkt, pt_pkt);
  231. cur = get_proxy_descriptor(pt_pkt->id_no);
  232. /* Handle the packet if it comes from "the other end." This is a bit tricky
  233. * to get right, since we receive both our own and the other end's packets.
  234. * Basically, a proxy will accept any packet from a user, regardless if it
  235. * has a valid connection or not. A user will only accept the packet if there
  236. * exists a connection to handle it.
  237. */
  238. if (cur) {
  239. type_flag = cur->type_flag;
  240. if (type_flag == kProxy_flag) {
  241. cur->icmp_id = pkt->identifier;
  242. cur->ping_seq = pkt->seq;
  243. }
  244. if (!is_pcap)
  245. cur->xfer.icmp_in++;
  246. } else {
  247. type_flag = kProxy_flag;
  248. }
  249. pkt_flag = (int)pt_pkt->state & kFlag_mask;
  250. pt_pkt->state &= ~kFlag_mask;
  251. if (pt_pkt->state > (kNum_proto_types - 1)) {
  252. pt_log(kLog_error, "Dropping packet with invalid state.\n");
  253. return;
  254. }
  255. pt_log(kLog_sendrecv,
  256. "Recv: %4d [%4d] bytes "
  257. "[id = 0x%04X] [seq = %d] "
  258. "[seq_no = %d] [type = %s] "
  259. "[ack = %d] [icmp = %d] "
  260. "[user = %s] [pcap = %d]\n",
  261. bytes,
  262. ntohl(pt_pkt->data_len),
  263. pkt->identifier,
  264. ntohs(pkt->seq),
  265. pt_pkt->seq_no,
  266. state_name[pt_pkt->state & (~kFlag_mask)],
  267. ntohl(pt_pkt->ack),
  268. pkt->type,
  269. (pkt_flag == kUser_flag ? "yes" : "no"),
  270. is_pcap);
  271. log_sendrecv_hexstr("RECV ICMP", pkt, sizeof(*pkt));
  272. log_sendrecv_hexstr("RECV PTNG", pt_pkt, sizeof(*pt_pkt));
  273. if (bytes - (pt_pkt->data - buf) > 0) {
  274. log_sendrecv_hexstr("RECV PAYL", pt_pkt->data, bytes - (pt_pkt->data - buf));
  275. }
  276. /* This test essentially verifies that the packet comes from someone who isn't us. */
  277. if ((pkt_flag == kUser_flag && type_flag == proxy_flag) || (pkt_flag == proxy_flag && type_flag == kUser_flag)) {
  278. pt_pkt->data_len = ntohl(pt_pkt->data_len);
  279. pt_pkt->ack = ntohl(pt_pkt->ack);
  280. if (pt_pkt->state == kProxy_start) {
  281. if (!cur && type_flag == proxy_flag) {
  282. cur = handle_incoming_tunnel_request(bytes, addr, icmp_sock, pkt, pt_pkt);
  283. if (!cur) {
  284. return;
  285. }
  286. } else if (type_flag == kUser_flag) {
  287. pt_log(kLog_error, "Dropping proxy session request - we are not a proxy!\n");
  288. return;
  289. } else {
  290. pt_log(kLog_error,
  291. "Dropping duplicate proxy session request "
  292. "with ID %d and seq %d.\n",
  293. pt_pkt->id_no,
  294. pt_pkt->seq_no);
  295. }
  296. } else if (cur && pt_pkt->state == kProto_authenticate) {
  297. /* Sanity check packet length, and make sure it matches what we expect */
  298. if (pt_pkt->data_len != sizeof(challenge_t)) {
  299. pt_log(kLog_error,
  300. "Received challenge packet, but data length "
  301. "is not as expected.\n");
  302. pt_log(kLog_debug, "Data length: %u Expected: %lu\n", pt_pkt->data_len, sizeof(challenge_t));
  303. cur->should_remove = 1;
  304. return;
  305. }
  306. /* Prevent packet data from being forwarded over TCP! */
  307. pt_pkt->data_len = 0;
  308. challenge = (challenge_t *)pt_pkt->data;
  309. /* If client: Compute response to challenge */
  310. if (type_flag == kUser_flag) {
  311. /* Required for integration tests w/ passwd set. */
  312. pt_log(kLog_debug, "AUTH-REQUEST: Received ack-series starting at seq %d\n", pt_pkt->seq_no);
  313. handle_auth_request(bytes, icmp_sock, pkt, cur, challenge);
  314. return;
  315. }
  316. /* If proxy: Handle client's response to challenge */
  317. else if (type_flag == proxy_flag) {
  318. cur->next_remote_seq++;
  319. handle_auth_response(bytes, icmp_sock, pkt, cur, challenge);
  320. return;
  321. }
  322. }
  323. /* Handle close-messages for connections we know about */
  324. if (cur && pt_pkt->state == kProto_close) {
  325. pt_log(kLog_info, "Received session close from remote peer.\n");
  326. cur->should_remove = 1;
  327. return;
  328. }
  329. /* The proxy will ignore any other packets from the client
  330. * until it has been authenticated. The packet resend mechanism
  331. * insures that this isn't problematic.
  332. */
  333. if (type_flag == proxy_flag && opts.password && cur && !cur->authenticated) {
  334. pt_log(kLog_debug,
  335. "Ignoring packet with seq-no %d "
  336. "- not authenticated yet.\n",
  337. pt_pkt->seq_no);
  338. return;
  339. }
  340. if (cur && cur->sock) {
  341. double now = time_as_double();
  342. if (pt_pkt->state != kProto_ack) {
  343. cur->last_data_activity = now;
  344. }
  345. if (pt_pkt->state == kProto_data || pt_pkt->state == kProxy_start || pt_pkt->state == kProto_ack) {
  346. if (pt_pkt->state == kProxy_start) {
  347. pt_pkt->data_len = 0;
  348. }
  349. handle_data(pkt, bytes, cur);
  350. }
  351. handle_ack(pt_pkt->ack, cur);
  352. cur->last_activity = now;
  353. }
  354. }
  355. }
  356. static void queue_payload_data(ping_tunnel_pkt_t * const pt_pkt, proxy_desc_t * const cur)
  357. {
  358. /* Check if we should add payload data to the queue. */
  359. if (!cur->recv_ring[cur->recv_idx] && pt_pkt->state == kProto_data) {
  360. pt_log(kLog_debug, "Queing data packet: %d\n", pt_pkt->seq_no);
  361. cur->recv_ring[cur->recv_idx] = create_fwd_desc(pt_pkt->seq_no, pt_pkt->data_len, pt_pkt->data);
  362. cur->recv_wait_send++;
  363. cur->recv_idx++;
  364. } else {
  365. pt_log(kLog_debug, "Dup packet for %d ?\n", pt_pkt->seq_no);
  366. }
  367. cur->next_remote_seq++;
  368. if (cur->recv_idx >= cur->window_size) {
  369. cur->recv_idx = 0;
  370. }
  371. /* Check if we have already received some of the next packets. */
  372. while (cur->recv_ring[cur->recv_idx]) {
  373. if (cur->recv_ring[cur->recv_idx]->seq_no == cur->next_remote_seq) {
  374. cur->next_remote_seq++;
  375. cur->recv_idx++;
  376. if (cur->recv_idx >= cur->window_size) {
  377. cur->recv_idx = 0;
  378. }
  379. } else {
  380. break;
  381. }
  382. }
  383. }
  384. static void queue_payload_data_out_of_order(ping_tunnel_pkt_t * const pt_pkt, proxy_desc_t * const cur)
  385. {
  386. int r, s, d, pos;
  387. pos = -1; /* If pos ends up staying -1, packet is discarded. */
  388. r = cur->next_remote_seq;
  389. s = pt_pkt->seq_no;
  390. d = s - r;
  391. if (d < 0) { /* This packet _may_ be old, or seq_no may have wrapped around */
  392. d = (s + 0xFFFF) - r;
  393. if (cur->window_size && d < cur->window_size) {
  394. /* Counter has wrapped, so we should add this packet to the recv ring */
  395. pos = (cur->recv_idx + d) % cur->window_size;
  396. }
  397. } else if (cur->window_size && d < cur->window_size) {
  398. pos = (cur->recv_idx + d) % cur->window_size;
  399. }
  400. if (pos != -1) {
  401. if (!cur->recv_ring[pos]) {
  402. pt_log(kLog_verbose,
  403. "Out of order. Expected: %d Got: %d Inserted: %d "
  404. "(cur = %d)\n",
  405. cur->next_remote_seq,
  406. pt_pkt->seq_no,
  407. pos,
  408. cur->recv_idx);
  409. cur->recv_ring[pos] = create_fwd_desc(pt_pkt->seq_no, pt_pkt->data_len, pt_pkt->data);
  410. cur->recv_wait_send++;
  411. }
  412. } else {
  413. pt_log(kLog_info, "Packet discarded - outside receive window.\n");
  414. }
  415. }
  416. /* handle_data:
  417. * Utility function for handling kProto_data packets, and place the data it contains
  418. * onto the passed-in receive ring.
  419. */
  420. void handle_data(icmp_echo_packet_t * pkt, int total_len, proxy_desc_t * cur)
  421. {
  422. ping_tunnel_pkt_t * pt_pkt = (ping_tunnel_pkt_t *)pkt->data;
  423. int expected_len = sizeof(ip_packet_t) + sizeof(icmp_echo_packet_t) + sizeof(ping_tunnel_pkt_t); /* 20+8+28 */
  424. /* Place packet in the receive ring, in its proper place.
  425. * This works as follows:
  426. * -1. Packet == ack packet? Perform ack, and continue.
  427. * 0. seq_no < next_remote_seq, and absolute difference is bigger than w size => discard
  428. * 1. If seq_no == next_remote_seq, we have no problems; just put it in the ring.
  429. * 2. If seq_no > next_remote_seq + remaining window size, discard packet.
  430. * Send resend request for missing packets.
  431. * 3. Else, put packet in the proper place in the ring
  432. * (don't overwrite if one is already there), but don't increment next_remote_seq_no
  433. * 4. If packed was not discarded, process ack info in packet.
  434. */
  435. expected_len += pt_pkt->data_len;
  436. expected_len += expected_len % 2;
  437. if (opts.udp || opts.unprivileged) {
  438. expected_len -= sizeof(ip_packet_t);
  439. }
  440. if (total_len < expected_len) {
  441. pt_log(kLog_error, "Packet not completely received: %d Should be: %d.\n", total_len, expected_len);
  442. pt_log(kLog_debug, "Data length: %d Total length: %d\n", pt_pkt->data_len, total_len);
  443. /* just ignore that packet */
  444. return;
  445. }
  446. if (pt_pkt->seq_no == cur->next_remote_seq) {
  447. queue_payload_data(pt_pkt, cur);
  448. } else {
  449. queue_payload_data_out_of_order(pt_pkt, cur);
  450. }
  451. }
  452. void handle_ack(uint32_t seq_no, proxy_desc_t * cur)
  453. {
  454. if (cur->send_wait_ack > 0) {
  455. int i, can_ack = 0, count = 0;
  456. i = cur->send_idx - 1;
  457. if (i < 0) {
  458. i = cur->window_size - 1;
  459. }
  460. pt_log(kLog_debug, "Received ack-series starting at seq %d\n", seq_no);
  461. while (count < cur->window_size) {
  462. if (!cur->send_ring[i].pkt) {
  463. break;
  464. }
  465. if (cur->send_ring[i].seq_no == seq_no) {
  466. can_ack = 1;
  467. } else if (!can_ack) {
  468. cur->send_first_ack = i;
  469. }
  470. if (can_ack) {
  471. free(cur->send_ring[i].pkt);
  472. cur->send_ring[i].pkt = 0;
  473. cur->send_ring[i].pkt_len = 0;
  474. cur->send_wait_ack--;
  475. }
  476. i--;
  477. if (i < 0) {
  478. i = cur->window_size - 1;
  479. }
  480. count++;
  481. }
  482. } else {
  483. pt_log(kLog_verbose,
  484. "Dropping superfluous acknowledgement for seq %d "
  485. "(no outstanding packets needing ack.)\n",
  486. seq_no);
  487. }
  488. }