Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Connect to port using TCP with C

Tags:

c

tcp

sockets

I'm 99% new to sockets and any sort of network programming, so please bear with me.

I am aiming to connect to a port (2111 in this case) on my local machine (192.168.0.1). From there, I'm planning on sending and receiving basic information, but that's for another day.

I've currently tried this:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char **argv)
{
    int sd;
    int port;
    int start;
    int end;
    int rval;
    struct hostent *hostaddr;
    struct sockaddr_in servaddr;

    start = 2111;
    end   = 2112;
    for(port = start; port <= end; port++)
    {
        sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
        if(sd == -1)
        {
            perror("Socket()\n");
            return (errno);
        }

        memset(&servaddr, 0, sizeof(servaddr));

        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(port);

        hostaddr = gethostbyname("192.168.0.1");

        memcpy(&servaddr.sin_addr, hostaddr->h_addr, hostaddr->h_length);

        rval = connect(sd, (struct sockaddr *)&servaddr, sizeof(servaddr));
        if(rval == -1)
        {
            printf("Port %d is closed\n", port);
            close(sd);
        }
        else printf("Port %d is open\n", port);

        close(sd);
    }

    return 0;
}

However, my connect() call hangs for about 90 seconds, then returns -1.

The device is directly connected to my Mac Mini's ethernet port and the manufacturer has confirmed that the port is 2111 or 2112.

What am I doing wrong? Also, can it be in the ELI5 (explain like I'm 5) format? I'm much better off with examples.

like image 871
RileyE Avatar asked Mar 20 '23 01:03

RileyE


1 Answers

When you call connect() to connect to a host, your computer sends a SYN packet to begin the three-way handshake of the TCP connection. From here, there are 3 possible scenarios:

  1. If the peer is listening on that port, it responds with a SYN+ACK packet, your computer responds with a final ACK, and the connection is established—connect() returns successfully.
  2. If the peer is not listening on that port, it responds with an ICMP packet with a type and code indicating that the port is closed, which causes your connect() call to fail almost immediately with the error ECONNREFUSED (connection refused). Under normal circumstances, this takes 1 network round-trip time (RTT) to happen, which is typically tens or hundreds if milliseconds.
  3. If your computer never receives either an appropriate SYN+ACK TCP packet or connection refused ICMP packet, it assumes that its original SYN packet got dropped by the network somewhere and will try to resend the SYN packet several times until it gets one of those packets back or it hits an OS-dependent timeout, at which point the connect() call fails with ETIMEDOUT. This is typically 1–2 minutes, depending on the OS and its TCP settings.

You're clearly hitting case #3. This can be caused by a few different issues:

  1. Your original SYN packets were getting lost in the network, possibly due to a faulty link, overloaded router, or firewall
  2. The peer's SYN+ACK or ICMP response packets were getting lost in the network, possibly due to a faulty link, overloaded router, or firewall
  3. The destination address may be unroutable/unreachable
  4. The peer may be failing to properly respond at all with a SYN+ACK or ICMP packet

If you're directly connecting to the device over ethernet, than that rules out #1 and #2. #4 is possible, but I think #3 is the most likely explanation.

A brief aside on packet routing

Your computer has multiple network interfaces—ethernet (sometimes multiple ethernet interfaces), Wi-Fi, the loopback device, VPN tunnels, etc. Whenever you create a socket, it has to be bound to one or more particular network interfaces in order for the OS to know which NIC to actually send the packet through. For listen sockets for servers, you typically bind to all network interfaces (to listen for connections on all of them), but you can also bind to a particular network interface to only listen on that one.

For client sockets, when you connect them to other peers, you don't normally bind them to a particular interface. By default, your computer uses its internal routing tables along with the destination IP address to determine which network interface to use. For example, if you have a gateway machine with two NICs, one of which is connected to the public internet with IP 54.x.y.z and hte other of which is connected to an internal, private network with IP 192.168.1.1, then that machine will in all likelihood have routing tables that say "for packets destined to 192.168.0.0/16, use NIC 2, for all other packets, use NIC 1". If you want to bypass the routing tables, you can bind the socket to the network interface you want by calling bind() on the socket before the call to connect().

Putting it all together

So, what does that all mean for you?

First, make sure that 192.168.0.1 is in fact the correct destination address you should be connecting to. How is that address determined? Is your computer acting as a DHCP server to assign that address to the other host? Is that host using a static IP configuration?

Next, make sure that your routing tables are correct. If the other machine is assigning itself a static IP, chances are that your Mac isn't aware of how to route to that destination and is probably trying to route through the wrong interface. You can manually adjust the routes on Mac OS X with the route(8) utility, but these get reset every reboot; this blog post shows an example of using a startup item to automate adding the new route on startup. You'll want to use the IP address associated with the ethernet interface connected to the target host.

Alternatively, instead of using routing tables, you could call bind() on your socket before connect() to bind to the local address of the interface you want to use, but this won't work for other programs unless they also provide that functionality. For example, the curl(1) utility lets you pass the --interface <name> command line flag to direct it to bind to a particular interface.

like image 191
Adam Rosenfield Avatar answered Mar 29 '23 12:03

Adam Rosenfield