UDP Proxy

R

Randy General

Hi,
I'm reasonably new to programming, and Ruby is my first language. I've
been trying to work on increasingly complex projects to get a feel for
the language, but I'm hitting a wall with this one.

My goal is to make a UDP Proxy. Basically it will be initialized with an
IP (ip_x), and if any packets are sent to the proxy, it will re-send
them to ip_x, take any response ip_x sends to the packet, and return it
to the client. I've tried someone else's implementation of this in C,
and it worked quite well. However, while my code works, it's
tremendously slow. I attempted to play a game through the proxy, and it
was exceptionally slow.

Here's my code so far:

http://pastie.org/707258.txt

I'm sure the code could be a lot cleaner at the bottom, as right now it
can only support one client, among other problems, but I'm trying to
sort out the poor performance issue first. While the implementation in C
works without a hitch, even when playing a network intensive FPS, my
implementation in Ruby is tremendously slow. That brings me to my
question:

Is the poor performance of the script a result of poor coding on my
part? Or is an interpreted language like Ruby incapable of efficiently
doing this sort of task?

Thanks!
 
D

David Masover

My goal is to make a UDP Proxy. Basically it will be initialized with an
IP (ip_x), and if any packets are sent to the proxy, it will re-send
them to ip_x, take any response ip_x sends to the packet, and return it
to the client.

Question #1: Would port forwarding work for this? It looks like a simple DNAT
filter on Linux, for example.
I've tried someone else's implementation of this in C,
and it worked quite well. However, while my code works, it's
tremendously slow. I attempted to play a game through the proxy, and it
was exceptionally slow.

Question #2: If you've got an implementation in C, why do you need one in
Ruby? If you're wanting to do fancier things in Ruby, would it work to use the
C version as a library?
Is the poor performance of the script a result of poor coding on my
part? Or is an interpreted language like Ruby incapable of efficiently
doing this sort of task?

It wouldn't be that Ruby is interpreted. It might be that Ruby is slow. Keep
in mind that Lisp can be interpreted, and can also run faster than C. For that
matter, C can be interpreted, too.

There's a few things you're doing that just feel wrong from a performance
standpoint:

data = []
parts.each do |part|
data += part[0..-2]
template << part[-1]
end

So wait -- you're doing += on an array, but << on a string? I'm not sure if <<
is more efficient on a string, but it certainly is on an array. Change that to:

data << part[0..-2]

If you didn't know, += in Ruby is expanded to: data = data + part[0..-2],
which likely means one full array copy per part.

You're also doing a lot of binary packing and unpacking, and building packets
out of arrays, which makes me wonder if there's a higher-level library you
could use for manipulating UDP packets.

Make sure you're at least using Ruby 1.9 (that's twice the speed of 1.8), and
try JRuby.

And finally: Run it through some profiling, to see where your bottlenecks
actually are.

I think that's the best way to figure out if it can be done. But I'm not really
sure, maybe someone more knowledgeable can tell you, for instance, how many
packets per second you'd expect, and if that runs into some hard limit on the
number of cycles per Ruby function call or something like that.
 
R

Randy General

Hi David,
Thanks for responding.
Question #1: Would port forwarding work for this? It looks like a simple
DNAT
filter on Linux, for example.

It might, but this is more so just to try and code something difficult,
rather than trying to find the easiest solution to achieve it. I don't
have any legitimate need for the end result.
Question #2: If you've got an implementation in C, why do you need one
in
Ruby? If you're wanting to do fancier things in Ruby, would it work to
use the
C version as a library?

Same reason as above, but another problem is that the C version is coded
exclusively for Windows, and I'd like to achieve this on Linux (trying
to learn Linux simultaneously).
There's a few things you're doing that just feel wrong from a
performance
standpoint:

Thanks for the feedback, I've made the suggested change.
Make sure you're at least using Ruby 1.9 (that's twice the speed of
1.8), and
try JRuby.

I was using Ruby 1.8.6, I'll upgrade to 1.9 now and give that a whirl.
Wasn't aware that the speed difference was so dramatic.
And finally: Run it through some profiling, to see where your
bottlenecks
actually are.

Haven't learned about profiling yet, but this is probably as good a time
as any. I'll take a look into that and try to figure out where it's
slowing down.

Thanks for all of your feedback!
 
B

Brian Candler

Randy said:
I attempted to play a game through the proxy, and it
was exceptionally slow.

Can you measure what you mean by 'slow', in terms of the elapsed time
between a packet coming in and a packet going out? You can do this
externally using tcpdump -ttt, or you can add some timing into the code
itself.

