Friday, December 28, 2012

Simple TCP client server sockets application using IPv6 and IPv6

So here is a pair of programs demonstrating the use of sockets on Linux. The server program is run first. Then whenever the client program is run, it connects to the server on the specified port and they both exchange strings and terminate. TCP is used over IPv4.

Simple_tcpserver_ipv4.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

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

#include <arpa/inet.h>

void error(char *msg) {
    perror(msg);
    exit(1);
}

int main(int argc, char *argv[]) {
    int sockfd, newsockfd, portno;
    socklen_t clilen;
    char buffer[256];
    struct sockaddr_in serv_addr, cli_addr;
    int n;


    if (argc < 2) {
        fprintf(stderr, "Usage: %s \n", argv[0]);
        exit(0);
    }

    printf("\nIPv4 TCP Server Started...\n");

    //Sockets Layer Call: socket()    
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
        error("ERROR opening socket");
    bzero((char *) &serv_addr, sizeof(serv_addr));
    portno = atoi(argv[1]);

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(portno);

    
    //Sockets Layer Call: bind()
    if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
        error("ERROR on binding");

    //Sockets Layer Call: listen()
    listen(sockfd, 5);
    clilen = sizeof(cli_addr);
    
    //Sockets Layer Call: accept()
    newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
    if (newsockfd < 0)
        error("ERROR on accept");

    //Sockets Layer Call: inet_ntoa()

    printf("Incoming connection from client having IPv4 address: %s\n",    inet_ntoa(cli_addr.sin_addr));

    memset(buffer,0, 256);
    
    //Sockets Layer Call: recv()
    n = recv(newsockfd, buffer, 255, 0);
    if (n < 0)
        error("ERROR reading from socket");

    printf("Message from client: %s\n", buffer);

    //Sockets Layer Call: send()
    n = send(newsockfd, "Server got your message", 23+1, 0);
    if (n < 0)
        error("ERROR writing to socket");
    
    //Sockets Layer Call: close()
    close(sockfd);
    close(newsockfd);
    
    return 0;
}

Simple_tcpclient_ipv4.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

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

void error(char *msg) {
    perror(msg);
    exit(0);
}

int main(int argc, char *argv[]) {
    int sockfd, portno, n;
    struct sockaddr_in serv_addr;
    struct hostent *server;
    char buffer[256] = "This is a string from client!";

    if (argc < 3) {
        fprintf(stderr, "Usage: %s  \n", argv[0]);
        exit(0);
    }
    portno = atoi(argv[2]);

    printf("\nIPv4 TCP Client Started...\n");
    
    //Sockets Layer Call: socket()
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
        error("ERROR opening socket");

    //Sockets Layer Call: gethostbyname()
    server = gethostbyname(argv[1]);
    if (server == NULL) {
        fprintf(stderr, "ERROR, no such host\n");
        exit(0);
    }

    memset((char *) &serv_addr, 0, sizeof(serv_addr));

    serv_addr.sin_family = AF_INET;
    memmove((char *) &serv_addr.sin_addr.s_addr, (char *) server->h_addr, server->h_length);
    serv_addr.sin_port = htons(portno);

    //Sockets Layer Call: connect()
    if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
        error("ERROR connecting");

    
    //Sockets Layer Call: send()
    n = send(sockfd,buffer, strlen(buffer)+1, 0);
    if (n < 0)
        error("ERROR writing to socket");

    memset(buffer, 0, 256);
    
    //Sockets Layer Call: recv()
    n = recv(sockfd, buffer, 255, 0);
    if (n < 0)
        error("ERROR reading from socket");
    printf("Message from server: %s\n", buffer);

    //Sockets Layer Call: close()
    close(sockfd);
        
    return 0;
}
Makefile_tcpipv4:
all: 
    gcc Simple_tcpserver_ipv4.c -o Simple_tcpserver_ipv4.out
    gcc Simple_tcpclient_ipv4.c -o Simple_tcpclient_ipv4.out

clean:
    rm Simple_tcpserver_ipv4.out
    rm Simple_tcpclient_ipv4.out

Execution results on Ubuntu:



In the above screenshot, I have shown both the client and server applications running on the same PC and so while running the client application, we use the local loopback address 127.0.0.1.

Now if we need to port the above pair of applications from IPv4 to IPv6, we need to make subtle changes in the code. When such changes are made to the above pair of applications, they would look as below. Changes are highlighted in red.


Simple_tcpserver_ipv6.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

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

#include <arpa/inet.h>

void error(char *msg) {
    perror(msg);
    exit(1);
}

