Receive UDP datagram on raw socket? - ruby

I'm trying to write my own implementation of UDP in ruby for educational purposes using raw sockets.
Here's what I have so far:
require 'socket'
addr = Socket.pack_sockaddr_in(4567, '127.0.0.1')
socket = Socket.new(
Socket::PF_INET,
Socket::SOCK_RAW,
Socket::IPPROTO_RAW
)
socket.bind(addr)
socket.recvfrom(1024)
I am testing it like so:
require 'socket'
udp = UDPSocket.new
udp.send "Hello World", 0, "127.0.0.1", 4567
But the call to recvfrom is blocking indefinitely.
If I change it to this:
socket = Socket.new(
Socket::PF_INET,
Socket::SOCK_DGRAM,
Socket::IPPROTO_UDP
)
It of course works because this is the system-level way to accept UDP packets.
How can I receive UDP packets on a raw socket?
To be clear: I want to handle the actual UDP protocol (decoding the datagram & doing a checksum) myself!

Raw sockets work at IP level. You cannot bind a raw socket to a port. You can bind to a local address with bind or to an interface by setting the proper socket options (I don't know how to do it in Ruby, in C you call setsockopt. Instead of IPPROTO_RAW you should use IPPROTO_UDP in protocol.You will receive all UDP datagrams received on that IP address or that interface if the socket is bound to it.

Related

How to pick up IP packets and inject on different VM and interface

I have been trying to solve these 2 problems, but without success.
I wonder if it's possible to remove specific packets from an interface with Gopacket or is it just for listening on the wire? For example when I send a UDP packet to a wrong port and then with Gopacket I correct it, it will send 2 packets, 1 to the wrong port and 1 to the correct one. Is there a way to discard/drop the wrong packet with Gopacket?
What I am trying to do, is to pick up all packets that are sent by a client over IP and then encapsulate each packet as a payload in another protocol X and send to the remote host which will receive on protocol X, get the payload and send it on its interface to reach the server over IP again. (IP (Client) -> Protocol X (Sniffer 1) -> Protocol X (Sniffer 2) -> IP (Server))
I have verified that the packet which Sniffer 1 picks up from the Client's interface is the same which arrives at Sniffer 2, but the problem is when Sniffer 2 injects it on the Server's interface. I can't see that packet with tcpdump or any other tool. The packet is injected with this command:
if handle, err := pcap.OpenLive("enp0s8", 1600, true, 100); err != nil {
panic(err)
} else {
err = handle.WritePacketData(packet.Data())
}
If the Protocol X part is avoided, then the server will receive messages from client, but with Protocol X it is not possible.
Thanks in advance!
According to the Documentation
Package pcap allows users of gopacket to read packets off the wire or
from pcap files.
To discard packages, you will need to be able to intercept them. Depending on how generic you want to solve this problem, you probably need to hook into the kernel. I recommend looking into iptables and netfilters.
I found some VPN that are written in go, maybe look into how they are built, as you want to do something similar (tunnelling of packets).

Why can a socket connect() to its own ephemeral port?

I can reliably get a Winsock socket to connect() to itself if I connect to localhost with a port in the range of automatically assigned ephemeral ports (5000–65534). Specifically, Windows appears to have a system-wide rolling port number which is the next port that it will try to assign as a local port number for a client socket. If I create sockets until the assigned number is just below my target port number, and then repeatedly create a socket and attempt to connect to that port number, I can usually get the socket to connect to itself.
I first got it to happen in an application that repeatedly tries to connect to a certain port on localhost, and when the service is not listening it very rarely successfully establishes a connection and receives the message that it initially sent (which happens to be a Redis PING command).
An example, in Python (run with nothing listening to the target port):
import socket
TARGET_PORT = 49400
def mksocket():
return socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
while True:
sock = mksocket()
sock.bind(('127.0.0.1', 0))
host, port = sock.getsockname()
if port > TARGET_PORT - 10 and port < TARGET_PORT:
break
print port
while port < TARGET_PORT:
sock = mksocket()
err = None
try:
sock.connect(('127.0.0.1', TARGET_PORT))
except socket.error, e:
err = e
host, port = sock.getsockname()
if err:
print 'Unable to connect to port %d, used local port %d: %s' % (TARGET_PORT, port, err)
else:
print 'Connected to port %d, used local port %d' (TARGET_PORT, port)
On my Mac machine, this eventually terminates with Unable to connect to port 49400, used local port 49400. On my Windows 7 machine, a connection is successfully established and it prints Connected to port 49400, used local port 49400. The resulting socket receives any data that is sent to it.
Is this a bug in Winsock? Is this a bug in my code?
Edit: Here is a screenshot of TcpView with the offending connection shown:
This appears to be a 'simultaneous initiation' as described in #3.4 of RFC 793. See Figure 8. Note that neither side is in state LISTEN at any stage. In your case, both ends are the same: that would cause it to work exactly as described in the RFC.
It is a logic bug in your code.
First off, only newer versions of Windows use 5000–65534 as ephemeral ports. Older versions used 1025-5000 instead.
You are creating multiple sockets that are explicitly bound to random ephemeral ports until you have bound a socket that is within 10 ports less than your target port. However, if any of those sockets happen to actually bind to the actual target port, you ignore that and keep looping. So you may or may end up with a socket that is bound to the target port, and you may or may not end up with a final port value that is actually less than the target port.
After that, if port happens to be less than your target port (which is not guaranteed), you are then creating more sockets that are implicitly bound to different random available ephemeral ports when calling connect() (it does an implicit bind() internally if bind() has not been called yet), none of which will be the same ephemeral ports that you explicitly bound to since those ports are already in use and cannot be used again.
At no point do you have any given socket connecting from an ephemeral port to the same ephemeral port. And unless another app happens to have bound itself to your target port and is actively listening on that port, then there is no way that connect() can be successfully connecting to the target port on any of the sockets you create, since none of them are in the listening state. And getsockname() is not valid on an unbound socket, and a connecting socket is not guaranteed to be bound if connect() fails. So the symptoms you think are happening are actually physically impossible given the code you have shown. Your logging is simply making the wrong assumptions and thus is logging the wrong things, giving you a false state of being.
Try something more like this instead, and you will see what the real ports are:
import socket
TARGET_PORT = 49400
def mksocket():
return socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
while True:
sock = mksocket()
sock.bind(('127.0.0.1', 0))
host, port = sock.getsockname()
print 'Bound to local port %d' % (port)
if port > TARGET_PORT - 10 and port < TARGET_PORT:
break
if port >= TARGET_PORT:
print 'Bound port %d exceeded target port %d' % (port, TARGET_PORT)
else:
while port < TARGET_PORT:
sock = mksocket()
# connect() would do this internal anyway, so this is just to ensure a port is available for logging even if connect() fails
sock.bind(('127.0.0.1', 0))
err = None
try:
sock.connect(('127.0.0.1', TARGET_PORT))
except socket.error, e:
err = e
host, port = sock.getsockname()
if err:
print 'Unable to connect to port %d using local port %d' % (TARGET_PORT, port)
else:
print 'Connected to port %d using local port %d' % (TARGET_PORT, port)

Sending UDP Packet to NTP to get a packet back (in ruby)

What I need to do:
Send a packet to pool.ntp.org (and I am assuming I get a packet back automatically).
The following just does not work for me. I have no solid idea of what I am doing, so for now I would be satisfied if I could reach the address and get the packet back. The code below just hangs. Any and all help would be appreciated.
require 'socket'
sock = UDPSocket.new
sock.connect("pool.ntp.org", 123)
sock.recvfrom(10)
The documentation on UDPSocket states:
connect(host, port)
Connects udpsocket to host:port.
This makes possible to send without destination address.
That means that you may use send(mesg, flags) form of the send, nothing more. You need to send a request message to the NTP server to get a reply
You can see the NTPv4 protocol specification here: RFC 5905

Ruby write byte to socket

How can I write a byte to a socket in ruby? I specifically mean how can I write something like 0x02 to a socket. Thanks.
One way of sending integer byte values would be to use array.pack.
socket.write [0x02].pack("C")
Something like this ?
require 'socket' # Get sockets from stdlib
server = TCPServer.open(2000) # Socket to listen on port 2000
loop { # Servers run forever
client = server.accept # Wait for a client to connect
client.write("\x02")
client.close # Disconnect from the client
}

how do I get the IP of incoming ICMP due to UDP-send to dead client in Ruby?

so.. I'm doing a small multiplayer game with blocking UDP and IO.select. To my problem.. (In the server) reading from a UDP socket (packet, sender = #socket.recvfrom(1000)) which have just sent a packet to a dead client results in a ICMP unreachable (and exception Errno::ECONNRESET in ruby). The problem is that I can't find any way whatsoever to extract the IP of that ICMP.. so I can clean out that dead client.
Anyone know how to achieve this?
thanks
You'll need to call recvmsg for the socket, and pass MSG_ERRQUEUE as the flag.
The original destination address of the datagram that caused the error is supplied via msg_name.
It's worth noting that the source IP address of the ICMP packet will not always be the same address as your client. Any router that handles packets for this connection could be the source, and the payload of the ICMP packet would contain the IP header + the first 8 bytes of the packet it relates to.

Resources