Linux: How to Send a Whole Packet to a Specific Port on Another Host

Linux: how to send TCP packet from specific port?

If you are using raw sockets, then just fill in the correct TCP source port in the packet header.

If, instead, you are using the TCP socket interface (socket(), connect() and friends), then you can set the source port by calling the bind() system call for the client socket - exactly as you would to set the listening port for the server socket.

forwarding packets to service in same host without using loopback network

I propose the following solution:

  • store packets in the application and return verdict NF_DROP
  • re-inject packets into the network stack using RAW sockets
  • tag concatenated UDP packets with a DSCP (see IP packet format)
  • in iptables, add a rule to match on this DSCP (--dscp) and ACCEPT the packet directly, without it passing through your netfilter application

If your provider already tags some packets with DSCP, you can add some iptables rules to clear them, like:

iptables -t mangle -A INPUT -j DSCP --set-dscp 0

I hope this solves your use-case.

How can I use the same UDP socket both for sending and receiving packets? What am I missing in this code?

Although it is not clear from the question itself, your comments seem to indicate that you are not bind()ing an address to the socket, as @Someprogrammerdude inferred. In that case, it is important to understand that bind()ing serves a different and largely orthogonal purpose to that served by the SO_BINDTODEVICE socket option, the use of "BIND" in the option name notwithstanding.

The bind() function is about associating the socket with an address, which for TCP and UDP includes a port number. The SO_BINDTODEVICE is about limiting the socket to data passing through a particular device. Although in practice, there is usually a one-to-one mapping between IP addresses and network interfaces,

  1. The POSIX system interfaces are not specific to the IP protocol suite, and they take care to avoid assuming that address families all have characteristics similar to those of IP.

  2. Even for IP, it is possible for one network interface to have multiple addresses.

  3. For IP in particular, you in any case need to associate your socket with a port before the system will accept inbound traffic at that port. The socket option does not do that, nor does it directly even associate your socket with an IP address. That's the role of bind().

In comments you ask

do I always need to use different struct sockaddr_in, one for bind and
one for sendto? Can't I obtain the same result using only one
structure?

You can reuse the socket address structure, but be well aware that its contents need to be different for bind() than for sendto(). The former requires a local address to bind to, whereas the latter requires the remote address to which the message should be dispatched. I think it's a bit cleaner to use separate objects for those distinct purposes, but it's not necessary.

As for letting the client choose its own port, as I said in comments, that's the usual mode of operation for UDP. So usual, in fact, that the system will take care of it for you on the first call to sendto() if you have not already bound the socket. You are already using recvfrom(), by which the server (and the client, too) can obtain the address of the peer that sent each message. You should be able to feed that address object back to sendto() to send a response. Thus, it should be as easy as ensuring that the server bind()s in order to listen to a well-known port number, but the client does not, in order to use a port number assigned automatically by the system.

Send UDP packet with fixed source port number using getaddrinfo and bind

The server then responds to that port number. Yes, this is unusual

There is nothing unusual about that. This is how most UDP servers are meant to work. They always respond to the sender's port. They have no concept whether that port is fixed or ephemeral, that is for the sender to decide. Unless a particular protocol dictates that responses are to be sent to a different port, which is not common.

All my searches online point to using bind()

Correct, that is what you need in this situation.

but that code is usually on the server side.

There is nothing preventing a client from using bind().

I haven't found a way to bind on the client side using the modern getaddrinfo() method.

It is the exact same as on the server side, except that you have to bind to a specific IP address, you can't bind to 0.0.0.0 or ::0 like you can with a server socket.

I tried to add a bind() right after the socket() setup but that wouldn't work

Yes, it does. The problem is that you are using the SAME IP address for both binding and sending, and that will not work. You need to bind to the CLIENT's IP address and then send to the SERVER's IP address.

because p is a server-side structure (derived from the hints structure that uses the server IP address)

You are misusing p. You can't bind() a client socket to the server's IP address (you need to use connect() for that instead). You need to bind() a client socket to an IP address that is local to the client's machine. Just like you have to bind() a server socket to an IP address that is local to the server machine.

Remember, a socket is associated with a pair of IP addresses. bind() establishes the socket's LOCAL IP address. connect() establishes the socket's REMOTE IP address.

I want to do this in a way that will work for both IPv4 and IPv6.

You can't create a single client socket for both protocols. You need separate sockets for each protocol (on the server side, you can create a single socket for both protocols, if your platform supports dual-stack sockets).

I've seen other examples whereby a local/source sockaddr_in structure is filled out with the client's information and that is used in the bind, but those are IPv4 or IPv6 specific.

Yes, because you will be sending a packet using EITHER IPv4 OR IPv6, you can't send a packet using both protocols at the same time (a dual-stack socket can receive packets from either protocol, though).

Can someone please show me how to properly update the talker.c code to sendto() and recvfrom() a UDP server using a fixed source port number . Assume that the server is immutable