int main(int argc, char *argv[]) {
    int sockfd, newsockfd, portno;
    socklen_t clilen;
    char buffer[256];
    struct sockaddr_in6 serv_addr, cli_addr;
    int n;
    char client_addr_ipv6[100];

    if (argc < 2) {
        fprintf(stderr, "Usage: %s \n", argv[0]);
        exit(0);
    }

    printf("\nIPv6 TCP Server Started...\n");
    
    //Sockets Layer Call: socket()
    sockfd = socket(AF_INET6, SOCK_STREAM, 0);
    if (sockfd < 0)
        error("ERROR opening socket");

    bzero((char *) &serv_addr, sizeof(serv_addr));
    portno = atoi(argv[1]);
    serv_addr.sin6_flowinfo = 0;
    serv_addr.sin6_family = AF_INET6;
    serv_addr.sin6_addr = in6addr_any;
    serv_addr.sin6_port = htons(portno);

    
    //Sockets Layer Call: bind()
    if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
        error("ERROR on binding");

    //Sockets Layer Call: listen()
    listen(sockfd, 5);
    clilen = sizeof(cli_addr);
    
    //Sockets Layer Call: accept()
    newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
    if (newsockfd < 0)
        error("ERROR on accept");

    //Sockets Layer Call: inet_ntop()
    inet_ntop(AF_INET6, &(cli_addr.sin6_addr),client_addr_ipv6, 100);
    printf("Incoming connection from client having IPv6 address: %s\n",client_addr_ipv6);

    memset(buffer,0, 256);
    
    //Sockets Layer Call: recv()
    n = recv(newsockfd, buffer, 255, 0);
    if (n < 0)
        error("ERROR reading from socket");

    printf("Message from client: %s\n", buffer);

    //Sockets Layer Call: send()
    n = send(newsockfd, "Server got your message", 23+1, 0);
    if (n < 0)
        error("ERROR writing to socket");
    
    //Sockets Layer Call: close()
    close(sockfd);
    close(newsockfd);
    
    return 0;
}
 Simple_tcpclient_ipv6.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

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

void error(char *msg) {
    perror(msg);
    exit(0);
}

