Class: Xlat::Rfc7915

Inherits:
Object
  • Object
show all
Extended by:
Common
Includes:
Common
Defined in:
lib/xlat/rfc7915.rb

Overview

RFC 7915 based stateless IPv4/IPv6 translator (SIIT). Intentionally not thread-safe.

datatracker.ietf.org/doc/html/rfc7915 www.rfc-editor.org/info/rfc7915

Defined Under Namespace

Classes: BufferInUse

Constant Summary collapse

MAX_FRAGMENT_ID =
0xffffffff
ICMPV6V4_TYPE_MAP =
ICMPV6_POINTER_MAP =
ICMPV4V6_TYPE_MAP =
ICMPV4_POINTER_MAP =

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Common

sum16be

Constructor Details

#initialize(source_address_translator:, destination_address_translator:, for_icmp: false) ⇒ Rfc7915

Returns a new instance of Rfc7915.

Parameters:



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/xlat/rfc7915.rb', line 20

def initialize(source_address_translator:, destination_address_translator:, for_icmp: false)
  @source_address_translator = source_address_translator
  @destination_address_translator = destination_address_translator

  # checksum_neutrality = @source_address_translator.checksum_neutral? && @destination_address_translator.checksum_neutral?

  @next_fragment_identifier = 0

  @ipv4_new_header_buffer = IO::Buffer.new(20)
  @ipv6_new_header_buffer = IO::Buffer.new(40)
  @output = []
  @new_header_buffer_in_use = false
  return_buffer_ownership

  unless for_icmp
    @inner_icmp = self.class.new(source_address_translator: destination_address_translator, destination_address_translator: source_address_translator, for_icmp: true)
    @inner_packet = Protocols::Ip.new(icmp_payload: true)
  end
end

Instance Attribute Details

#next_fragment_identifierObject

Returns the value of attribute next_fragment_identifier.



40
41
42
# File 'lib/xlat/rfc7915.rb', line 40

def next_fragment_identifier
  @next_fragment_identifier
end

Instance Method Details

#return_buffer_ownershipObject



195
196
197
198
199
200
201
202
203
204
# File 'lib/xlat/rfc7915.rb', line 195

def return_buffer_ownership
  @new_header_buffer_in_use = false
  @ipv4_new_header_buffer.clear
  @ipv6_new_header_buffer.clear
  @output.clear
  if @inner_icmp
    @inner_icmp.return_buffer_ownership
  end
  nil
end

#translate_to_ipv4(ipv6_packet, max_length) ⇒ Object

Returns array of bytestrings to send as a IPv4 packet. May update original packet content.

Raises:



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/xlat/rfc7915.rb', line 48

def translate_to_ipv4(ipv6_packet, max_length)
  raise BufferInUse if @new_header_buffer_in_use
  raise ArgumentError unless ipv6_packet.version.to_i == 6
  icmp_payload = @inner_icmp.nil?
  @new_header_buffer_in_use = true
  new_header_buffer = @ipv4_new_header_buffer
  ipv6_bytes = ipv6_packet.bytes
  ipv6_bytes_offset = ipv6_packet.bytes_offset

  cs_delta = 0 # delta for incremental update of upper-layer checksum fields

  # Version = 4, IHL = 5
  new_header_buffer.set_value(:U8, 0, (4 << 4) + 5)

  # FIXME: ToS ignored

  # Total Length = copy from IPv6; may be updated in later step
  ipv4_length = ipv6_packet.l4_length + 20
  # not considering as a checksum delta because upper layer packet length doesn't take this into account; cs_delta += ipv6_length - ipv4_length
  new_header_buffer.set_value(:U16, 2, ipv4_length)

  # Identification = generate
  new_header_buffer.set_value(:U16, 4, make_fragment_id())

  # TTL = copy from IPv6
  new_header_buffer.set_value(:U8, 8, ipv6_bytes.get_value(:U8, ipv6_bytes_offset + 7))

  # Protocol = copy from IPv6; may be updated in later step for ICMPv6=>4 conversion
  new_header_buffer.set_value(:U8, 9, ipv6_packet.proto)

  # Source and Destination address
  cs_delta_a = @source_address_translator.translate_address_to_ipv4(ipv6_bytes.slice(ipv6_bytes_offset + 8,16), new_header_buffer, 12) or return return_buffer_ownership()
  cs_delta_b = @destination_address_translator.translate_address_to_ipv4(ipv6_bytes.slice(ipv6_bytes_offset + 24,16), new_header_buffer, 16) or return return_buffer_ownership()
  cs_delta += cs_delta_a + cs_delta_b

  # TODO: DF bit
  # TODO: discard if expired source route option is present

  if ipv6_packet.proto == 58 # icmpv6
    icmp_result, icmp_output = translate_icmpv6_to_icmpv4(ipv6_packet, new_header_buffer, max_length - 20)
    return return_buffer_ownership() unless icmp_result
    cs_delta += icmp_result
  end

  unless icmp_output
    l4_length = ipv6_packet.l4_bytes_length
    unless 20 + l4_length <= max_length
      if icmp_payload
        l4_length = max_length - 20
      else
        # FIXME: this should not happen
        return return_buffer_ownership()
      end
    end
  end

  #p ipv6_bytes.chars.map { _1.ord.to_s(16).rjust(2,'0') }.join(' ')
  #p new_header_buffer.chars.map { _1.ord.to_s(16).rjust(2,'0') }.join(' ')

  ipv4_packet = ipv6_packet.convert_version!(Protocols::Ip::Ipv4, new_header_buffer, cs_delta)
  ipv4_packet.apply_changes

  # Recompute checksum (this must be performed after Ip#apply_changes as it updates ipv4 checksum field along with l4 checksum field using delta,
  # while new_header_buffer has no prior checksum value)
  new_header_buffer.set_value(:U16, 10, 0)
  cksum = Protocols::Ip.checksum(new_header_buffer)
  new_header_buffer.set_value(:U16, 10, cksum)

  # TODO: Section 5.4. Generation of ICMPv6 Error Messages
  # TODO: Section 5.1.1. IPv6 Fragment Processing

  @output << new_header_buffer
  if icmp_output
    @output.concat(icmp_output)
  else
    @output << ipv4_packet.l4_bytes.slice(ipv4_packet.l4_bytes_offset, l4_length)
  end
  @output
