tap2pcap.py 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. """Tool to convert Envoy tap trace format to PCAP.
  2. Uses od and text2pcap (part of Wireshark) utilities to translate the Envoy
  3. tap trace proto format to a PCAP file suitable for consuming in Wireshark
  4. and other tools in the PCAP ecosystem. The TCP stream in the output PCAP is
  5. synthesized based on the known IP/port/timestamps that Envoy produces in its
  6. tap files; it is not a literal wire tap.
  7. Usage:
  8. bazel run @envoy_api//tools:tap2pcap <tap .pb/.pb_text> <pcap path>
  9. Known issues:
  10. - IPv6 PCAP generation has malformed TCP packets. This appears to be a text2pcap
  11. issue.
  12. TODO(htuch):
  13. - Figure out IPv6 PCAP issue above, or file a bug once the root cause is clear.
  14. """
  15. from __future__ import print_function
  16. import datetime
  17. import io
  18. import socket
  19. import subprocess as sp
  20. import sys
  21. import time
  22. from google.protobuf import text_format
  23. from envoy.data.tap.v2alpha import wrapper_pb2
  24. def dump_event(direction, timestamp, data):
  25. dump = io.StringIO()
  26. dump.write('%s\n' % direction)
  27. # Adjust to local timezone
  28. adjusted_dt = timestamp.ToDatetime() - datetime.timedelta(seconds=time.altzone)
  29. dump.write('%s\n' % adjusted_dt)
  30. od = sp.Popen(['od', '-Ax', '-tx1', '-v'], stdout=sp.PIPE, stdin=sp.PIPE, stderr=sp.PIPE)
  31. packet_dump = od.communicate(data)[0]
  32. dump.write(packet_dump.decode())
  33. return dump.getvalue()
  34. def tap2pcap(tap_path, pcap_path):
  35. wrapper = wrapper_pb2.TraceWrapper()
  36. if tap_path.endswith('.pb_text'):
  37. with open(tap_path, 'r') as f:
  38. text_format.Merge(f.read(), wrapper)
  39. else:
  40. with open(tap_path, 'r') as f:
  41. wrapper.ParseFromString(f.read())
  42. trace = wrapper.socket_buffered_trace
  43. local_address = trace.connection.local_address.socket_address.address
  44. local_port = trace.connection.local_address.socket_address.port_value
  45. remote_address = trace.connection.remote_address.socket_address.address
  46. remote_port = trace.connection.remote_address.socket_address.port_value
  47. dumps = []
  48. for event in trace.events:
  49. if event.HasField('read'):
  50. dumps.append(dump_event('I', event.timestamp, event.read.data.as_bytes))
  51. elif event.HasField('write'):
  52. dumps.append(dump_event('O', event.timestamp, event.write.data.as_bytes))
  53. ipv6 = False
  54. try:
  55. socket.inet_pton(socket.AF_INET6, local_address)
  56. ipv6 = True
  57. except socket.error:
  58. pass
  59. text2pcap_args = [
  60. 'text2pcap', '-D', '-t', '%Y-%m-%d %H:%M:%S.', '-6' if ipv6 else '-4',
  61. '%s,%s' % (remote_address, local_address), '-T',
  62. '%d,%d' % (remote_port, local_port), '-', pcap_path
  63. ]
  64. text2pcap = sp.Popen(text2pcap_args, stdout=sp.PIPE, stdin=sp.PIPE)
  65. text2pcap.communicate('\n'.join(dumps).encode())
  66. if __name__ == '__main__':
  67. if len(sys.argv) != 3:
  68. print('Usage: %s <tap .pb/.pb_text> <pcap path>' % sys.argv[0])
  69. sys.exit(1)
  70. tap2pcap(sys.argv[1], sys.argv[2])