int main(int argc, char *argv[]) {
    int sockfd, portno, n;
    struct sockaddr_in6 serv_addr;
    struct hostent *server;
    char buffer[256] = "This is a string from client!";

    if (argc < 3) {
        fprintf(stderr, "Usage: %s  \n", argv[0]);
        exit(0);
    }
    portno = atoi(argv[2]);

    printf("\nIPv6 TCP Client Started...\n");
    
    //Sockets Layer Call: socket()
    sockfd = socket(AF_INET6, SOCK_STREAM, 0);
    if (sockfd < 0)
        error("ERROR opening socket");

    //Sockets Layer Call: gethostbyname2()
    server = gethostbyname2(argv[1],AF_INET6);
    if (server == NULL) {
        fprintf(stderr, "ERROR, no such host\n");
        exit(0);
    }

    memset((char *) &serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin6_flowinfo = 0;
    serv_addr.sin6_family = AF_INET6;
    memmove((char *) &serv_addr.sin6_addr.s6_addr, (char *) server->h_addr, server->h_length);
    serv_addr.sin6_port = htons(portno);

    //Sockets Layer Call: connect()
    if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
        error("ERROR connecting");

    
    //Sockets Layer Call: send()
    n = send(sockfd,buffer, strlen(buffer)+1, 0);
    if (n < 0)
        error("ERROR writing to socket");

    memset(buffer, 0, 256);
    
    //Sockets Layer Call: recv()
    n = recv(sockfd, buffer, 255, 0);
    if (n < 0)
        error("ERROR reading from socket");
    printf("Message from server: %s\n", buffer);

    //Sockets Layer Call: close()
    close(sockfd);
        
    return 0;
}
Makefile_tcpipv6:
all: 
    gcc Simple_tcpserver_ipv6.c -o Simple_tcpserver_ipv6.out
    gcc Simple_tcpclient_ipv6.c -o Simple_tcpclient_ipv6.out

clean:
    rm Simple_tcpserver_ipv6.out
    rm Simple_tcpclient_ipv6.out

Execution results on Ubuntu:




Source code(s):
Archive containing the above files can be downloaded from here.

Here is another version of the code which uses UDP instead of TCP. uses recvfrom() and sendto(). In this case only the client sends the string to server - the server does not send a string back.

Here is the source code for another pair of applications which implement a sort of chat server and its chat clients. Multiple chat clients can connect to a single chat server. Whatever string one client sends to the server, it is forwarded to all the clients. select() is used. The archive contains both IPv4 as well as IPv6 implementations.

Note: If you are going to be using link local IPv6 addresses with above sample applications, make sure to set serv_addr.sin6_scope_id = 0,1,2,3...(whatever is the interface number of your network adapter) in the client code. Read more here.

References:

22 comments:

sujinsr said...

Thanks.. it was nice tutorial.

How to mention ipv6 directly instead of localhost as ::1
"fe80::20c:29ff:fe61:6d65/64" This is my ipv6 address of my host.. i tried to run the client with the help of above mentioned address, it getting errror as network unreachable..

Tell me the format!!

Anurag Chugh said...

Hi,
Just use "fe80::20c:29ff:fe61:6d65".
Drop the "/64" which is the subnet info - its required only when configuring the ipv6 addreess using ip or ifconfig commands

Ivan Adame said...

how can use in a virtual machine and the host machine?

Anurag Chugh said...

@Ivan set your virtual machine to use the host adapter in the bridge mode. Bridge it to that ethernet/wifi interface of the host machine that you use to connect to the wifi router and hence the internet. The wifi router will assign two distinct ip addresses to the host machine and the guest virtual machine and they both will appear on the same logical LAN and hence would be able to communicated with each other.

To do this for VirtualBox:
https://www.virtualbox.org/manual/ch06.html#network_bridged

To do this for VMware:
http://www.vmware.com/support/ws5/doc/ws_net_configurations_bridged.html

bilal gill said...

hey how is it possible to handle both ipv4 and ipv6 addresses in the server coming from the client. Use gettaddress info with AF_UNSPEC?

Gonzalo NavĂ­a Aras said...

hi do you know how to do a dualstack server? that can handle both, ipv4 and ipv6 addresses in the server coming from the client.

Anurag Chugh said...

Hi, in the references section at the end of the post above, click on the link which says "Example: Accepting connections from both IPv6 and IPv4 clients" to know how to do exactly what u want. Its called "protocol agnostic" network coding.

shreyans said...

Hi,

I have been trying to use the IPv6 program with 2 systems (server running on one system and client on other). I every time I run the client, I get an Error in connection.


[root@localhost CPE_Project]# ./a.out fe80::6ef0:49ff:fe66:62ac 1234

IPv6 TCP Client Started...
ERROR connecting: Invalid argument

Anurag Chugh said...

@shreyans, have you tried running the client and server on the same machine first? try using ::1 as loopback address. then try to run the two programs on different computers connected by a cross LAN cable to ensure that the router does not mess things up. Please try assigning a different IPv6 address than fe80::6ef0:49ff:fe66:62ac as well.

shreyans said...

@Anurag, Yes, I have tried with client server running on the same machine with ::1 and it is working as shown in the output snapshot. I have also tried it with cross LAN and I am getting the same error. Then I connected 2 systems with a switch (without a router). In that case I got the error mentioned in my previous post. However, in the program, there is a memset at one place. When I commented the memset and then executed the program, I got a different error:

[root@localhost CPE_Project]# ./a.out fe80::6ef0:49ff:fe66:62ac 1234

IPv6 TCP Client Started...
ERROR connecting: Network is unreachable

Le Trung Thong said...

I have run you code and got the error as follows:

./client fe80::20c:29ff:fe51:f20c 8080

IPv6 TCP Client Started...
ERROR connecting: Invalid argument

Le Trung Thong said...

I try to use good command line with ./client 0000:0000:0000:0000:0000:0000:0000:0000 8080 but problem with ./client fe80:0000:0000:0000:020c:29ff:fe51:f20c 8080

IPv6 TCP Client Started...
ERROR connecting: Invalid argument

shreyans said...

@Thong, please comment the memset in the client code and re-execute the client. Check if the error disappears...

shreyans said...

Hi All, I have an update. the client server only communicate on the global IPv6 address starting with 2001::. Earlier, I was trying with the link local address which was giving me all sort of problems. But when I configured global IP address on both the nodes and tried, it was working fine. Hope this adds to IPv6 understanding.

Anurag Chugh said...

@Shreyans, thanks for that update.
I have been out of touch for a long time and wasn't able to recollect my experience while building native IPv6 applications.
But now thanks to your input, I do.
I remember now why added the note at the end of the blog post:

Note: If you are going to be using link local IPv6 addresses with above sample applications, make sure to set serv_addr.sin6_scope_id = 0,1,2,3...(whatever is the interface number of your network adapter) in the client code. Read more here.

I think to use the link local address, you need to specify the device as well. When that is not convenient (which is true in case of most portable application) you will have to resort to configuring global IPv6 address.

This is how I configured IPv6 address for a particular network device named eth0:
/sbin/ip -6 addr add fe80::aa11:22ff:fe33:4456/64 dev eth0

Richard Wicks said...

Can I make the following suggestion to add into your Simple_tcpclient_ipv6.c example? Add the following at the top of main():

int scope = 0;

if (argc >= 4) {
scope = atoi(argv[3]);
printf ("Scope set to %d\n", scope);
}

And add the following when you are setting up serv_addr:

serv_addr.sin6_scope_id = scope;

I got a list of my interfaces with interface # under Linux with "ip link" although I'm curious how to do that with C, without having to call "ip link". My ethernet is on interface 2 according to "ip link".

It's interesting that loopback is on interface 1 but a scope of 0 also works, and of course, loopback only works with :: not the ipv6 address assigned to eth0.

Anurag Chugh said...

@Richard that's a good idea. I think not specifying the scope might be causing the programs to fail for some people depending on their network / virtual machine setups. When I put up this post, I was just beginning to work with IPv6. I still don't know enough - need more real world experience with IPv6.

Richard Wicks said...

Here's some example code that will convert an interface name (as given by ifconfig under Linux) and convert it to a number suitable for using with serv_addr.sin6_scope_id

#include <sys/ioctl.h>
#include <net/if.h>
#include <string.h>

int interfaceNameToScope(int fd, const char *szName) {
struct ifreq ifreqVal;
int iRet = -1;

memset(&ifreqVal, 0, sizeof(ifreqVal));
strncpy (ifreqVal.ifr_name, szName, sizeof(ifreqVal.ifr_name));
if (ioctl (fd, SIOCGIFINDEX, &ifreqVal) == 0) {
iRet = ifreqVal.ifr_ifindex;
printf ("Interface index = %d\n", iRet);
}
else
perror ("ioctl");

return iRet;
}

You would call it with something like (if argv[3] was "eth0")

if (argc >= 4) {
scope = interfaceNameToScope (sockfd, argv[3]);
if (scope < 0) {
fprintf (stderr, "Invalid scope");
}
printf ("Scope set to %d\n", scope);
}

BTW - is there any way I can post stuff so that I can retain formatting?

Also, I'm happy to share my own tribulations with IPV6 as I hit them and fix them. When learning a new interface, I always write example code so I can divide my work between what I'm actually doing (implementing a stack) and the stuff it depends on (TCP/IPV6, drivers, etc).

I can share my example code as I go along.

Anurag Chugh said...

Richard,
Thank you helping improve this example. Whenever I go through examples on another person's blog, I take time to go through the comments to make sure I am upto date with all the insights that the readers of that blog might have contributed back.

I think blogger allows the use of some html tags so those can be used to format the code, otherwise one way might be to simple use notepad to replace tabs with 4 spaces before pasting the code here.

Thank you the insight on looking up the scope when interface is specified. I just remembered, I used to manually disable all interfaces when trying out the code above.

Richard Wicks said...

Well, I'm implementing TCP IPV6 servers in both UDP and TCP. Once I finish fleshing out the examples that I'm posting for my team, I'll be happy to share with you and you can do what you like with them. I can post them somewhere that you can access them.

This blog (at least in this area) ignores multiple spaces. See? The next line has 8 spaces in front of it:
9 is the column I began writing in.

And tabs in code are forbidden at my company, at my insistence!

My stuff is written for Linux, and the code is highly dependent on that platform in some of (probably all) my functions - but among the functions I am implementing are:

interface name -> scope id
scope id -> interface name
get all interface names / scopes (highly platform dependent)

Interestingly, I'm running into a problem, I cannot seem to get recvfrom() to get back a valid IPV6 address with the TCP/IPV6 client. Very odd - not really important as I already know the address I have sent to, but.. Odd.

Again, thank you for producing this example. I know IPV4 quite well, but this still would have taken me several days to get an IPV6 server going, if not over a week. This protocol is frustrating to me, seemingly over-designed and under-designed at the same time!

Anurag Chugh said...

Hi Richard,

Thanks for the updates, I have an idea.
If blog is too much of a hassle, and like you said, publishing/presenting code on the blog is an issue, what you can do is use create an account on github and use their gist service: https://gist.github.com/

I can update my blog to include links to your codes hosted on gist.
This way, the audience who come looking for basic IPv6 code, will be able to get access to everything in one place AND you contribution will stay attributed to you as well. In the future, as you play around with various OS APIs, you can keep using the gist service to simple share your code quick and fast.

Thank you for your kind words.

About recvfrom() - if memory serves correctly, even I ran to the same issue as you did, but like you I didn't care much about the issue, because even I did know the address of the client before hand :)

Anurag Chugh said...

Richard, another update,

I just published code for IPv4 Chat and Server applications:
http://www.electronicsfaq.com/2016/07/simple-chat-server-and-client.html

I hosted the code on gist and was able to embed them into the blog post
The original gists are here:
https://gist.github.com/lithiumhead/0e944417be2ec6ae28121b4eb12c6d6e

<a href="https://gist.github.com/lithiumhead/b6b5a50668f651c461fe3bdc906acc1e>https://gist.github.com/lithiumhead/b6b5a50668f651c461fe3bdc906acc1e</a>

As you can see, gist interface allows you to comment on the codes hosted on gist!!

Post a Comment