redhound 0.2.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -3
  3. data/Dockerfile +1 -1
  4. data/README.md +2 -2
  5. data/Rakefile +12 -1
  6. data/Steepfile +4 -0
  7. data/lib/redhound/analyzer.rb +19 -15
  8. data/lib/redhound/builder/packet_mreq.rb +56 -0
  9. data/lib/redhound/builder/socket.rb +35 -0
  10. data/lib/redhound/builder.rb +4 -0
  11. data/lib/redhound/command.rb +6 -1
  12. data/lib/redhound/l2/ether.rb +68 -0
  13. data/lib/redhound/l2/protocol.rb +33 -0
  14. data/lib/redhound/l2.rb +4 -0
  15. data/lib/redhound/l3/arp.rb +114 -0
  16. data/lib/redhound/l3/base.rb +49 -0
  17. data/lib/redhound/{header → l3}/ipv4.rb +27 -44
  18. data/lib/redhound/l3/ipv6.rb +69 -0
  19. data/lib/redhound/l3/protocol.rb +173 -0
  20. data/lib/redhound/l3/resolver.rb +30 -0
  21. data/lib/redhound/l3.rb +9 -0
  22. data/lib/redhound/l4/base.rb +31 -0
  23. data/lib/redhound/{header → l4}/icmp.rb +20 -26
  24. data/lib/redhound/l4/resolver.rb +28 -0
  25. data/lib/redhound/{header → l4}/udp.rb +19 -19
  26. data/lib/redhound/l4.rb +6 -0
  27. data/lib/redhound/receiver.rb +17 -5
  28. data/lib/redhound/resolver.rb +22 -0
  29. data/lib/redhound/source/socket.rb +18 -0
  30. data/lib/redhound/source.rb +3 -0
  31. data/lib/redhound/version.rb +2 -1
  32. data/lib/redhound/writer.rb +9 -2
  33. data/lib/redhound.rb +6 -3
  34. data/rbs_collection.lock.yaml +20 -0
  35. data/rbs_collection.yaml +17 -0
  36. data/sig/generated/redhound/analyzer.rbs +14 -0
  37. data/sig/generated/redhound/builder/packet_mreq.rbs +39 -0
  38. data/sig/generated/redhound/builder/socket.rbs +24 -0
  39. data/sig/generated/redhound/command.rbs +19 -0
  40. data/sig/generated/redhound/l2/ether.rbs +41 -0
  41. data/sig/generated/redhound/l2/protocol.rbs +15 -0
  42. data/sig/generated/redhound/l3/arp.rbs +57 -0
  43. data/sig/generated/redhound/l3/base.rbs +28 -0
  44. data/sig/generated/redhound/l3/ipv4.rbs +53 -0
  45. data/sig/generated/redhound/l3/ipv6.rbs +38 -0
  46. data/sig/generated/redhound/l3/protocol.rbs +16 -0
  47. data/sig/generated/redhound/l3/resolver.rbs +16 -0
  48. data/sig/generated/redhound/l4/base.rbs +19 -0
  49. data/sig/generated/redhound/l4/icmp.rbs +33 -0
  50. data/sig/generated/redhound/l4/resolver.rbs +16 -0
  51. data/sig/generated/redhound/l4/udp.rbs +36 -0
  52. data/sig/generated/redhound/receiver.rbs +19 -0
  53. data/sig/generated/redhound/resolver.rbs +11 -0
  54. data/sig/generated/redhound/source/socket.rbs +13 -0
  55. data/sig/generated/redhound/version.rbs +5 -0
  56. data/sig/generated/redhound/writer.rbs +25 -0
  57. metadata +48 -11
  58. data/lib/redhound/header/ether.rb +0 -67
  59. data/lib/redhound/header.rb +0 -6
  60. data/lib/redhound/packet_mreq.rb +0 -46
  61. data/lib/redhound/socket_builder.rb +0 -30
  62. data/sig/redhound.rbs +0 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1f9c914cea67f4e0f32f6dcf84149039eb95f1f2311bd6f410641b67b81d441f
