5 System Calls or Bust

System Calls or Bust

This section deals with system calls to access network functionality of a box or computer that supports sockets API. These calls are handled by kernel. Problem occurs in determining what order should we call these things in.

getaddrinfo()

This is a real helpful function with a lots of option. First of all its helps set up the structs we need. Documentation here.

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

int getaddrinfo(const char *node,     // e.g. "www.example.com" or IP
                const char *service,  // e.g. "http" or port number
                const struct addrinfo *hints,
                struct addrinfo **res);

It takes three input parameters and returns a pointer to a linked-list res. node is the host name to connect to, or an IP address. service, can be a port number or name of a particular service as mentioned in THE IANA Port List, you can also check first section of etc/services. hints parameter points to a struct addrinfo that we've already filled with relevant information. It returns 0 if it succeeds and other non-zero integers if it fails. Example Code that listens to port "3490".

int status;
struct addrinfo hints;
struct addrinfo *servinfo;  // will point to the results

memset(&hints, 0, sizeof hints); // make sure the struct is empty
hints.ai_family = AF_UNSPEC;     // don't care IPv4 or IPv6
hints.ai_socktype = SOCK_STREAM; // TCP stream sockets
hints.ai_flags = AI_PASSIVE;     // fill in my IP for me

if ((status = getaddrinfo(NULL, "3490", &hints, &servinfo)) != 0) {
    fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
    exit(1);
}

// servinfo now points to a linked list of 1 or more struct addrinfos

// ... do everything until you don't need servinfo anymore ....

freeaddrinfo(servinfo); // free the linked-list

AF_UNSPEC here means it is IPv agnostic. AI_PASSIVE flag tells getaddrinfo() to assign the address of local host to the socket structure1. If there is an error, we can print it using gai_strerror() which takes status code as parameter and returns an string. serverinfo pointer gets allocated if everything works alright, and we can later free that using freeaddrinfo(). Now what if instead of local address we wanted to connect to "www.example.com"?(not actually connect but set up struct for it) Then we can use

int status;
struct addrinfo hints;
struct addrinfo *servinfo;  // will point to the results

memset(&hints, 0, sizeof hints); // make sure the struct is empty
hints.ai_family = AF_UNSPEC;     // don't care IPv4 or IPv6
hints.ai_socktype = SOCK_STREAM; // TCP stream sockets

// get ready to connect
status = getaddrinfo("www.example.net", "3490", &hints, &servinfo);

// servinfo now points to a linked list of 1 or more struct addrinfos

// etc.

Now for example this code will show IP address of whatever host we write in.

/*
** showip.c -- show IP addresses for a host given on the command line
*/

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

int main(int argc, char *argv[])
{
    struct addrinfo hints, *res, *p;
    int status;
    char ipstr[INET6_ADDRSTRLEN];

    if (argc != 2) {
        fprintf(stderr,"usage: showip hostname\n");
        return 1;
    }

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC; // AF_INET or AF_INET6 to force version
    hints.ai_socktype = SOCK_STREAM;

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

    printf("IP addresses for %s:\n\n", argv[1]);

    for(p = res;p != NULL; p = p->ai_next) {
        void *addr;
        char *ipver;

        // get the pointer to the address itself,
        // different fields in IPv4 and IPv6:
        if (p->ai_family == AF_INET) { // IPv4
            struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
            addr = &(ipv4->sin_addr);
            ipver = "IPv4";
        } else { // IPv6
            struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
            addr = &(ipv6->sin6_addr);
            ipver = "IPv6";
        }

        // convert the IP to a string and print it:
        inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr);
        printf("  %s: %s\n", ipver, ipstr);
    }

    freeaddrinfo(res); // free the linked list

    return 0;
}

socket()

Socket system call looks like:

#include <sys/types.h>
    #include <sys/socket.h>
    
    int socket(int domain, int type, int protocol); 

These arguments allow us to tell what kind of socket we want, IPv4 or IPv6(using PF_INET or PF_INET6), steam or datagram and TCP or UDP.