It's possible that Ruby is doing reverse DNS lookups for every incoming
packet when your C code isn't. tcpdump will show that. You can disable
it using:

Socket.do_not_reverse_lookup = true

at the top of your code.

Why are you using a raw socket? You could just forward using a UDP
socket. Then you wouldn't need to build an IP header, nor a UDP header,
nor calculate the UDP checksum, since this is all taken care of for you
by the kernel. Are you trying to send out a packet with a source address
which doesn't belong to the sending machine? (which isn't proxying)
Is the poor performance of the script a result of poor coding on my
part? Or is an interpreted language like Ruby incapable of efficiently
doing this sort of task?

Stuff like the checksum calculation is going to be a lot slower in Ruby
than in C. But you should use the profiler to find out exactly where the
majority of time is being spent.
 
R

Randy General

Hi Brian,
I'll add some timing in briefly. I'll also do the change you mentioned
to prevent reverse lookup.

I'm not spoofing an IP that isn't on the machine, but I don't know how
to send a packet out on the same port I'm listening on. Basically I'm
listening for packets that I'm going to re-send to ip_x on port y, so
any responses I send back have to be sent from port y, and I'm not sure
how to do that. So I've resorted to spoofing the port.

Is there a way to dictate which port that packet will go out on?

Thanks!
 
R

Randy General

I was able to sort out how to send from the same port I'm listening on
and condensed the code down to the following:

http://pastie.org/707376.txt

But it's still unable to handle proxying a connection to a FPS server,
while the C implementation can. Any way I might be able to speed it up?
And would threading possibly be a wise idea?

Thanks.
 
B

Brian Candler

Randy said:
I was able to sort out how to send from the same port I'm listening on
and condensed the code down to the following:

http://pastie.org/707376.txt

This code looks very strange to me. It handles the first received packet
in one way then the second received packet in a different way, then
alternates between these two ways. If the protocol requires handling in
this way then if you ever lost one packet then it would get out of sync.

I suspect what you're trying to do is handle
a --> proxy --> b
and
a <-- proxy <-- b

in which case you need to be able to distinguish these two cases when
you receive the packet, not by the sequencing.

It also seems strange to me your requirement that if the proxy is
listening on port X, then it must also send with a source port of X.
That's not normally a requirement for a proxy, but then you haven't
really said much about the protocol itself.

Does the destination really care about the source port which the request
comes from? Normally a client picks a random source port >=1024, so the
only thing which matters is the destination port:

r1 X r2 X
a ------> proxy -------> b

where r1 and r2 are randomly-chosen ports for the lifetime of the
socket.

Anyway, in your code, life would be much easier if you bound two
different sockets (either on different ports, or on different IPs).

X Y
a ------> proxy -------> b

because you could select() on the two sockets, and easily distinguish
outbound from return packets.

You also said you need to support multiple concurrent clients. So you
need, for each return packet, to know which client to send it to. The
easiest way of doing this would be to bind a new socket for each client,
on a different (dynamic) port.
But it's still unable to handle proxying a connection to a FPS server,
while the C implementation can.

I suggest you look at tcpdump output of the two programs, to see how the
sent packets differ in each case.
 
R

Randy General

Hi Brian,
I suspect what you're trying to do is handle
a --> proxy --> b
and
a <-- proxy <-- b


"It also seems strange to me your requirement that if the proxy is
listening on port X, then it must also send with a source port of X.
That's not normally a requirement for a proxy, but then you haven't
really said much about the protocol itself."

I was mistaken there, what I meant to say was what you diagramed later
where the source ports, while different on each side, remained
consistent for each client.
Anyway, in your code, life would be much easier if you bound two
different sockets (either on different ports, or on different IPs).

X Y
a ------> proxy -------> b

because you could select() on the two sockets, and easily distinguish
outbound from return packets.

Great, thanks for the suggestion. I was trying to think of how to deal
with outgoing packet for which there's no response, that will hopefully
take care of it.

I'll post what I come up with in here shortly.

Thanks for all of your help!
 
R

Randy General

Here's what I have so far:

http://tinyurl.com/yeoy9xr

It seems to work perfectly well when there's only one user, but as soon
as multiple users attempt to connect, or a single user has more than one
connection across different ports, it runs into problems and doesn't
keep each conversation distinct from the next.

Is there any obvious problem that might be causing that?

Thanks for your help!
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads


Members online

No members online now.

Forum statistics

Threads
473,770
Messages
2,569,584
Members
45,075
Latest member
MakersCBDBloodSupport

Latest Threads

Top