4
- data.tar.gz: ac1012da2b103d27bb43d98ca273567efc44fa4995de92af315370f73a899dc8
3
+ metadata.gz: 7fc9146df6d249b2bdd3ca7c529c6d72eaad8c78970e6cc21d4be41f9e233a25
4
+ data.tar.gz: 9f66932249c4235042d3934579fdf7f9be92ad531f30e1e5c05ed1a55ae436e4
5
5
  SHA512:
6
- metadata.gz: 4c26cd6fea83dcbd1f60e2615a8070f60ccb036f506f9a46c77d0f935a57027ddf19d7f408271dccc8a31cc3d9624b6e6db62b252bd9b6393de858819a2855c9
7
- data.tar.gz: 4ce4cf820846e2e652bfa33a16d08ca942eb1739d0f29a04a18389fb6d992b9ab4b13504de5d5684ab67f753bc02cc94a1c9cd702dcc922b95b2de5a7a9983a8
6
+ metadata.gz: 4ee95bd38fa1717c5286ae994db3b4d2c024c07119489d65f7b48a2cd9f5337b1a0cf0ebf36659f37d113052aaee178cd8ee29d50c2d995bee06d97443335fc6
7
+ data.tar.gz: 895d8f8b92858aaf63ac5bfc9b7ee2dc4a4fb594313e20052a96bdf893380aaae4853988c8e2966657ea3f2788eabf604a3ef44ebb16cf9954f0df5c4aeef444
data/CHANGELOG.md CHANGED
@@ -1,9 +1,22 @@
1
- ## [Unreleased]
1
+ # Changelog
2
2
 
3
- ## [0.2.0] - 2025-01-03
3
+ ## Unreleased
4
+
5
+ ## 1.0.1 - 2025-01-17
6
+
7
+ - Fix an NameError in Redhound::L3::Arp
8
+
9
+ ## 1.0.0 - 2025-01-16
10
+
11
+ - Add ARP header support.
12
+ - Add IPv6 header support.
13
+ - Improve formatting of packet output.
14
+ - Remove debug print statement from IPv4 header parsing.
15
+
16
+ ## 0.2.0 - 2025-01-03
4
17
 
5
18
  - Add option to write packets to file as PCAP Capture File Format.
6
19
 
7
- ## [0.1.0] - 2024-11-05
20
+ ## 0.1.0 - 2024-11-05
8
21
 
9
22
  - Initial release
data/Dockerfile CHANGED
@@ -1,5 +1,5 @@
1
1
  FROM ruby:3.3.4
2
- RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs
2
+ RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs iputils-ping iproute2 iptables
3
3
  WORKDIR /app
4
4
  COPY Gemfile /app/Gemfile
5
5
  RUN bundle install
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Redhound
1
+ # Redhound [![Gem Version](https://badge.fury.io/rb/redhound.svg)](https://badge.fury.io/rb/redhound) [![Test](https://github.com/ydah/redhound/actions/workflows/main.yml/badge.svg)](https://github.com/ydah/redhound/actions/workflows/main.yml)
2
2
 
3
3
  Pure Ruby packet analyzer.
4
4
  At this time, it is only guaranteed to work on Linux.
@@ -25,7 +25,7 @@ gem install redhound
25
25
  / , _/ -_) _ / _ \/ _ \/ // / _ \/ _ /
26
26
  /_/|_|\__/\_,_/_//_/\___/\_,_/_//_/\_,_/
27
27
 
28
- Version: 0.1.0
28
+ Version: 1.0.1
29
29
  Dump and analyze network packets.
30
30
 
31
31
  Usage: redhound [options] ...
