redhound 0.2.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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