What you really want to do is use the values from the results of the call to getaddrinfo(), and feed them into socket() directly like this:

int s;
struct addrinfo hints, *res;

// do the lookup
// [pretend we already filled out the "hints" struct]
getaddrinfo("www.example.com", "http", &hints, &res);

// again, you should do error-checking on getaddrinfo(), and walk
// the "res" linked list looking for valid entries instead of just
// assuming the first one is good (like many of these examples do).
// See the section on client/server for real examples.

s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

socket() will return us a socket descriptor(or an error as -1) that we can further use in system calls. Global variable errno is set to the error's value.

Bind()

This isn't that important because we have listen() and connect() but still here we go

#include <sys/types.h>
#include <sys/socket.h>

int bind(int sockfd, struct sockaddr *my_addr, int addrlen);

sockfd is the socket file descriptor, my_addris a pointer to a sockaddr that contains information about our address(port and IP address), and at last addrlen is the length in bytes of that address. Whew. That's a bit to absorb in one chunk. Let's have an example that binds the socket to the host the program is running on, port 3490:

struct addrinfo hints, *res;
int sockfd;

// first, load up address structs with getaddrinfo():

memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;  // use IPv4 or IPv6, whichever
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;     // fill in my IP for me

getaddrinfo(NULL, "3490", &hints, &res);

// make a socket:

sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

// bind it to the port we passed in to getaddrinfo():

bind(sockfd, res->ai_addr, res->ai_addrlen);

AI_PASSIVE can be changed to hosts of our choice. Bind returns -1 on error and sets errno for error's value. Port below 1024 are reserved, so only choose above it. If port given to bind() is already in use we can add this code allowing it to reuse the port.

setsockopt

Sets a socket for use

int yes=1;
//char yes='1'; // Solaris people use this

// lose the pesky "Address already in use" error message
if (setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof yes) == -1) {
    perror("setsockopt");
    exit(1);
} 

If we only care about remote machine then we can use coneect(), it will find any local unused port and connect using it. And speaking of connect() ## Connect() The connect() call is as follows:

#include <sys/types.h>
#include <sys/socket.h>

int connect(int sockfd, struct sockaddr *serv_addr, int addrlen); 

sockfd (again) is our socket file descriptor returned by socket(), serv_addr is struct sockaddr containing the destination port and IP address, addrlen is the length in bytes of the server address structure. All this info can be gleaned from the result of the getaddrinfo(). Suppose we have to make a socket connection to "www.example.com", port 3490.

struct addrinfo hints, *res; int sockfd;

// first, load up address structs with getaddrinfo():

memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;

getaddrinfo("www.example.com", "3490", &hints, &res);

// make a socket:

sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

// connect!

connect(sockfd, res->ai_addr, res->ai_addrlen);

getaddrinfo() is superman of a function. We did not call bind because we dont care abut binding our application to a specific local port.

listen()

What if instead of connecting to remote connection, We are the remote connection listening to oncoming connections? This is a two step process, first we call listen(), then we accept() those connections. listen() call is simple

int listen(int sockfd, int backlog); 

sockfd=(again again) is socket file descriptor, =backlog is the number of connections allowed on the incoming queue. Incoming connections wait in queue until we accept() them and this is the limit on how many can queue up. As always listen() returns -1 on error.\\ This time we will need to call bind() so that the server can bind to a specific port(we will need to tell user which port to connect to).

getaddrinfo();
socket();
bind();
listen();
/* accept() goes here */ 

and now

accept()

When someone will try to connect() to our machine which is listen()=ing for connections. These connection requests will be queued, waiting for =accept().We tell accept() to get pending connection. it will return a brand new file socket descriptor.

#include <sys/types.h>
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 

in here sockfd is the socket descriptor function is listen()=ing to.=addr will usually point to a local sockaddr-storage, where information about the incoming connection will go(and which host is calling us from which port). addrlen should be set to sizeof(struct sockaddr_storage) before its address is passed to the accent. accept will not put more bytes than stated in addrlen and if it puts fewer it will change the value of addrlen.

