init
This commit is contained in:
commit
f37d8e8283
6 changed files with 192 additions and 0 deletions
21
LICENSE
Normal file
21
LICENSE
Normal 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
3
README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# natpmpcrystal
|
||||
|
||||
Work in progress library implementation of RFC6886 **NAT Port Mapping Protocol (NAT-PMP)**
|
13
shard.yml
Normal file
13
shard.yml
Normal 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
|
9
spec/natpmpcrystal_spec.cr
Normal file
9
spec/natpmpcrystal_spec.cr
Normal 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
2
spec/spec_helper.cr
Normal file
|
@ -0,0 +1,2 @@
|
|||
require "spec"
|
||||
require "../src/natpmpcrystal"
|
144
src/natpmpcrystal.cr
Normal file
144
src/natpmpcrystal.cr
Normal 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
|
Loading…
Reference in a new issue