Try something like this:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdbool.h>

#define LOCALPORT "4950" // the port users will be sending from
#define SERVERPORT "4950" // the port users will be connecting to
#define MAXBUFLEN 65535

int main(int argc, char *argv[])
{
int sockfd;
struct addrinfo hints, *myinfo, *servinfo, *pserv, *plocal;
int rv;
int numbytes;
char buf[MAXBUFLEN];
char ipstr[INET6_ADDRSTRLEN];
fd_set readfds;
struct timeval tv;
bool stop = false;

if (argc < 3) {
fprintf(stderr, "usage: talker destaddr message [localaddr]\n");
return 1;
}

// get all of the server addresses
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;

if ((rv = getaddrinfo(argv[1], SERVERPORT, &hints, &servinfo)) != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
return 2;
}

// loop through all the server addresses
for(pserv = servinfo; (pserv != NULL) && (!stop); pserv = pserv->ai_next) {

memset(ipstr, 0, sizeof(ipstr));
switch (pserv->ai_family)
{
case AF_INET:
inet_ntop(AF_INET, &(((struct sockaddr_in*)pserv->ai_addr)->sin_addr), ipstr, INET_ADDRSTRLEN);
break;
case AF_INET6:
inet_ntop(AF_INET6, &(((struct sockaddr_in6*)pserv->ai_addr)->sin6_addr), ipstr, INET6_ADDRSTRLEN);
break;
}

printf("talker: trying to send message to %s\n", ipstr);

// get all of the matching local addresses
memset(&hints, 0, sizeof hints);
hints.ai_family = pserv->ai_family;
hints.ai_socktype = pserv->ai_socktype;
hints.ai_protocol = pserv->ai_protocol;

if ((rv = getaddrinfo(argc > 3 ? argv[3] : NULL, LOCALPORT, &hints, &myinfo)) != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
continue;
}

// loop through all the local addresses, sending the
// message from each one until a reply is received
for(plocal = myinfo; (plocal != NULL) && (!stop); plocal = plocal->ai_next) {

if ((sockfd = socket(plocal->ai_family, plocal->ai_socktype, plocal->ai_protocol)) == -1) {
perror("socket");
continue;
}

memset(ipstr, 0, sizeof(ipstr));
switch (plocal->ai_family)
{
case AF_INET:
inet_ntop(AF_INET, &(((struct sockaddr_in*)plocal->ai_addr)->sin_addr), ipstr, INET_ADDRSTRLEN);
break;
case AF_INET6:
inet_ntop(AF_INET6, &(((struct sockaddr_in6*)plocal->ai_addr)->sin6_addr), ipstr, INET6_ADDRSTRLEN);
break;
}

printf("talker: binding to %s\n", ipstr);

if (bind(sockfd, plocal->ai_addr, plocal->ai_addrlen) == -1) {
perror("bind");
close(sockfd);
continue;
}

// make sure this server address is the only one we talk to
if (connect(sockfd, pserv->ai_addr, pserv->ai_addrlen) == -1) {
perror("connect");
close(sockfd);
continue;
}

if ((numbytes = send(sockfd, argv[2], strlen(argv[2]), 0)) == -1) {
perror("send");
close(sockfd);
continue;
}

printf("talker: sent %d bytes\n", numbytes);

FD_ZERO(&readfds);
FD_SET(sockfd, &readfds);

tv.tv_sec = 5;
tv.tv_usec = 0;

rv = select(sockfd+1, &readfds, NULL, NULL, &tv);
if (rv == -1)
{
perror("select");
close(sockfd);
continue;
}

if (rv == 0)
{
printf("talker: no reply for 5 seconds\n");
close(sockfd);
continue;
}

if ((numbytes = recv(sockfd, buf, MAXBUFLEN, 0)) == -1)
{
perror("recv");
close(sockfd);
continue;
}

printf("talker: received %d bytes\n", numbytes);

close(sockfd);

stop = true;
break;
}

freeaddrinfo(myinfo);
}

freeaddrinfo(servinfo);

close(sockfd);

if (!stop) {
fprintf(stderr, "talker: failed to communicate with server\n");
return 3;
}

return 0;
}

Program does not send UDP package on specified port

The problem is how you're setting the port:

broadcastAddr.sin_port   = port;

The IP address and port number stored in a struct sockaddr_in must both be in network byte order which is big-endian byte ordering, i.e. most significant byte first. Your machine apparently uses little-endian byte ordering, i.e. least significant byte first, so by assigning the port number directly to the sin_port member that conversion is not being done.

This is more apparent if you look at the hexadecimal representation of the expected and actual port numbers:

  • 10000d = 2710h, 4135d = 1027h
  • 15000d = 3a98h, 38970 = 983ah

You need to call the htons() function, which converts a 16-bit value from host byte order to network byte order:

broadcastAddr.sin_port   = htons(port);


Related Topics



Leave a reply



Submit