Unable to show all UPnP devices within the local network C++ - c

I am new to UPnP development and trying to discover all UPnP device within the local network, and I followed an example from the online resource, but my code will only keep looping at the first response. How could I get another response other than the first one, could I get some hints for this?
Example :
First Response from 192.168.xxx.123, and it will keeps printing the following result:
HTTP/1.1 200 OK
CACHE-CONTROL: max-age=1790
DATE: Thu, 01 Jan 2015 10:43:15 GMT
ST: uuid:4d696xxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
USN: uuid:4d696xxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
EXT:
SERVER: Linux 2.6 DLNADOC/1.50 UPnP/1.0 ReadyDLNA/1.0.26
LOCATION: http://192.168.xxx.123:xxxx/rootDesc.xml
Content-Length: 0
I checked in Wireshark, and I can see the other device [IP: 192.168.xxx.99] has given me a response, but I am not able to receive it in my code.
I also read a question on SO and used select in my code, but still cannot get it working.
Receiving response(s) from N number of clients in reply to a broadcast request over UDP
The code:
#include <QCoreApplication>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#define RESPONSE_BUFFER_LEN 1024
#define SSDP_MULTICAST "239.255.255.250"
#define SSDP_PORT 1900
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
int sock;
size_t ret;
unsigned int socklen;
struct sockaddr_in sockname;
struct sockaddr clientsock;
struct hostent *hostname;
char data[] =
"M-SEARCH * HTTP/1.1\r\n"
"HOST: 239.255.255.250:1900\r\n"
"MAN: \"ssdp:discover\"\r\n"
"ST: ssdp:all\r\n"
"MX: 120\r\n"
"\r\n";
char buffer[RESPONSE_BUFFER_LEN];
unsigned int len = RESPONSE_BUFFER_LEN;
fd_set fds;
struct timeval timeout;
hostname = gethostbyname(SSDP_MULTICAST);
hostname->h_addrtype = AF_INET;
if((sock = socket(PF_INET, SOCK_DGRAM, 0)) == -1)
{
printf("err: socket() failed");
return -1;
}
memset((char *)&sockname, 0, sizeof(struct sockaddr_in));
sockname.sin_family = AF_INET;
sockname.sin_port = htons(SSDP_PORT);
sockname.sin_addr.s_addr = *((unsigned long *)(hostname->h_addr_list[0]));
ret = sendto(sock, data, strlen(data), 0, (struct sockaddr *)&sockname,
sizeof(struct sockaddr_in));
if(ret != strlen(data))
{
printf("err:sendto");
return -1;
}
/* Get response */
FD_ZERO(&fds);
FD_SET(sock, &fds);
timeout.tv_sec = 5;
timeout.tv_usec = 5;
while(select(sock + 1, &fds, NULL, NULL, &timeout) > 0)
{
if(FD_ISSET(sock, &fds))
{
socklen = sizeof(clientsock);
if((len = recvfrom(sock, buffer, len, MSG_PEEK, &clientsock, &socklen)) == (size_t)-1)
{
printf("err: recvfrom");
return -1;
}
buffer[len] = '\0';
/* Check the HTTP response code */
if(strncmp(buffer, "HTTP/1.1 200 OK", 12) != 0)
{
printf("err: ssdp parsing ");
return -1;
}
printf(buffer);
}
else
{
printf("err: no ssdp answer");
}
}
//close(sock);
return a.exec();
}

You are using MSG_PEEK, which means to read the first message in the socket's receive buffer, but not remove it from the buffer.
Therefore every time you call recvfrom you get the first received message.
Change MSG_PEEK to 0 and then each call will read the first message that hasn't been read yet. (So the second call will read the second message, and so on)

Related

TCP socket hangs when reading response from custom HTTP request - C

