This commit is contained in:
Fijxu 2024-12-29 01:44:57 -03:00
parent f37d8e8283
commit 60d77e7a3a
Signed by: Fijxu
GPG key ID: 32C1DDF333EDA6A4

View file

@ -11,30 +11,62 @@ module NatPMP
UNSUPPORTED_OPCODE = 5
end
struct MappingPacket
enum OP : UInt8
UDP = 1_u8
TCP = 2_u8
end
property vers : UInt8 = 0_u8
property op : UInt8
property reserved : UInt16 = 0_u16
property internal_port : UInt16
property external_port : UInt16
property lifetime : UInt32
struct MappingPacket
@vers : UInt8 = 0_u8
@op : UInt8 = OP::UDP.value
@reserved : UInt16 = 0_u16
@internal_port : UInt16
@external_port : UInt16
@lifetime : UInt32 = 0_u32
def initialize(@op = UDP, @internal_port = nil, @external_port = nil, @lifetime = 0)
def initialize(@internal_port, @external_port, @lifetime = 0, @op = UDP)
unless [1, 2].include?(@op)
raise ArgumentError, "Operation should be either '1_u8' for UDP or '2_u8' for TCP (default: UDP)"
end
end
# private record MappingPacket,
# vers : UInt8 = 0,
# op : UInt8 = 0,
# reserved : UInt16 = 0,
# internal_port : UInt16 = 0,
# external_port : UInt16 = 0,
# lifetime : UInt32 = 0,
def initialize(@internal_port, @external_port)
end
def to_io
io = IO::Memory.new(12)
io.write_bytes(@vers, IO::ByteFormat::BigEndian)
io.write_bytes(@op, IO::ByteFormat::BigEndian)
io.write_bytes(@reserved, IO::ByteFormat::BigEndian)
io.write_bytes(@internal_port, IO::ByteFormat::BigEndian)
io.write_bytes(@external_port, IO::ByteFormat::BigEndian)
io.write_bytes(@lifetime, IO::ByteFormat::BigEndian)
return io
end
def to_slice
slice = uninitialized UInt8[12]
IO::ByteFormat::BigEndian.encode(@vers, v = Bytes.new(1))
IO::ByteFormat::BigEndian.encode(@op, o = Bytes.new(1))
# IO::ByteFormat::BigEndian.encode(@reserved, r = Bytes.new(2))
IO::ByteFormat::BigEndian.encode(@internal_port, i = Bytes.new(2))
IO::ByteFormat::BigEndian.encode(@external_port, e = Bytes.new(2))
IO::ByteFormat::BigEndian.encode(@lifetime, l = Bytes.new(4))
slice[0] = v[0]
slice[1] = o[0]
slice[2] = 0 # RESERVED
slice[3] = 0 # RESERVED
slice[4] = i[0]
slice[5] = i[1]
slice[6] = e[0]
slice[7] = e[1]
slice[8] = l[0]
slice[9] = l[1]
slice[10] = l[2]
slice[11] = l[3]
return slice
end
end
class Client
@client : UDPSocket
@ -45,8 +77,7 @@ module NatPMP
initialize(gateway_ip.path)
end
def initialize(gateway_ip : String)
@gateway_ip = gateway_ip
def initialize(@gateway_ip : String)
@client = UDPSocket.new
# A given host may have more than one independent
# NAT-PMP client running at the same time, and address announcements
@ -77,9 +108,27 @@ module NatPMP
def send_public_address_request
@client.send("\x00\x00")
msg = Bytes.new(12)
@client.read_timeout = 250.milliseconds
8.times do |i|
begin
@client.receive(msg)
vers = (msg[0])
op = (msg[1])
break
rescue IO::TimeoutError
# If no
# NAT-PMP response is received from the gateway after 250 ms, the
# client retransmits its request and waits 500 ms. The client SHOULD
# repeat this process with the interval between attempts doubling each
# time.
@client.read_timeout = @client.read_timeout.not_nil!*2
next
rescue
raise "The gateway '#{@gateway_ip}' does not support NAT-PMP"
break
end
end
vers = msg[0]
op = msg[1]
result_code = get_result_code(msg[2..3])
epoch = get_epoch(msg[4..7])
@ -94,17 +143,18 @@ module NatPMP
return vers, op, result_code, epoch, ip_address
end
# def request_mapping(op : Int32, internal_port : Int32, external_port : Int32, lifetime : Int32)
# begin
# request_mapping(op.to_u8, internal_port.to_u16, external_port.to_u16, lifetime.to_u32)
# rescue ex
# puts ex.message
# exit(1)
# end
# end
def request_mapping(op : UInt8, internal_port : UInt16, external_port : UInt16, lifetime : UInt32)
request = MappingPacket.new(op, internal_port, external_port, lifetime)
def request_mapping(internal_port : UInt16, external_port : UInt16, lifetime : Uint32, operation : UInt8)
request = MappingPacket.new(internal_port, external_port, lifetime, operation)
msg = Bytes.new(16)
@client.receive(msg)
vers = msg[0]
op = msg[1]
result_code = get_result_code(msg[2..3])
epoch = get_epoch(msg[4..7])
internal_port = get_port(msg[8..9])
external_port = get_port(msg[10..11])
lifetime = get_lifetime(msg[12..15])
return vers, op, result_code, epoch, internal_port, external_port, lifetime
end
private def get_result_code(msg)
@ -115,30 +165,50 @@ module NatPMP
# Seconds Since Start of Epoch
private def get_epoch(msg)
# epoch : Int32 = 0
# msg.each_with_index do |byte, index|
# epoch |= (byte.to_i << (8 * (msg.size - 1 - index)))
# end
# Responses also contain a 32-bit unsigned integer
# corresponding to the number of seconds since the NAT gateway was
# rebooted or since its port mapping state was otherwise reset.
return IO::ByteFormat::BigEndian.decode(UInt32, msg)
end
private def get_port(msg)
return IO::ByteFormat::BigEndian.decode(UInt16, msg)
end
private def get_ip_address(msg)
"#{msg[0]}.#{msg[1]}.#{msg[2]}.#{msg[3]}"
end
end
end
client = NatPMP::Client.new("192.168.1.1")
pp client.send_public_address_request
xd = client.send_public_address_request_raw
pp xd
# client = NatPMP::Client.new("192.168.0.1")
# pp client.send_public_address_request
pp mapping_packet = NatPMP::MappingPacket.new(25555,25555)
request = client.request_mapping(NatPMP::MappingPacket::OP::UDP.value, 25580_u16, 25580_u16, 0_u32)
Benchmark.ips do |x|
x.report("bytes") do
mapping_packet.to_io
end
x.report("staticarray") do
# pp mapping_packet.to_io.to_slice
mapping_packet.to_slice
end
x.report("staticarray to io") do
# pp mapping_packet.to_io.to_slice
mapping_packet.to_slice
end
end
pp typeof(mapping_packet)
# xd = client.send_public_address_request_raw
# pp xd
# request = client.request_mapping(25580, 25580)
# request2 = client.request_mapping(1, 255802, 25580, 0)
pp request
# pp request