end

#translate_to_ipv6(ipv4_packet, max_length) ⇒ Object

Returns array of bytestrings to send as a IPv6 packet. May update original packet content.

Raises:



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/xlat/rfc7915.rb', line 129

def translate_to_ipv6(ipv4_packet, max_length)
  raise BufferInUse if @new_header_buffer_in_use
  raise ArgumentError unless ipv4_packet.version.to_i == 4
  icmp_payload = @inner_icmp.nil?
  # TODO: support fragment extension and esp
  # TODO: ignore extension
  @new_header_buffer_in_use = true
  ipv4_bytes = ipv4_packet.bytes
  ipv4_bytes_offset = ipv4_packet.bytes_offset
  new_header_buffer = @ipv6_new_header_buffer
  cs_delta = 0 # delta for incremental update of upper-layer checksum fields

  # Version = 6, traffic class = 0
  new_header_buffer.set_value(:U8, 0, 6 << 4)

  # Flow label = 0

  # IPv6 Length = IPv4 total length - IPv4 header length; may be updated in later step
  ipv6_length = ipv4_packet.l4_length
  # not considering as a checksum delta because upper layer packet length doesn't take this into account; cs_delta += ipv4_length - ipv6_length
  new_header_buffer.set_value(:U16, 4, ipv6_length)

  # Next Header = copy from IPv4; may be updated in later step for ICMPv6=>4 conversion
  new_header_buffer.set_value(:U8, 6, ipv4_packet.proto)

  # Hop limit = copy from IPv4
  new_header_buffer.set_value(:U8, 7, ipv4_bytes.get_value(:U8, ipv4_bytes_offset + 8))

  # Source and Destination address
  cs_delta_a = @destination_address_translator.translate_address_to_ipv6(ipv4_bytes.slice(ipv4_bytes_offset + 12,4), new_header_buffer, 8) or return return_buffer_ownership()
  cs_delta_b = @source_address_translator.translate_address_to_ipv6(ipv4_bytes.slice(ipv4_bytes_offset + 16,4), new_header_buffer, 24) or return return_buffer_ownership()
  cs_delta += cs_delta_a + cs_delta_b

  if ipv4_packet.proto == 1 # icmpv4
    icmp_result, icmp_output = translate_icmpv4_to_icmpv6(ipv4_packet, new_header_buffer, max_length - 40)
    return return_buffer_ownership() unless icmp_result
    cs_delta += icmp_result
  end

  unless icmp_output
    l4_length = ipv4_packet.l4_bytes_length
    unless 40 + l4_length <= max_length
      if icmp_payload
        l4_length = max_length - 40
      else
        # FIXME: generate "fragmentation needed" if DF=1
        return return_buffer_ownership()
      end
    end
  end

  # TODO: generate udp checksum option (section 4.5.)
  ipv6_packet = ipv4_packet.convert_version!(Protocols::Ip::Ipv6, new_header_buffer, cs_delta)
  ipv6_packet.apply_changes

  # TODO: Section 4.4.  Generation of ICMPv4 Error Message

  @output << new_header_buffer
  if icmp_output
    @output.concat(icmp_output)
  else
    @output << ipv6_packet.l4_bytes.slice(ipv6_packet.l4_bytes_offset, l4_length)
  end
  @output
end