I have the following code that I have written which is suppose to send a simple http request over a TCP socket, I get a response but as soon as I try to read in the loop it hangs, in the 2nd read operation (tried it manually)
if anyone has an idea of why this might fail I will appreciate it a lot
attached below is the entire code
I am running the program like this: ./http_client yahoo.com
I get this response text at first:
HTTP/1.1 301 Moved Permanently
Date: Sat, 06 Aug 2022 08:07:11 GMT
Connection: keep-alive
Server: ATS
Cache-Control: no-store, no-cache
Content-Type: text/html
Content-Language: en
X-Frame-Options: SAMEORIGIN
Location: https://www.yahoo.com/
Content-Length: 8
redirect
and then it hangs and closes the socket, it shouldn't hang at all, it should run and exit without a delay or anything
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
int main(int argc, char *argv[]) {
int sockfd;
struct sockaddr_in cli_name;
struct sockaddr_in *saddr;
char *hostname;
struct addrinfo *res;
int port = 80;
if (argc != 2) {
perror("Usage: establish tcp connection to: <hostname>\n");
exit(1);
}
hostname = argv[1];
printf("Client is alive and establishing socket connection %s.\n", hostname);
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("Error opening channel");
close(sockfd);
exit(1);
}
if (0 != getaddrinfo(hostname, NULL, NULL, &res)) {
fprintf(stderr, "Error in resolving hostname %s\n", hostname);
exit(1);
}
bzero(&cli_name, sizeof(cli_name));
cli_name.sin_family = AF_INET;
saddr = (struct sockaddr_in *) res->ai_addr;
cli_name.sin_addr.s_addr = inet_addr(inet_ntoa(saddr->sin_addr));
cli_name.sin_port = htons(port);
fflush(stdout);
if (connect(sockfd, (struct sockaddr *) &cli_name, sizeof(cli_name)) < 0) {
perror("Error establishing communications");
close(sockfd);
exit(1);
}
char header[100];
int cx;
char buf[2056];
size_t byte_count = 0;
size_t sent_byte_count = 0;
cx = snprintf(header, 100, "GET / HTTP/1.1\r\nHost: %s:%d\r\n\r\n", hostname, port);
size_t total = strlen(header);
size_t sent = 0;
do {
sent_byte_count = write(sockfd, header + sent, total - sent);
if (sent_byte_count < 0)
printf("ERROR writing message to socket");
if (sent_byte_count == 0)
break;
sent += sent_byte_count;
} while (sent < total);
memset(buf,0,sizeof(buf));
while ((byte_count = read(sockfd, buf, 2054)) > 0) {
buf[byte_count] = '\0';
printf("%s", buf); // <-- give printf() the actual data size
fflush(stdout);
}
printf("Exiting now.\n");
close(sockfd);
exit(0);
}

getaddrinfo() fails when attempting to download a stream