data/Rakefile CHANGED
@@ -1,4 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'bundler/gem_tasks'
4
- task default: %i[]
4
+
5
+ desc "steep check"
6
+ task :steep do
7
+ sh "bundle exec steep check"
8
+ end
9
+
10
+ desc "Run rbs-inline"
11
+ task :rbs_inline do
12
+ sh "bundle exec rbs-inline --output lib/"
13
+ end
14
+
15
+ task default: %i[rbs_inline steep]
data/Steepfile ADDED
@@ -0,0 +1,4 @@
1
+ target :lib do
2
+ signature "sig"
3
+ check "lib"
4
+ end
@@ -1,30 +1,34 @@
1
+ # rbs_inline: enabled
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module Redhound
4
5
  class Analyzer
5
- def self.analyze(msg:)
6
- new(msg: msg).analyze
6
+ # @rbs (msg: String, count: Integer) -> void
7
+ def self.analyze(msg:, count:)
8
+ new(msg:, count:).analyze
7
9
  end
8
10
 
9
- def initialize(msg:)
11
+ # @rbs (msg: String, count: Integer) -> void
12
+ def initialize(msg:, count:)
10
13
  @msg = msg
14
+ @count = count
11
15
  end
12
16
 
17
+ # @rbs () -> void
13
18
  def analyze
14
- puts 'Analyzing...'
15
- ether = Header::Ether.generate(bytes: @msg.bytes[0..13])
16
- ether.dump
17
- return unless ether.ipv4?
19
+ l2 = L2::Ether.generate(bytes: @msg.bytes[0..], count: @count)
20
+ l2.dump
21
+ return unless l2.supported_type?
18
22
 
19
- ip = Header::Ipv4.generate(bytes: @msg.bytes[14..33])
20
- ip.dump
21
- if ip.udp?
22
- udp = Header::Udp.generate(bytes: @msg.bytes[34..41])
23
- udp.dump
24
- elsif ip.icmp?
25
- icmp = Header::Icmp.generate(bytes: @msg.bytes[34..41])
26
- icmp.dump
23
+ l3 = L3::Resolver.resolve(bytes: @msg.bytes[l2.size..], l2:)
24
+ return if !l3 || @msg.bytes.size <= l2.size + l3.size
25
+ l3.dump
26
+ unless l3.supported_protocol?
27
+ puts " └─ Unsupported protocol #{l3.protocol}"
28
+ return
27
29
  end
30
+
31
+ L4::Resolver.resolve(bytes: @msg.bytes[(l2.size + l3.size)..], l3:)&.dump
28
32
  end
29
33
  end
30
34
  end
