This commit is contained in:
Fijxu 2024-12-25 23:43:25 -03:00
commit f37d8e8283
Signed by: Fijxu
GPG key ID: 32C1DDF333EDA6A4
6 changed files with 192 additions and 0 deletions

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Fijxu <fijxu@nadeko.net>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

3
README.md Normal file
View file

@ -0,0 +1,3 @@
# natpmpcrystal
Work in progress library implementation of RFC6886 **NAT Port Mapping Protocol (NAT-PMP)**

13
shard.yml Normal file
View file

@ -0,0 +1,13 @@
name: natpmpcrystal
version: 0.1.0
authors:
- Fijxu <fijxu@nadeko.net>
targets:
natpmpcrystal:
main: src/natpmpcrystal.cr
crystal: '>= 1.14.0'
license: MIT

View file

@ -0,0 +1,9 @@
require "./spec_helper"
describe Natpmpcrystal do
# TODO: Write tests
it "works" do
false.should eq(true)
end
end

2
spec/spec_helper.cr Normal file
View file

@ -0,0 +1,2 @@
require "spec"
require "../src/natpmpcrystal"

144
src/natpmpcrystal.cr Normal file
View file

@ -0,0 +1,144 @@
require "socket"
require "benchmark"
module NatPMP
enum ResultCodes
SUCCESS = 0
UNSUPPORTED_VERSION = 1
NOT_AUTHORIZED_OR_REFUSED = 2
NETWORK_FAILURE = 3
OUT_OF_RESOURCES = 4
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
def initialize(@op = UDP, @internal_port = nil, @external_port = nil, @lifetime = 0)
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,
class Client
@client : UDPSocket
@gateway_ip : String
# Overload
def initialize(gateway_ip : URI)
initialize(gateway_ip.path)
end
def initialize(gateway_ip : String)
@gateway_ip = gateway_ip
@client = UDPSocket.new
# A given host may have more than one independent
# NAT-PMP client running at the same time, and address announcements
# need to be available to all of them. Clients should therefore set
# the SO_REUSEPORT option or equivalent in order to allow other
# processes to also listen on port 5350.
@client.reuse_port = true
# Additionally, implementers
# have encountered issues when one or more processes on the same device
# listen to port 5350 on *all* addresses. Clients should therefore
# bind specifically to 224.0.0.1:5350, not to 0.0.0.0:5350.
# @client.bind("224.0.0.1", 5350)
connect()
end
def connect
# @client.join_group(Socket::IPAddress.new("224.0.0.1", 5351))
@client.connect(@gateway_ip, 5351)
end
def send_public_address_request_raw : Bytes
@client.send("\x00\x00")
msg = Bytes.new(12)
@client.receive(msg)
return msg
end
def send_public_address_request
@client.send("\x00\x00")
msg = Bytes.new(12)
@client.receive(msg)
vers = (msg[0])
op = (msg[1])
result_code = get_result_code(msg[2..3])
epoch = get_epoch(msg[4..7])
# If the result code is non-zero, the value of the External
# IPv4 Address field is undefined (MUST be set to zero on transmission,
# and MUST be ignored on reception).
if result_code != 0
ip_address = nil
else
ip_address = get_ip_address(msg[8..11])
end
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)
end
private def get_result_code(msg)
# Responses always contain a
# 16-bit result code in network byte order
return IO::ByteFormat::BigEndian.decode(UInt16, msg)
end
# 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_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
request = client.request_mapping(NatPMP::MappingPacket::OP::UDP.value, 25580_u16, 25580_u16, 0_u32)
# request2 = client.request_mapping(1, 255802, 25580, 0)
pp request