#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

#define MYPORT "3490"  // the port users will be connecting to
#define BACKLOG 10     // how many pending connections queue will hold

int main(void)
{
    struct sockaddr_storage their_addr;
    socklen_t addr_size;
    struct addrinfo hints, *res;
    int sockfd, new_fd;

    // !! don't forget your error checking for these calls !!

    // first, load up address structs with getaddrinfo():

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;  // use IPv4 or IPv6, whichever
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;     // fill in my IP for me

    getaddrinfo(NULL, MYPORT, &hints, &res);

    // make a socket, bind it, and listen on it:

    sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    bind(sockfd, res->ai_addr, res->ai_addrlen);
    listen(sockfd, BACKLOG);

    // now accept an incoming connection:

    addr_size = sizeof their_addr;
    new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &addr_size);

    // ready to communicate on socket descriptor new_fd!

send() and recv()

These two functions are used to communicate with only stream sockets2. send() call looks like c++ int send(int sockfd, const void *msg, int len, int flags); socfd is the file descriptor(we get this from either socket() call or accept()).=msg= is pointer to the data we want to send and len is the length of data in bytes. flags is..well flags. Set it to 0. send() returns bytes sent(can be used to send remaing bytes later). Example code:

char *msg = "Beej was here!";
int len, bytes_sent;
.
.
len = strlen(msg);
bytes_sent = send(sockfd, msg, len, 0);
.
.
. 

recv() is similar. Call sounds like

``` c++ int recv(int sockfd, void *buf, int len, int flags); ```

buf is the buffer to read information into. Len is the maximum length of the buffer. Flags are set to 0. It returns bytes it read into buffer. recv() can also return 0, meaning remote has closed the connection.

sendto() and recvfrom()

Since datagrams arent connected to a remote host, we simply enter destination address. c++ int sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, socklen_t tolen); to is a pointer to sockaddrr which contains the destination IP address and port. tolen is set to sizeof *to or sizeof(struct sockaddr_storage). When sendto()=returns,=fromlen contains the length of address actually stored in from.\\ recvfrom() returns number of bytes received. So why use sockadrr_storage instead of struct_sockaddr? because it is short3. ## close() and shutdown() We can close connection on our socket descriptor using close(sockfd), this will prevent any more read and writes to the socket. Or we can even control how exactly socket descriptor closes using shutdown(int sockfd, int how).

howEffect
0receives are disallowed
1sends are disallowed
2both are disallowed(like close()

getpeername()

getpeername() call seems like

int getpeername(int sockfd, struct sockaddr *addr, int *addrlen); 

addr is a pointer to a sockaddr=(ot =sockaddr_in) that will hold the information about the other side of the connection. And addrlen is set to sizeof *addr or sizeof(struct sockaddr().\\ inet_ntop(), getnameinfo(), or gethostbyaddr() to print or get more information. ## gethostname() It returns the name of the host machine(machine program is running on) name which can later be used with gethostbyname() to detrmie current IP address of local machine..

#include <unistd.h>

int gethostname(char *hostname, size_t size); 

Footnotes:

1

#+BEGINQUOTE

If the AIPASSIVE flag is specified in hints.aiflags, and node is NULL, then the returned socket addresses will be suitable for bind(2)ing a socket that will accept(2) connections. The returned socket address will contain the "wildcard address" (INADDRANY for IPv4 addresses, IN6ADDRANYINIT for IPv6 address). The wildcard address is used by applications (typically servers) that intend to accept connections on any of the host's network addresses. If node is not NULL, then the AIPASSIVE flag is ignored.\\ for information about more flags #+ENDQUOTE

2

for datagrams we use senndto() and recvfrom()

3

if you connect() a datagram socket, you can then simply use send() and recv() for all your transactions. The socket itself is still a datagram socket and the packets still use UDP, but the socket interface will automatically add the destination and source information for you.