@@ -0,0 +1,56 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ require 'socket'
5
+
6
+ module Redhound
7
+ module Builder
8
+ class PacketMreq
9
+ PACKET_MR_PROMISC = 0x0001 # NOTE: netpacket/packet.h
10
+
11
+ # @rbs (ifname: String) -> void
12
+ def initialize(ifname:)
13
+ @ifname = ifname
14
+ end
15
+
16
+ # see: https://man7.org/linux/man-pages/man7/packet.7.html
17
+ # struct packet_mreq {
18
+ # int mr_ifindex; /* interface index */
19
+ # unsigned short mr_type; /* action */
20
+ # unsigned short mr_alen; /* address length */
21
+ # unsigned char mr_address[8]; /* physical-layer address */
22
+ # };
23
+ # @rbs () -> String
24
+ def build
25
+ mr_ifindex + mr_type + mr_alen + mr_address
26
+ end
27
+
28
+ # @rbs () -> String
29
+ def mr_ifindex
30
+ @mr_ifindex ||= [[index].pack('I')].pack('a4')
31
+ end
32
+
33
+ private
34
+
35
+ # @rbs () -> Integer?
36
+ def index
37
+ ::Socket.getifaddrs.find { |ifaddr| ifaddr.name == @ifname }&.ifindex
38
+ end
39
+
40
+ # @rbs () -> String
41
+ def mr_type
42
+ @mr_type ||= [PACKET_MR_PROMISC].pack('S')
43
+ end
44
+
45
+ # @rbs () -> String
46
+ def mr_alen
47
+ @mr_alen ||= [0].pack('S')
48
+ end
49
+
50
+ # @rbs () -> String
51
+ def mr_address
52
+ @mr_address ||= [0].pack('C') * 8
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,35 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ require 'socket'
5
+
6
+ module Redhound
7
+ module Builder
8
+ class Socket
9
+ SOL_PACKET = 0x0107 # bits/socket.h
10
+ PACKET_ADD_MEMBERSHIP = 0x0001 # NOTE: netpacket/packet.h
11
+ ETH_P_ALL = 768 # NOTE: htons(ETH_P_ALL) => linux/if_ether.h
12
+ PACKED_ETH_P_ALL = [ETH_P_ALL].pack('S').unpack1('S>')
13
+
14
+ class << self
15
+ # @rbs (ifname: String) -> Redhound::Source::Socket
16
+ def build(ifname:)
17
+ new(ifname:).build
18
+ end
19
+ end
20
+
21
+ # @rbs (ifname: String) -> void
22
+ def initialize(ifname:)
23
+ @mq_req = PacketMreq.new(ifname:)
24
+ end
25
+
26
+ # @rbs () -> Redhound::Source::Socket
27
+ def build
28
+ socket = ::Socket.new(::Socket::AF_PACKET, ::Socket::SOCK_RAW, ETH_P_ALL)
29
+ socket.bind([::Socket::AF_PACKET, PACKED_ETH_P_ALL, @mq_req.mr_ifindex].pack('SS>a16'))
30
+ socket.setsockopt(SOL_PACKET, PACKET_ADD_MEMBERSHIP, @mq_req.build)
31
+ Redhound::Source::Socket.new(socket:)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'builder/packet_mreq'
4
+ require_relative 'builder/socket'
@@ -1,3 +1,4 @@
1
+ # rbs_inline: enabled
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require 'optparse'
@@ -5,10 +6,12 @@ require 'socket'
5
6
 
6
7
  module Redhound
7
8
  class Command
9
+ # @rbs () -> void
8
10
  def initialize
9
11
  @options = { ifname: nil }
10
12
  end
11
13
 
14
+ # @rbs (Array[untyped] argv) -> void
12
15
  def run(argv)
13
16
  parse(argv)
14
17
  if @options[:ifname].nil?
@@ -18,8 +21,9 @@ module Redhound
18
21
  Receiver.run(ifname: @options[:ifname], filename: @options[:filename])
19
22
  end
20
23
 
24
+ # @rbs (Array[untyped] argv) -> void
21
25
  def parse(argv)
22
- OptionParser.new do |o|
26
+ OptionParser.new do |o| # steep:ignore
23
27
  o.banner = <<~'BANNER' + <<~BANNER2
24
28
  ___ ____ __
25
29
  / _ \___ ___/ / / ___ __ _____ ___/ /
@@ -55,6 +59,7 @@ module Redhound
55
59
 
56
60
  private
57
61
 
62
+ # @rbs () -> void
58
63
  def list_interfaces
59
64
  ::Socket.getifaddrs.each { |ifaddr| puts ifaddr.name }
60
65
  end
