Reputation: 76309
I have a UDPSocket
instance:
io = UDPSocket.new
io.connect "8.8.4.4", 53
It connects to a DNS server over port 53, sends a DNS query and retrieves the result. DNS has built-in message compression in the form of pointers, see RFC 1035, 4.1.4. Message compression. Instead of a domain name, resource records can contain a message offset to point to the domain of the question. This is so the domain name doesn't have to be repeated for each record.
I have implemented this as follows in my resource record class:
def self.from_io(io : IO, format : IO::ByteFormat) : self
domain = ""
loop do
codepoint = UInt8.from_io io, format
break if codepoint == 0
if codepoint >= 192 # if the octet starts with 11 as defined in the rfc
current_pos = io.pos
pointer = UInt8.from_io io, format
io.seek(pointer)
# read the string...
end
# ...
end
end
This does not work because UDPSocket
does not implement IO#pos
and IO#seek
:
Unhandled exception: Unable to pos
To fix this, I have created a subclass that utilizes IO::Memory
:
class DNS::DNSSocket < UDPSocket
def initialize(family : Socket::Family = Socket::Family::INET)
super family
@memory = IO::Memory.new
end
def read(slice : Bytes)
if slice.size + pos > @memory.size
super slice
@memory.write slice
else
@memory.read slice
end
slice.size
end
def pos
@memory.pos
end
def pos=(value)
@memory.pos = value
end
def seek(offset, whence : Seek = IO::Seek::Set)
@memory.seek offset, whence
end
def clear
@memory.clear
end
end
My questions are as follows:
Is this a good solution, or do you know of something more elegant?
The IO::Memory
instance needs to be reset after each message. Is it possible to call clear
at the end or beginning of a datagram (packet) from within my DNSSocket implementation? I can also call it in my message parser but I'd prefer not to.
Upvotes: 0
Views: 38
Reputation: 76309
To add to the other reply, the solution is much much simpler than I thought.
slice = Bytes.new(512)
socket.read slice
io = IO::Memory.new slice
Upvotes: 0
Reputation: 5661
UDP is a connectionless communication model and thus doesn't support streaming. UDPSocket
inherits IO
, but it shouldn't and I think that's actually a flaw in the API caused by Socket
inheriting from IO
. It still works, because the underlying syscalls used by the IO implementation also work with UDP sockets. But using a UDPSocket
as an IO is not ideal and should be avoided. Since datagrams are usually short messages anyway, it's perfectly fine to load them entirely into memory.
So, I'd advise to use UDPSocket#receive
instead, which allows you to easily wrap the slice in IO::Memory
.
Upvotes: 1