I'm trying to download a few seconds from a livestream using sockets.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> /* close() */
#include <sys/socket.h>
#include <netdb.h>
int main(void)
{
int sock;
char host[] = "http://141.138.89.176/fun-1-44-128";
char port[] = "80";
struct addrinfo hints, *res;
char message[] = "GET / HTTP/1.1\nHost: http://141.138.89.176/fun-1-44-128";
unsigned int i;
char buf[1024];
int bytes_read;
int status;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
status = getaddrinfo(host, port, &hints, &res);
if (status != 0) {
perror("getaddrinfo");
return 1;
}
sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (sock == -1) {
perror("socket");
return 1;
}
status = connect(sock, res->ai_addr, res->ai_addrlen);
if (status == -1) {
perror("connect");
return 1;
}
freeaddrinfo(res);
send(sock, message, strlen(message), 0);
do {
bytes_read = recv(sock, buf, 1024, 0);
if (bytes_read == -1) {
perror("recv");
}
else {
printf("%.*s", bytes_read, buf);
}
} while (bytes_read > 0);
close(sock);
return 0;
}
The code compiles, however when running, getaddrinfo() fails. I assume that this means that the host cannot be found.
This is my url: http://141.138.89.176/fun-1-44-128
It works in my browser so I don't know what's going on. Could anyone shed some light on the problem?
I tested your code on my environment and it's perfectly working so your code is surely okay. I recommend checking your compiler, maybe reinstall it.
EDIT: Okay I think I found the problem. It was that the host should only be the ip address, not the full link.
(also added a fix to the error reporting from getaddrinfo)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> /* close() */
#include <sys/socket.h>
#include <netdb.h>
int main(void)
{
int sock;
char host[] = "141.138.89.176";
char port[] = "80";
struct addrinfo hints, *res;
char message[] = "GET / HTTP/1.1\nHost: 141.138.89.176/fun-1-44-128";
unsigned int i;
char buf[1024];
int bytes_read;
int status;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
status = getaddrinfo(host, port, &hints, &res);
if (status != 0) {
printf("Code: %d\n", status);
printf("Message: %s\n", gai_strerror(status));
return 1;
}
...
OK I figured out the solution.
getaddrinfo() only accepts base host names, domain names or ip addresses, with no subdirectories.
This means that 192.168.0.4, www.site.com, localhost are all valid. 192.168.0.4/Search, www.site.com/Search, localhost/Search are not.
You also don't need to include the scheme. Sockets don't differentiate http from https, it's how requests are dealt with makes the difference.
You need to change host so that it only contains the IP address.
What you want is:
char host[] = "141.138.89.176";
You won't get that error now.
If you want to access a subdirectory you pass it as a GET request. It's actually the web server that handles file and directory stuff. The base IP stays the same.
First off your formatting is wrong. It should be \r\n and \r\n\n\n to finish. It should look like this: char message[] = "GET / HTTP/1.1\r\nHost: 141.138.89.176/fun-1-44-128\r\n\n\n";
Now if you try this, you will get a 400 error. this is because it's incorrectly written. Much like the above Host only accepts the base url/ip. See that / right after the GET? That's where you request any subdirectories/files you want. The / you have there now indicates that you want the top level dictionary.
It should look like this:
message[] = "GET /fun-1-44-128 HTTP/1.1\r\nHost: 141.138.89.176\r\n\n\n";

OpenSSL DTLS connection never establishes

I'm sorry for such a big block of code, but I'm so lost as to what is happening, I have no idea where the problem might be...
I am trying to get a VERY minimal dtls server going and I can't get the client and server to finish handshaking.
Here is my code:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <openssl/ssl.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <openssl/opensslv.h>
int generate_cookie(SSL *ssl, unsigned char *cookie, unsigned int *cookie_len)
{
cookie = "cookie";
cookie_len = 5;
return 1;
}
int verify_cookie(SSL *ssl, const unsigned char *cookie, unsigned int cookie_len)
{
return 1;
}
int dtls_verify_callback (int ok, X509_STORE_CTX *ctx) {
/* This function should ask the user
* if he trusts the received certificate.
* Here we always trust.
*/
return 1;
}
int main() {
char buff[FILENAME_MAX];
getcwd(buff, FILENAME_MAX);
union {
struct sockaddr_storage ss;
struct sockaddr_in s4;
struct sockaddr_in6 s6;
} client_addr;
struct sockaddr_in server_addr;
const int on = 1, off = 0;
memset(&server_addr, 0, sizeof(server_addr));
memset(&client_addr, 0, sizeof(client_addr));
int res;
SSL *ssl;
BIO *bio;
int sock;
struct timeval timeout;
SSL_CTX *ctx = SSL_CTX_new(DTLS_server_method());
SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
if(!SSL_CTX_use_certificate_file(ctx, "/home/matthew/CLionProjects/OpenSSL.Test/cmake-build-debug/certs/cert.crt", SSL_FILETYPE_PEM))
{
perror("cert");
exit(EXIT_FAILURE);
}
if(!SSL_CTX_use_PrivateKey_file(ctx, "/home/matthew/CLionProjects/OpenSSL.Test/cmake-build-debug/certs/key.key", SSL_FILETYPE_PEM))
{
perror("key");
exit(EXIT_FAILURE);
}
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, dtls_verify_callback);
SSL_CTX_set_read_ahead(ctx, 1);
SSL_CTX_set_cookie_generate_cb(ctx, generate_cookie);
SSL_CTX_set_cookie_verify_cb(ctx, &verify_cookie);
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(1114);
if((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
perror("socket");
exit(EXIT_FAILURE);
}
if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*) &on, (socklen_t) sizeof(on)) < 0)
{
perror("set reuse address");
exit(EXIT_FAILURE);
}
if(setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, (const char*) &on, (socklen_t) sizeof(on)) < 0)
{
perror("set reuse port");
exit(EXIT_FAILURE);
}
if(bind(sock, (const struct sockaddr *) &server_addr, sizeof(server_addr)) < 0)
{
perror("bind");
exit(EXIT_FAILURE);
}
memset(&client_addr, 0, sizeof(struct sockaddr_storage));
/* Create BIO */
bio = BIO_new_dgram(sock, BIO_NOCLOSE);
/* Set and activate timeouts */
timeout.tv_sec = 5;
timeout.tv_usec = 0;
BIO_ctrl(bio, BIO_CTRL_DGRAM_SET_RECV_TIMEOUT, 0, &timeout);
ssl = SSL_new(ctx);
SSL_set_bio(ssl, bio, bio);
SSL_set_options(ssl, SSL_OP_COOKIE_EXCHANGE);
if(!SSL_set_fd(ssl, sock))
{
perror("set fd");
exit(EXIT_FAILURE);
}
res = 0;
while(res <= 0)
{
res = DTLSv1_listen(ssl, (BIO_ADDR *) &client_addr);
if(res < 0)
{
perror("dtls listen"); <--- "Destination address required"
exit(EXIT_FAILURE);
}
}
SSL_accept(ssl);
printf("Hello, World!\n");
return 0;
}
I've added error checks in every nook and cranny I can think of...
What happens is, it loops over DTLSv1_accept() fine until a connection is attempted. As soon as the client tries to connect (sends the Client Hello), DTLSv1_accept() return -1. A call to SSL_get_error gives me 5, which is an SSL_ERROR_SYSCALL.
So I do perror and get Destination address required...
AFAIK the whole point of DTLSv1_listen is to listen for any incoming Client Hellos and populate the BIO_ADDR with the client's address once the handshake is finished...
I am using this to test the server:
openssl s_client -dtls -connect 127.0.0.1:1114
I've spent many hours on this. I am about to give up on OpenSSL and try libressl...
Any help is greatly appreciated.
P.S. the code is complete, in that it should compile and run if you want to try it out.
Thanks!
There are two significant problems in your code sample.
First your code to generate a cookie is not correct. You are supposed to create the
cookie and store it in the location pointed to by cookie, and then fill in the length of the cookie in *cookie_len. So the code should like this:
int generate_cookie(SSL *ssl, unsigned char *cookie, unsigned int *cookie_len)
{
memcpy(cookie, "cookie", 6);
*cookie_len = 6;
return 1;
}
However, the most important error comes later when setting up the SSL object for use. OpenSSL has the concept of a BIO for abstracting the underlying transport layer. In order to do DTLS over UDP you need to use a "dgram" BIO. You do this bit correctly:
/* Create BIO */
bio = BIO_new_dgram(sock, BIO_NOCLOSE);
/* Set and activate timeouts */
timeout.tv_sec = 5;
timeout.tv_usec = 0;
BIO_ctrl(bio, BIO_CTRL_DGRAM_SET_RECV_TIMEOUT, 0, &timeout);
ssl = SSL_new(ctx);
SSL_set_bio(ssl, bio, bio);
So at this point the SSL object is correctly setup for the DTLSv1_listen() call. However you then do this:
if(!SSL_set_fd(ssl, sock))
{
perror("set fd");
exit(EXIT_FAILURE);
}
The SSL_set_fd function is an alternative to the SSL_set_bio function. What it does is take the supplied fd, wraps it in a "socket" BIO and then calls SSL_set_bio() with the result. A socket BIO is primarily useful for standard TLS and can't be used for DTLS. So the effect of the above code is to throw away the dgram BIO that you previously set up.
In my tests, if I made the generate_cookie change I suggested above, and removed the SSL_set_fd line, it all started to work as expected.