@@ -0,0 +1,68 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ module Redhound
5
+ class L2
6
+ class Ether
7
+ attr_reader :type #: Protocol
8
+
9
+ class << self
10
+ # @rbs (bytes: Array[Integer], count: Integer) -> Redhound::L2::Ether
11
+ def generate(bytes:, count:)
12
+ new(bytes:, count:).generate
13
+ end
14
+ end
15
+
16
+ # @rbs (bytes: Array[Integer], count: Integer) -> void
17
+ def initialize(bytes:, count:)
18
+ raise ArgumentError, "bytes must be #{size} bytes" unless bytes.size >= size
19
+
20
+ @bytes = bytes
21
+ @count = count
22
+ end
23
+
24
+ # @rbs () -> Integer
25
+ def size = 14
26
+
27
+ # @rbs () -> Redhound::L2::Ether
28
+ def generate
29
+ @dhost = @bytes[0..5]
30
+ @shost = @bytes[6..11]
31
+ @type = Protocol.new(protocol: hex_type(@bytes[12..13]))
32
+ self
33
+ end
34
+
35
+ # @rbs () -> void
36
+ def dump
37
+ puts self
38
+ end
39
+
40
+ # @rbs () -> String
41
+ def to_s
42
+ "[#{@count}] Ethernet Dst: #{dhost} Src: #{shost} Type: #{@type}"
43
+ end
44
+
45
+ # @rbs () -> bool
46
+ def supported_type?
47
+ @type.ipv4? || @type.ipv6? || @type.arp? # steep:ignore
48
+ end
49
+
50
+ private
51
+
52
+ # @rbs () -> String
53
+ def dhost
54
+ @dhost.map { |b| b.to_s(16).rjust(2, '0') }.join(':')
55
+ end
56
+
57
+ # @rbs () -> String
58
+ def shost
59
+ @shost.map { |b| b.to_s(16).rjust(2, '0') }.join(':')
60
+ end
61
+
62
+ # @rbs (Array[Integer] type) -> Integer
63
+ def hex_type(type)
64
+ type.map { |b| b.to_s(16).rjust(2, '0') }.join.to_i(16)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,33 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ module Redhound
5
+ class L2
6
+ class Protocol
7
+ PROTO_TABLE = {
8
+ 0x0060 => 'Loopback',
9
+ 0x0800 => 'IPv4',
10
+ 0x0806 => 'ARP',
11
+ 0x86DD => 'IPv6',
12
+ 0x8100 => 'VLAN',
13
+ }
14
+
15
+ # @rbs (protocol: Integer) -> void
16
+ def initialize(protocol:)
17
+ @protocol = protocol
18
+ end
19
+
20
+ # @rbs () -> String
21
+ def to_s
22
+ PROTO_TABLE[@protocol] || 'Unknown'
23
+ end
24
+
25
+ PROTO_TABLE.each do |id, name|
26
+ method_name = name.downcase.gsub(/[ \-]/, '_') + '?'
27
+ define_method(method_name) do
28
+ @protocol == id
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'l2/ether'
4
+ require_relative 'l2/protocol'
@@ -0,0 +1,114 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ module Redhound
5
+ class L3
6
+ class Arp < Base
7
+ class << self
8
+ # @rbs (bytes: Array[Integer]) -> Redhound::L3::Arp
9
+ def generate(bytes:)
10
+ new(bytes:).generate
11
+ end
12
+ end
13
+
14
+ # @rbs (bytes: Array[Integer]) -> void
15
+ def initialize(bytes:)
16
+ raise ArgumentError, "bytes must be bigger than #{arp_size} bytes" unless bytes.size >= arp_size
17
+
18
+ @bytes = bytes
19
+ end
20
+
21
+ # @rbs () -> Redhound::L3::Arp
22
+ def generate
23
+ @htype = @bytes[0..1]
24
+ @ptype = @bytes[2..3]
25
+ @hlen = @bytes[4]
26
+ @plen = @bytes[5]
27
+ @oper = @bytes[6..7]
28
+ @sha = @bytes[8..13]
29
+ @spa = @bytes[14..17]
30
+ @tha = @bytes[18..23]
31
+ @tpa = @bytes[24..27]
32
+ @type = Redhound::L2::Protocol.new(protocol: ptype)
33
+ @l3 = generate_l3
34
+ self
35
+ end
36
+
37
+ # @rbs () -> Integer
38
+ def arp_size = 28
39
+
40
+ # @rbs () -> Integer
41
+ def size
42
+ if @l3.nil?
43
+ arp_size
44
+ else
45
+ arp_size + @l3.size
46
+ end
47
+ end
48
+
49
+ # @rbs () -> String
50
+ def to_s
51
+ " └─ ARP HType: #{htype} PType: #{ptype} HLen: #{@hlen} PLen: #{@plen} Oper: #{oper} SHA: #{sha} SPA: #{spa} THA: #{tha} TPA: #{tpa}"
52
+ end
53
+
54
+ # @rbs () -> bool
55
+ def supported_protocol?
56
+ return false if @l3.nil?
57
+ @l3.supported_protocol?
58
+ end
59
+
60
+ # @rbs () -> String?
61
+ def protocol
62
+ @l3.protocol if @l3
63
+ end
64
+
65
+ private
66
+
67
+ # @rbs () -> Integer
68
+ def htype
69
+ @htype.map { |b| b.to_s(16).rjust(2, '0') }.join.to_i(16)
70
+ end
71
+
72
+ # @rbs () -> Integer
73
+ def ptype
74
+ @ptype.map { |b| b.to_s(16).rjust(2, '0') }.join.to_i(16)
75
+ end
76
+
77
+ # @rbs () -> Integer
78
+ def oper
79
+ @oper.map { |b| b.to_s(16).rjust(2, '0') }.join.to_i(16)
80
+ end
81
+
82
+ # @rbs () -> String
83
+ def sha
84
+ @sha.map { |b| b.to_s(16).rjust(2, '0') }.join(':')
85
+ end
86
+
87
+ # @rbs () -> String
88
+ def spa
89
+ @spa.map { |b| b.to_s(16).rjust(2, '0') }.join('.')
90
+ end
91
+
92
+ # @rbs () -> String
93
+ def tha
94
+ @tha.map { |b| b.to_s(16).rjust(2, '0') }.join(':')
95
+ end
96
+
97
+ # @rbs () -> String
98
+ def tpa
99
+ @tpa.map { |b| b.to_s(16).rjust(2, '0') }.join('.')
100
+ end
101
+
102
+ # @rbs () -> Redhound::L3::Base?
103
+ def generate_l3
104
+ return if @bytes.size == arp_size
105
+
106
+ if @type.ipv4?
107
+ Ipv4.generate(bytes: @bytes[arp_size..])
108
+ elsif @type.ipv6?
109
+ Ipv6.generate(bytes: @bytes[arp_size..])
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,49 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ module Redhound
5
+ class L3
6
+ class Base
7
+ class << self
8
+ # @rbs (bytes: Array[Integer]) -> Redhound::L3::Base
9
+ def generate(bytes:)
10
+ new(bytes:).generate
11
+ end
12
+ end
13
+
14
+ # @rbs (bytes: Array[Integer]) -> void
15
+ def initialize(bytes:)
16
+ warn 'initialize method must be implemented'
17
+ end
18
+
19
+ # @rbs () -> Redhound::L3::Base
20
+ def generate
21
+ warn 'generate method must be implemented'
22
+ self
23
+ end
24
+
25
+ # @rbs () -> void
26
+ def dump
27
+ puts self
28
+ end
29
+
30
+ # @rbs () -> Integer
31
+ def size
32
+ warn 'size method must be implemented'
33
+ 0
34
+ end
35
+
36
+ # @rbs () -> bool
37
+ def supported_protocol?
38
+ warn 'supported_protocol? method must be implemented'
39
+ false
40
+ end
41
+
42
+ # @rbs () -> Protocol
43
+ def protocol
44
+ warn 'protocol method must be implemented'
45
+ Protocol.new(protocol: 0)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,24 +1,26 @@
1
+ # rbs_inline: enabled
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module Redhound
4
- class Header
5
- class Ipv4
5
+ class L3
6
+ class Ipv4 < Base
6
7
  class << self
