[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Re: LuaSocket and multiple UDP peers
- From: Sean Conner <sean@...>
- Date: Tue, 23 Apr 2013 23:36:59 -0400
It was thus said that the Great Rena once stated:
> On Tue, Apr 23, 2013 at 9:07 PM, William Ahern
> <william@25thandclement.com>wrote:
>
> > On Tue, Apr 23, 2013 at 08:15:24PM -0400, Rena wrote:
> > > On Tue, Apr 23, 2013 at 7:53 PM, William Ahern
> > > <william@25thandclement.com>wrote:
> > <snip>
> > > Yes, by listening I meant able to receive packets, sorry for the
> > confusion.
> > > Am I correct in understanding that a single message sent by a peer will
> > > always arrive as a single message at the listening application?
> >
> > Yes. UDP preserves message boundaries, and it will always arrive (if it
> > arrives, and if it's not dropped by the UDP stack) as a single message, and
> > will be dequeued in a single call to recv, recvfrom, or read.
> >
> > > Actually, that still doesn't make it perfectly clear how *a should work
> > > with UDP, since the peer itself might split its message into several
> > > pieces.
> >
> > If the _application_ peer splits its logical message into several pieces,
> > then of course each individual piece will be received atomically by the
> > recipient, possibly interleaved with other pieces by other senders. Your
> > application "pieces" are messages as far as UDP is concerned.
> >
> > A "message" in UDP is functionally equivalent to a single send or recv
> > call,
> > not whatever your application defines it as. If the application requires
> > logical messaging which span multiple send calls, then it needs to
> > implement
> > an additional layer of framing at both end points.
> >
> > I'd prefer to abstain from using ambiguous OSI layering terminology, but
> > you
> > can think of UDP as layer 4, and your logical framing as something higher
> > than layer 4. If your logical framing isn't satisfied by UDP itself, then
> > you need to implement it separately.
> >
> > > It seems like blindly reading until the socket is closed in this
> > situation
> > > would be a great way to let other peers interfere with the
> > transmission...
> >
> > If you mean flooding, then yes it could be a problem, but usually only if
> > done deliberately, in which case you may have other problems. But barring
> > address spoofing and bandwidth issues, it doesn't by itself cause problems
> > unless the application makes unwarranted assumptions.
> >
> > You can always use connect(2) to create an association between two UDP end
> > points. The kernel will then drop messages for you, so you don't have to
> > check the IP address.
> >
> >
> >
> Well flooding would be one way, but I was thinking more of a communication
> between two systems using UDP, and a malicious third party just starts
> sending some of its own packets to one of the clients on that same port; if
> you just read everything until the socket is closed, disregarding who it
> came from, and return it all as one string, the third party could inject
> some malicious packets into the stream. But you're right, that would be a
> silly way to do things... of course, writing a general-purpose socket
> library and not a particular application, you have to be ready for the
> possibility that someone wants to use your library to communicate with a
> client that does silly things like that.
>
> I wasn't aware you could use connect() on a UDP socket. So, would it be
> done like this?
>
> void processMessage(char *buffer, ssize_t size) { ... }
>
> int done = 0;
> int sockfd = socket(SOCK_DGRAM, AF_INET, 0);
> bind(sockfd, AF_INET, someAddress, somePort);
>
> char buffer[8192];
> struct sockaddr_in peer;
> socklen_t peer_len = sizeof(peer);
> ssize_t recvsz = recvfrom(sockfd, buffer, sizeof(buffer), 0,
> (struct sockaddr*)&peer, &peer_len);
>
> processMessage(buffer, recvsz);
> connect(sockfd, peer.sin_addr, peer.sin_port);
>
> while(!done) {
> recvsz = recv(sockfd, buffer, sizeof(buffer), 0);
> processMessage(buffer, recvsz);
> }
>
> or am I still missing something?
I feel you are missing something, and for this explanation, I'll be using
my own net library [1] as it's Lua and we're on a Lua list here.
On the client side:
server = net.address('fc00::dead:beef',15151,'udp')
sock = net.socket(server.family,'udp')
You can now sent "messages" to the server:
sock:write(server,"this is a message")
If you split it up, like:
sock:write(server,"this is ")
sock:write(server,"a message")
The server process will receive two separate UDP packets. Now, if you
send a very large packet:
data = some_big_string_that_is_48_k_in_size
sock:write(server,data)
The IP stack on the client side will most likely break it up into multiple
packets---this is IP fragmentation. The IP stack on the receiving end will
collect all the fragments that comprise the packet (and here I'm being very
loose with the terminology) and when all 48k worth have been reassembled,
only then will the IP stack send the packet to the receiving process.
So in this case:
data = some_big_string_that_is_48_k_in_size
sock:write(server,data)
data = more_big_data_that_is_now_60_k_in_size
sock:write(server,data)
the server process will still receive only two UDP packets, even though
across an Ethernet based network, there will probably be around 100 packets
sent.
Now, on the server side of things:
addr = net.address('fc00::dead:beef',15151,'udp')
sock = net.socket(addr.family,'udp')
sock.reuseaddr = true
sock:bind(addr)
function mainloop()
local remaddr,data,err = sock:read()
if data == nil then
report_error(err)
return mainloop()
end
-- rest of code
return mainloop()
end
(I use tail calls so I can "continue" the loop)
sock:read() is implemented via recvfrom(), so I have the address of the
sender so I can send replies back to:
if data == 'time?' then
sock:send(remaddr,os.date())
end
I can also use the remaddr as a key to maintain data per client---so, for
example:
local remaddr,data = sock:read() -- assume okay for this example
-- checks for lost and duplicated packets left to the read
if client_list[remaddr] == nil then
client_list[remaddr] = data
else
client_list[remaddr] = client_list[remaddr] .. data
end
Now, about connect() and UDP sockets. The use of connect() on a datagram
based socket (UDP) means the client program can use the read() and write()
system calls on the socket, since the kernel "knows" which address to send
the packets to (think of it as binding the remote address of a socket). If
you don't use connect(), then you *have* to use sendto() to send the packet.
A server program should not use connect(), as it probably has to talk to
multiple clients at a time.
Another thing to remember about UDP---it's not connection oriented, so the
client side can close its socket without the server ever being the wiser.
It's up to higher levels of code to detect a non-communicating end (server
or client).
-spc (And yes, those are IPv6 addresses)
[1] https://github.com/spc476/lua-conmanorg/blob/master/src/net.c