No response from recv when using nonblocking socket

I am trying to create a portscanner in c. If the port is open, I want to get a response from the server. When I use regular blocking sockets, this works fine. For example, I know that for a certain address on my network, if I check port 80, it will return the html page to me when I call recv. I have tested this, and it works correctly every time.
However, I want to use nonblocking sockets, because sometimes certain servers will not respond and will cause the program to hang. I was able to get the nonblocking sockets to (kindof) work (the code is currently commented out below). I could see which ports were open, which were closed, and which timed out, but I was not able to get a response from the server (even though I know it should send one). What am I doing wrong?
tl;dr: When using nonblocking sockets (vs blocking), recv doesn't return any data.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <fcntl.h>
#define MAX_LEN 100000
int main(int argc, char **argv)
{
int sock, test_sock;
struct sockaddr_in server_addr;
struct hostent *hp;
char buf[MAX_LEN];
int num_bytes;
int err_code;
int START_PORT = 1;
int END_PORT = 100;
fd_set fdset;
struct timeval tv;
int opts;
// resolve server name for its IP address, etc.
hp = gethostbyname(argv[1]);
if (NULL == hp) {
perror("gethostbyname");
exit(2);
}
//printf("Here1\n");
// build remote server addr/port
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
memcpy(&server_addr.sin_addr, hp->h_addr, hp->h_length);
//server_addr.sin_port = htons(atoi(argv[2]));
test_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
for(int i=START_PORT; i<=END_PORT; i++) {
printf("Here2\n");
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //created the tcp socket
//opts = fcntl(sock, F_SETFL, O_NONBLOCK);
printf("Here3\n");
if (sock < 0)
{
perror("Socket()\n");
exit(1);
}
server_addr.sin_port = htons(i);
// connect to server
printf("Here4\n");
err_code = connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr));
printf("Here5\n");
/* ... */
if (err_code < 0) {
printf("Port %d: connection refused\n", i);
//exit(3);
} else {
printf("Port %d:\n", i);
memset(buf, 0, MAX_LEN);
// Create message to send
char message[256];
strcpy(message, "GET / HTTP/1.0\r\nHost: ");
strcat(message, argv[1]);
strcat(message, "\r\n\r\n");
unsigned total_bytes_sent = 0;
num_bytes = send(sock, message, strlen(message), 0);
if (num_bytes < 0) {
perror("send");
exit(4);
}
unsigned total_bytes_received = 0;
while(1) {
num_bytes = recv(sock, buf+total_bytes_received, MAX_LEN, 0);
if(num_bytes <= 0){
break;
}
total_bytes_received += num_bytes;
}
// display received ack message
//printf("Port %d:\n", i);
fflush(stdout);
write(1, buf, total_bytes_received);
printf("\n");
printf("Done...\n");
}
close(sock);
}
// close sock to release resource
close(sock);
return 0;
}
SOLUTION
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <errno.h>
#include <fcntl.h>
#define MAX_LEN 100000
int main(int argc, char **argv)
{
int sock, sock_test;
struct sockaddr_in server_addr;
struct hostent *hp;
char buf[MAX_LEN];
int num_bytes;
int err_code;
int START_PORT = 1;
int END_PORT = 100;
int valid = 1;
fd_set fdset;
struct timeval tv;
// resolve server name for its IP address, etc.
hp = gethostbyname(argv[1]);
if (NULL == hp) {
perror("gethostbyname");
exit(2);
}
// build remote server addr/port
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
memcpy(&server_addr.sin_addr, hp->h_addr, hp->h_length);
for(int i=START_PORT; i<=END_PORT; i++) {
sock_test = socket(AF_INET, SOCK_STREAM, 0);
if (sock_test < 0)
{
perror("Socket()\n");
exit(1);
}
fcntl(sock_test, F_SETFL, O_NONBLOCK);
server_addr.sin_port = htons(i);
connect(sock_test, (struct sockaddr *)&server_addr, sizeof(server_addr));
FD_ZERO(&fdset);
FD_SET(sock_test, &fdset);
tv.tv_sec = 3;
tv.tv_usec = 0;
if (select(sock_test + 1, NULL, &fdset, NULL, &tv) == 1)
{
int so_error;
socklen_t len = sizeof so_error;
getsockopt(sock_test, SOL_SOCKET, SO_ERROR, &so_error, &len);
if (so_error == 0) {
printf("%s:%d is open\n", argv[1], i);
memset(buf, 0, MAX_LEN);
// Create message to send
char message[256];
strcpy(message, "GET / HTTP/1.0\r\nHost: ");
strcat(message, argv[1]);
strcat(message, "\r\n\r\n");
printf("Here6\n");
unsigned total_bytes_sent = 0;
num_bytes = send(sock_test, message, strlen(message), 0);
printf("Here7\n");
int retry = 3;
unsigned total_bytes_received = 0;
while(retry) {
num_bytes = recv(sock_test, buf+total_bytes_received, MAX_LEN, 0);
if (0 == num_bytes)
{
/* socket has been closed by peer */
break;
}
else if(-1 == num_bytes)
{
if ((EAGAIN == errno) || (EWOULDBLOCK == errno))
{
/* no data to be read on socket */
retry--;
/* wait one second */
sleep(1);
}
else
{
/* other error */
perror("recv");
break;
}
}
else
{
total_bytes_received += num_bytes;
}
}
// display received ack message
//printf("Port %d:\n", i);
fflush(stdout);
write(1, buf, total_bytes_received);
printf("\n");
printf("Done...\n");
}
else
{
//printf("%s:%d is closed\n", argv[1], i);
}
} else {
printf("timed out\n");
valid = 0; //set the boolean flag to false
}
close(sock_test);
}
// close sock to release resource
close(sock_test);
return 0;
}
As pointed in comments, in non-blocking mode, you have to handle cases when
server is not ready to send data.
For man recv(3)
Return Value
Upon successful completion, recv() shall return the length of the message in bytes. If no messages are available to be received and the peer has performed an orderly shutdown, recv() shall return 0. Otherwise, -1 shall be returned and errno set to indicate the error.
Errors
The recv() function shall fail if:
EAGAIN or EWOULDBLOCK
The socket's file descriptor is marked O_NONBLOCK and no data is waiting to be received; or MSG_OOB is set and no out-of-band data is available and either the socket's file descriptor is marked O_NONBLOCK or the socket does not support blocking to await out-of-band data.
Since your client may try to read before the server send something, you must
adapt your code to wait:
/* maximum number of retry, one second per retry */
int retry = 10;
unsigned total_bytes_received = 0;
while(retry) {
num_bytes = recv(sock, buf+total_bytes_received, MAX_LEN, 0);
if (0 == num_bytes)
{
/* socket has been closed by peer */
break;
}
else if(-1 == num_bytes)
{
if ((EAGAIN == errno) || (EWOULDBLOCK == errno))
{
/* no data to be read on socket */
retry--;
/* wait one second */
sleep(1);
}
else
{
/* other error */
perror("recv");
break;
}
}
else
{
total_bytes_received += num_bytes;
}
}