8
+ # @rbs (bytes: Array[Integer]) -> Redhound::L3::Ipv4
7
9
  def generate(bytes:)
8
10
  new(bytes:).generate
9
11
  end
10
12
  end
11
13
 
12
- # ref: https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml#protocol-numbers-1
13
- ICMP = 1
14
- UDP = 17
14
+ attr_reader :protocol #: Protocol
15
15
 
16
+ # @rbs (bytes: Array[Integer]) -> void
16
17
  def initialize(bytes:)
17
- raise ArgumentError, 'bytes must be 20 bytes' unless bytes.size == 20
18
+ raise ArgumentError, "bytes must be #{size} bytes" unless bytes.size >= size
18
19
 
19
20
  @bytes = bytes
20
21
  end
21
22
 
23
+ # @rbs () -> Redhound::L3::Ipv4
22
24
  def generate
23
25
  @version = @bytes[0]
24
26
  @ihl = @bytes[0]
@@ -27,85 +29,66 @@ module Redhound
27
29
  @id = @bytes[4..5]
28
30
  @frag_off = @bytes[6..7]
29
31
  @ttl = @bytes[8]
30
- @protocol = @bytes[9]
32
+ @protocol = Protocol.new(protocol: @bytes[9])
31
33
  @check = @bytes[10..11]
32
34
  @saddr = @bytes[12..15]
33
35
  @daddr = @bytes[16..19]
34
36
  self
35
37
  end
36
38
 
37
- def icmp?
38
- @protocol == ICMP
39
- end
40
-
41
- def udp?
42
- @protocol == UDP
43
- end
39
+ # @rbs () -> Integer
40
+ def size = 20
44
41
 
45
- def dump
46
- puts 'IPv4 HEADER----------------'
47
- puts self
42
+ # @rbs () -> String
43
+ def to_s
44
+ " └─ IPv4 Ver: #{version} IHL: #{ihl} TOS: #{@tos} Total Length: #{tot_len} ID: #{id} Offset: #{frag_off} TTL: #{@ttl} Protocol: #{@protocol} Checksum: #{check} Src: #{saddr} Dst: #{daddr}"
48
45
  end
49
46
 
50
- def to_s
51
- <<~IPV4
52
- Version: #{@version}
53
- IHL: #{@ihl}
54
- TOS: #{@tos}
55
- Total Length: #{tot_len}
56
- ID: #{id}
57
- Fragment Offset: #{frag_off}
58
- TTL: #{@ttl}
59
- Protocol: #{protocol}
60
- Checksum: #{check}
61
- Source IP: #{saddr}
62
- Destination IP: #{daddr}
63
- IPV4
47
+ # @rbs () -> bool
48
+ def supported_protocol?
49
+ @protocol.udp? || @protocol.icmp? # steep:ignore
64
50
  end
65
51
 
66
52
  private
67
53
 
54
+ # @rbs () -> Integer
68
55
  def version
69
56
  @version & 0xF0
70
57
  end
71
58
 
59
+ # @rbs () -> Integer
72
60
  def ihl
73
61
  @ihl & 0x0F
74
62
  end
75
63
 
64
+ # @rbs () -> Integer
76
65
  def tot_len
77
66
  @tot_len.map { |b| b.to_s(16).rjust(2, '0') }.join.to_i(16)
78
67
  end
79
68
 
69
+ # @rbs () -> Integer
80
70
  def id
81
71
  @id.map { |b| b.to_s(16).rjust(2, '0') }.join.to_i(16)
82
72
  end
83
73
 
74
+ # @rbs () -> Integer
84
75
  def frag_off
85
76
  @frag_off.map { |b| b.to_s(16).rjust(2, '0') }.join.to_i(16) & 0x1FFF
86
77
  end
87
78
 
88
- def protocol
89
- case @protocol
90
- when ICMP
91
- 'ICMP'
92
- when UDP
93
- 'UDP'
94
- else
95
- 'Unknown'
96
- end
97
- end
98
-
79
+ # @rbs () -> Integer
99
80
  def check
100
81
  @check.map { |b| b.to_s(16).rjust(2, '0') }.join.to_i(16)
101
82
  end
102
83
 
84
+ # @rbs () -> String
103
85
  def saddr
104
- @saddr.map { |b| b.to_s(16).rjust(2, '0') }.join('.')
86
+ @saddr.join('.')
105
87
  end
106
88
 
89
+ # @rbs () -> String
107
90
  def daddr
108
- @daddr.map { |b| b.to_s(16).rjust(2, '0') }.join('.')
91
+ @daddr.join('.')
109
92
  end
110
93
  end
111
94
  end