Unix domain sockets 100x slower on Solaris 10 than on Linux?

I am benchmarking local socket performance on Linux and Solaris for a project. For some reason I cannot find out, performance on Solaris is roughly 100x worse than on Linux. In Linux, opening a socket, exchanging one very short (2 char) message each way and closing it takes about 10us elapsed time. On Solaris, the same thing takes about 1000us.
Set-up is Solaris 10 developer vm in Virtual Box and Linux both in the same Virtual Box and directly on the same hardware (makes no difference).
Is this a known issue with Solaris? Any ways to work around it? I cannot use a local network connection instead for reasons I cannot go into here.
Code for client and server below. Compile with "cc -fast -m64 -lrt -lsocket -lnsl -o server server.c" and the equivalent for the client. Gcc 3.4.3 as delivered with Solaris 10 gives comparable results. This code has been cut down, for example timeouts have been removed end error handling is minimal.
server.c:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#define DIRECTORY "sub/"
#define FULL_PATH "sub/c_socket"
#define MAX_COMMAND_LEN 8192
#define PERMISSIONS 0700
void on_error(int err, char * msg) { // simple convenient error handler
if (err == -1) { // Tests whether 'err' is -1 and
perror(msg); // prints error and msg if so.
exit(-1);
}
}
int main() {
struct sockaddr_un addr;
int srv_fd, inst_fd;
int inst_adr_size;
char c;
int ret;
char readbuf[MAX_COMMAND_LEN];
int num_read;
fd_set rfds;
int fail;
int i;
// make address
memset(&addr, 0, sizeof(addr)); // clear out addr
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, FULL_PATH, sizeof(addr.sun_path));
// Remove old pseudo file if present
ret = unlink(FULL_PATH);
if (ret == -1 && errno != ENOENT) {
on_error(ret,"\nRemoving old socket file\n");
}
// Remove old directory if present
ret = rmdir(DIRECTORY);
if (ret == -1 && errno != ENOENT) {
on_error(ret, "\nRemoving old socket directory\n");
}
// Re-create new directory with appropriate permissonsm
ret = mkdir(DIRECTORY, PERMISSIONS);
on_error(ret,"\nCreating directoroy for socket file\n");
// create server listening socket
srv_fd = socket(AF_UNIX, SOCK_STREAM, 0);
on_error(srv_fd, "\nSocket creation:\n");
// bind server listening socket to address
ret = bind(srv_fd, (struct sockaddr *) &addr, sizeof(addr));
on_error(ret, "\nSocket binding:\n");
// set file permissions for socket file (somewhat redundant)
ret = chmod(FULL_PATH, PERMISSIONS);
on_error(ret, "\nSetting socket file permissions\n");
// set socket listening and queue length
ret = listen(srv_fd, 10);
on_error(ret, "\nSet socket to listen:\n");
while(1) {
// accept requests
inst_fd = accept(srv_fd, NULL, NULL);
on_error(inst_fd, "\n accepting connection:\n");
// prepare to use select on inst_fd
FD_ZERO(&rfds);
FD_SET(inst_fd, &rfds);
// now interact with the client on the instance socket.
while(1) {
num_read = 0;
while (1) {
// read a line terminated by '\n'
ret = select(inst_fd + 1, &rfds, NULL, NULL, NULL);
on_error(ret, "\nSelect on socket\n");
if (ret == 1) {
// we can read something
ret = recv(inst_fd, readbuf+num_read, MAX_COMMAND_LEN-num_read, 0
on_error(ret, "\nrecv:\n");
if (ret == 0) {
break; // we have EOF
}
num_read += ret;
if (readbuf[num_read - 1] == '\n') {
break;
}
}
} /* reading one input line done */
if (num_read == 0) break; // EOF propagated
// process command: Just send 2 chars back
ret = send(inst_fd, "n\n", 2, 0);
}
close(inst_fd); // clean up
}
// runs forever...
}
client.c:
#include <stdio.h>
#include <time.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define RCVBUFSIZE 8192 /* Size of receive buffer */
#define FULL_PATH "sub/c_socket"
#define CYCLES 100000
void on_error(int err, char * msg) { // more convenient error output
if (err == -1) { // Tests whether 'err' is -1 and
perror(msg); // prints error and msg if so.
exit(-1);
}
}
int main(int argc, char *argv[]) {
int client_fd;
struct sockaddr_un addr;
char readbuf[RCVBUFSIZE+1];
int num_read;
int ret;
int count;
fd_set rfds;
char * msg = "N\n";
// make address
memset(&addr, 0, sizeof(addr)); // clear out addr
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, FULL_PATH, sizeof(addr.sun_path));
for(count = 0; count < CYCLES; count++) {
// create socket
client_fd = socket(PF_UNIX, SOCK_STREAM, 0);
on_error(client_fd, "socket() failed");
// prepare to use select on inst_fd
FD_ZERO(&rfds);
FD_SET(client_fd, &rfds);
// connect
ret = connect(client_fd, (struct sockaddr *) &addr, sizeof(addr));
on_error(ret, "connect() failed");
// send msg to server
ret = send(client_fd, msg, 2, 0);
if (ret != 2) {
on_error(-1, "\nnot all bytes sent\n");
}
num_read = 0;
// read until we have a '\n'
while (1) {
ret = select(client_fd + 1, &rfds, NULL, NULL, NULL);
on_error(ret, "\nSelect on socket\n");
if (ret == 1) {
// we can read something
ret = recv(client_fd, readbuf + num_read, RCVBUFSIZE - num_read, 0)
on_error(ret, "\nrecv:\n");
num_read += ret;
if (readbuf[num_read - 1] == '\n') break;
}
}
if (num_read == 0) break;
close(client_fd);
}
return(0);
}
I had a similar issue when I was studying sockets and tried writing an ftp server: because of a bug in the conversion to ascii I ended up writing files one byte at a time, but on linux it was ok, while on windows I ended up with something like 100KB/s on the loop interface... if that is the case, increasing the number of bytes should lessen the difference a lot.
It seems that under linux the act of requesting a system call is simply faster.
PS
I don't know much about the internals of an operating system, so if anyone can share some pointers to understand the issue (like http://yarchive.net/comp/linux/linux_speed.html) I'd be grateful.

Resources