I have an SSL server (code listed below) connecting to multiple SSL client. I am using a single context and the initialization is
SSL_CTX *ctx;
SSL *ssl[MAXEVENTS];
SSL_library_init();
ctx = InitServerCTX(); // initialize SSL
...
...
Then I have the following piece of code
ssl[i] = SSL_new(ctx); // get new SSL state with context
SSL_set_fd(ssl[i], infd); // set connection socket to SSL state
Then I perform SSL_accept(ssl[i]).
All this is being performed using epoll (edge trigger mode). I have modified the example in https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/ to use SSL by referring to https://www.cs.utah.edu/~swalton/listings/articles/ssl_server.c as a reference
The logic for this is
events = new epoll_event[MAXEVENTS * sizeof event];
// The event loop
while (true)
{
int n, i;
n = epoll_wait (efd, events, MAXEVENTS, -1);
for (i = 0; i < n; i++)
{
if ((events[i].events & EPOLLERR) ||
(events[i].events & EPOLLHUP) ||
(!(events[i].events & EPOLLIN)))
{
// An error has occured on this fd, or the socket is not
// ready for reading (why were we notified then?)
fprintf (stderr, "epoll error\n");
close (events[i].data.fd);
continue;
} else if (sfd == events[i].data.fd) {
// We have a notification on the listening socket, which
// means one or more incoming connections.
while (1)
{
struct sockaddr in_addr;
socklen_t in_len;
int infd;
char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
in_len = sizeof in_addr;
infd = accept (sfd, &in_addr, &in_len);
if (infd == -1)
{
if ((errno == EAGAIN) ||(errno == EWOULDBLOCK)) {
// We have processed all incoming
// connections.
break;
} else {
perror ("accept");
break;
}
}
s = getnameinfo (&in_addr, in_len,
hbuf, sizeof hbuf,
sbuf, sizeof sbuf,
NI_NUMERICHOST | NI_NUMERICSERV);
if (s == 0) {
printf("Accepted connection on descriptor %d "
"(host=%s, port=%s)\n", infd, hbuf, sbuf);
}
ssl[i] = SSL_new(ctx); // get new SSL state with context
SSL_set_fd(ssl[i], infd); // set connection socket to SSL state
int ret;
if ( (ret=SSL_accept(ssl[i])) == FAIL ) { // do SSL-protocol accept
ERR_print_errors_fp(stderr);
printf("Performing exchange Error 1.\n");
int error = SSL_get_error(ssl[i], 0);
//TODO A retry timer or retry counter. Cannot keep retrying perpetually.
if (ret <=0 && (error == SSL_ERROR_WANT_READ)) {
//Need to wait until socket is readable. Take action?
//LOG the reason here
perror ("Need to wait until socket is readable.");
} else if (ret <=0 && (error == SSL_ERROR_WANT_WRITE)) {
//Need to wait until socket is writable. Take action?
//LOG the reason here
perror ("Need to wait until socket is writable.");
} else {
//LOG the reason here
perror ("Need to wait until socket is ready.");
}
shutdown (infd, 2);
SSL_free (ssl[i]);
continue;
}
// Make the incoming socket non-blocking and add it to the
// list of fds to monitor.
s = SocketNonBlocking (infd);
if (s == -1) {
abort ();
}
event.data.fd = infd;
event.events = EPOLLIN | EPOLLET | EPOLLHUP;
s = epoll_ctl (efd, EPOLL_CTL_ADD, infd, &event);
if (s == -1) {
perror ("epoll_ctl");
abort ();
}
}
continue;
Now,
while (1)
{
ssize_t count;
char buf[1024];
char reply[1024];
printf("Performing exchange.\n");
const char* HTMLecho="<html><body><pre>%s</pre></body></html>\n\n";
ShowCerts(ssl[i]); // get any certificates
count = SSL_read(ssl[i], buf, sizeof(buf)); // get request
int32_t ssl_error = SSL_get_error (ssl[i], count);
switch (ssl_error) {
case SSL_ERROR_NONE:
printf("SSL_ERROR_NONE\n");
break;
case SSL_ERROR_WANT_READ:
printf("SSL_ERROR_WANT_READ\n");
break;
case SSL_ERROR_WANT_WRITE:
printf("SSL_ERROR_WANT_WRITE\n");
break;
case SSL_ERROR_ZERO_RETURN:
printf("SSL_ERROR_ZERO_RETURN\n");
break;
default:
break;
}
if (( count > 0 ) )
{
buf[count] = 0;
printf("count > 0 Client msg: \"%s\"\n", buf);
sprintf(reply, HTMLecho, buf); // construct reply
SSL_write(ssl[i], reply, strlen(reply)); // send reply
} else if ((count < 0) ){
printf("count < 0 \n");
if (errno != EAGAIN)
{
printf("count < 0 errno != EAGAIN \n");
perror ("read");
done = 1;
}
break;
} else if (count==0){
ERR_print_errors_fp(stderr);
epoll_ctl(efd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
printf("count == 0 Client Disconnected.\n");
done = 1;
break;
}
}
if (done)
{
printf("Freeing data.\n");
int sd = SSL_get_fd(ssl[i]);
SSL_free(ssl[i]); // release SSL state
close(sd); // close connection
//close (events[i].data.fd);
}
}
This works fine for one server - one client. But the moment I try to connect two clients, the client that connected last is the only one that receives the data. The client that was connected before just keeps hanging without any activity.
UPDATE
I found that there is some indexing issue going on here. The value of the variable i from the epoll example is not corresponding to what I think it should be corresponding. I tried connecting two clients and I had thought initially the i should have incremented for second client but it is not the case. It still stays 0.
Ok I solved the issue. My problem stemmed from incorrect indexing. I was relying on the variable i that did not behave the way I expected. (see update in my question)
First I declare std::map<int,SSL*> sslPairMap;
Then I insert the successful fd and SSL accept into a std::map in C++. In C, one can use struct based pairing. There is one example here https://github.com/dCache/dcap/blob/b432bd322f0c1cf3e5c6a561845899eec3acad1e/plugins/ssl/sslTunnel.c
//(c) 2014 enthusiasticgeek for stack overflow
sslPairMap.insert(std::pair<int,SSL*>(infd, ssl));
// Make the incoming socket non-blocking and add it to the
// list of fds to monitor.
s = AibSocketNonBlocking (infd);
if (s == -1) {
abort ();
}
aibevent.data.fd = infd;
aibevent.events = EPOLLIN | EPOLLET | EPOLLHUP;
s = epoll_ctl (efd, EPOLL_CTL_ADD, infd, &aibevent);
if (s == -1) {
perror ("epoll_ctl");
abort ();
}
After this I simply retrieve the SSL* from the map which ensures I do not change index inadvertently. std::map saves the day
//(c) 2014 enthusiasticgeek for stack overflow
while (1)
{
ssize_t count;
char buf[1024];
char reply[1024];
printf("Performing exchange where i = %d.\n",i);
const char* HTMLecho="<html><body><pre>%s</pre></body></html>\n\n";
ShowCerts(sslPairMap[aibevents[i].data.fd]); // get any certificate
count = SSL_read(sslPairMap[aibevents[i].data.fd], buf, sizeof(buf)); // get request
ssl_error = SSL_get_error (sslPairMap[aibevents[i].data.fd], count);
switch (ssl_error) {
case SSL_ERROR_NONE:
printf("SSL_ERROR_NONE\n");
break;
case SSL_ERROR_WANT_READ:
printf("SSL_ERROR_WANT_READ\n");
break;
case SSL_ERROR_WANT_WRITE:
printf("SSL_ERROR_WANT_WRITE\n");
break;
case SSL_ERROR_ZERO_RETURN:
printf("SSL_ERROR_ZERO_RETURN\n");
break;
default:
break;
}
if (( count > 0 ) )
{
buf[count] = 0;
printf("count > 0 Client msg: \"%s\"\n", buf);
sprintf(reply, HTMLecho, buf); // construct reply
SSL_write(sslPairMap[aibevents[i].data.fd], reply, strlen(reply)); // send reply
break;
} else if ((count < 0) ){
printf("count < 0 \n");
if (errno != EAGAIN)
{
printf("count < 0 errno != EAGAIN \n");
perror ("read");
done = 1;
}
break;
} else if (count==0){
ERR_print_errors_fp(stderr);
epoll_ctl(efd, EPOLL_CTL_DEL, aibevents[i].data.fd, NULL);
printf("count == 0 Client Disconnected.\n");
done = 1;
break;
}
}
if (done)
{
printf("Freeing data.\n");
int sd = SSL_get_fd(sslPairMap[aibevents[i].data.fd]);
if(ssl_error == SSL_ERROR_NONE){
SSL_shutdown(sslPairMap[aibevents[i].data.fd]);
}
SSL_free(sslPairMap[aibevents[i].data.fd]); // release SSL state
close(sd); // close connection
//close (aibevents[i].data.fd);
erase_from_map(sslPairMap, aibevents[i].data.fd);
}
}
If anyone comes across this, another way to go about it is to store the SSL* pointer in the event data itself:
events[i].data.u64 = (long long)ssl;
And when you need to read/write from it:
auto ssl = (SSL*)events[i].data.u64;
SSL_read(ssl, someBuffer, sizeof(someBuffer));
For example.
Related
So I'm writing a very basic TCP server which just echoes back the messages sent from the client back to the client. I have a setup where the server is running in a while loop and waiting for either a new client to connect or for a message from one of the existing clients using the select() method.
My question is:
How do i, from the server side, close the master socket and basically shutting down the server. Im not asking for exact code but more for standard practice.
For some context:
In the long run I am imagining multiple clients connected to my server and I need to shutdown the server gracefully from the server side.
Even more context: The server code.
#define TRUE 1
#define FALSE 0
#define PORT 55555
int main(int argc, char* argv[])
{
int opt = TRUE;
int masterSocket, addrlen, newSocket, maxClients = 10, clientSockets[maxClients],
activity, valread, sd, maxSd;
struct sockaddr_in address;
char buffer[1025];
fd_set readfds;
const char *message = "ECHO DAMON v1.0 \r\n";
/* Initialize array with 0 so it's not read */
for(int i = 0; i < maxClients ; i++)
{
clientSockets[i] = 0;
}
/* Create master socket */
if((masterSocket = socket(AF_INET, SOCK_STREAM,0)) == 0)
{
perror("Error when trying to create master socket.\n");
exit(EXIT_FAILURE);
}
/* Set master socket to allow multiple connections */
if( setsockopt(masterSocket, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt)) < 0)
{
perror("Could not set sockopt");
exit(EXIT_FAILURE);
}
/* Socket type */
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
if( bind(masterSocket, (struct sockaddr*)&address, sizeof(address)) < 0)
{
perror("Error, could not bind master socket. \n");
exit(EXIT_FAILURE);
}
printf("Listening on %d. \n", PORT);
if( listen(masterSocket, 3) < 0)
{
perror("Error, could not listen.\n");
exit(EXIT_FAILURE);
}
addrlen = sizeof(address);
puts("Waiting for connections...\n"); //just a printf variant
/* END INIT */
while(TRUE)
{
/* Clear socket set */
FD_ZERO(&readfds);
/* Add master socket to set */
FD_SET(masterSocket, &readfds);
/* Add child sockets to set, will be 0 first iteration */
for(int i = 0; i < maxClients ; i++)
{
sd = clientSockets[i]; // sd = socket descriptor
/* If valid socket descriptor */
if(sd > 0)
{
FD_SET(sd, &readfds);
}
/* Get highest fd number, needed for the select function (later) */
if(sd > maxSd)
{
maxSd = sd;
}
}//end for-loop
/* Wait for activity on any socket */
activity = select(maxSd +1, &readfds, NULL, NULL, NULL);
if((activity < 0) && (errno != EINTR))
{
printf("Error on select.\n"); //no need for exit.
}
/* If the bit for the file descriptor fd is set in the
file descriptor set pointed to by fdset */
/* If something happend in the master socket, its a new connection */
if(FD_ISSET(masterSocket, &readfds))
{
if((newSocket = accept(masterSocket, (struct sockaddr*)&address, (socklen_t*)&addrlen)) < 0)
{
perror("Could not accept new socket.\n");
exit(EXIT_FAILURE);
}
/* Print info about connector */
printf("New connection, socket fd is %d, ip is: %s, port: %d\n", newSocket, inet_ntoa(address.sin_addr), ntohs(address.sin_port));
if( send(newSocket, message, strlen(message), 0) != strlen(message))
{
perror("Could not sent welcome message to new socket.\n");
}
puts("Welcome message sen successfully.\n");
/* Add new socket to array of clients */
for(int i = 0; i < maxClients; i++)
{
if(clientSockets[i] == 0)
{
clientSockets[i] = newSocket;
printf("Adding socket to list of client at index %d\n", i);
break;
}
}
}//end masterSocket if
/* Else something happend at client side */
for(int i = 0; i < maxClients; i++)
{
sd = clientSockets[i];
if(FD_ISSET(sd, &readfds))
{ /* Read socket, if it was closing, else read value */
if((valread = read( sd, buffer, 1024)) == 0)
{
getpeername( sd, (struct sockaddr*)&address, (socklen_t*)&addrlen);
printf("Host disconnected, ip %s, port %d.\n", inet_ntoa(address.sin_addr), ntohs(address.sin_port));
close(sd);
clientSockets[i] = 0;
}
}
else
{
buffer[valread] = '\0';
send(sd, buffer, strlen(buffer), 0);
}
}
}
return 0;
}
If you are planning to exit your application gracefully by adding a breakstatement in your app by something similar to this:
if (exit_condition)
{
break;
}
You can place this loop at the end of your main function:
/* close connections gracefully */
closesocket(masterSocket);
masterSocket = 0; /* optional, see comment below */
for(int i = 0; i < maxClients; i++)
{
if (clientSockets[i] != 0)
{
shutdown(clientSockets[i]);
closesocket(clientSockets[i]);
clientSockets[i] = 0; /* optional, except if you also have a SIGINT handler */
}
}
If you want to do the same to handle an exit with Ctrl-C, you will find details on how to setup a SIGINT handler to handle Ctr-C here:
Catch Ctrl-C in C. Place the above loop in your handler, then. Your sockets-related variables will have to be declared at global scope, since your sockets are only visible from main() in your current code.
I have a non-blocking socket server which supports all connecting clients. It's using multi-threading and it's cross-compilable using GCC.
It works perfect (as I want) in Linux, but when I try to use it in Windows, when I send a 70MB of file through it, it loses around 20MB from the file.
All sockets are non-blocking, so for recv/send socket calls, I don't have check/stop. It's in a loop and it sends what it receive, it sort of acts as a Echo server, but it loses data in Windows. I'm using Winsock 2.2 in WSAStartup.
What is wrong? How can I have wait/flush send calls, but never block recv calls? (if this is the issue)
Code pieces:
How I make it non-blocking:
iMode = 1;
ioctlsocket(sock1,FIONBIO, &iMode);
ioctlsocket(sock2,FIONBIO, &iMode);
How I send/receive between two sockets:
for (;;)
{
memset(buffer, 0, 8192);
int count = recv(sock1, buffer, sizeof(buffer), 0);
receiveResult = WSAGetLastError();
if (receiveResult == WSAEWOULDBLOCK && count <= 0)
continue;
if (count <= 0)
{
closesocket(sock1);
closesocket(sock2);
return;
}
if (count > 0)
{
int retval = send(sock2, buffer, count, 0);
}
}
int count = recv(sock1, buffer, sizeof(buffer), 0);
receiveResult = WSAGetLastError();
if (receiveResult == WSAEWOULDBLOCK && count <= 0)
When calling recv() or send(), WSAGetLastError() will return a meaningful value only if -1 (SOCKET_ERROR) is returned, but you are also checking it when 0 is returned instead. They do not set an error code for WSAGetLastError() when returning >= 0. You need to separate those conditions.
Also, just because you have read X number of bytes does not guarantee that you will be able to send X number of bytes at one time, so you need to check send() for WSAEWOULDBLOCK until you have no more data to send.
Try something more like this:
bool keepLooping = true;
do
{
int count = recv(sock1, buffer, sizeof(buffer), 0);
if (count > 0)
{
// data received...
char *p = buffer;
do
{
int retval = send(sock2, p, count, 0);
if (retval > 0)
{
p += retval;
count -= retval;
}
else if (retval == 0)
{
// peer disconnected...
keepLooping = false;
}
else if (WSAGetLastError() != WSAEWOULDBLOCK)
{
// a real error occurred...
keepLooping = false;
}
else
{
// peer is not ready to receive...
// optionally use select() to wait here until it is...
}
}
while ((count > 0) && (keepLooping));
}
else if (count == 0)
{
// peer disconnected...
keepLooping = false;
}
else if (WSAGetLastError() != WSAEWOULDBLOCK)
{
// a real error occurred...
keepLooping = false;
}
else
{
// no data is available for reading...
// optionally use select() to wait here until it is...
}
}
while (keepLooping);
closesocket(sock1);
closesocket(sock2);
return;
I have a problem where the returned file descriptor would gradually increase to be a number larger than FD_SETSIZE.
My tcp server is continually shutdown which requires my client to close the socket and reconnect. The client will then attempt to reconnect to the server by calling socket to obtain a new file descriptor before calling connect.
However it appears that everytime I call socket the file descriptor returned is incremented and after a certain amount of time it becomes larger than FD_SETSIZE, which is a problem where I use select to monitor the socket.
Is it ok to reuse the first file descriptor returned from socket for the connect call even though the the socket was closed? Or is there other workarounds?
Reconnect code (looping until connected):
int s = getaddrinfo(hostname, port, &hints, &result);
if (s != 0) { ... HANDLE ERROR ...}
...
struct addrinfo *rp;
int sfd;
for (rp = result; rp != NULL; rp -> ai_protocol)
{
sfd = socket( rp->ai_family, rp->ai_sockettype, rp->ai_addrlen);
if (sfd >= 0)
{
int res = connect(sfd, rp->ai_addr, rp->ai_addrlen);
if (res != -1)
{
_sockFd = sfd;
_connected = true;
break;
}
else
{
close (sfd);
break;
}
}
}
if (result != NULL)
{
free(result);
}
Read Message code:
if (_connected)
{
...
retval = select(n, &rec, NULL, NULL, &timeout);
if (retval == -1)
{
...
_connected = false;
close(_sockFd);
}
else if (retval)
{
if (FD_ISSET(_sockFD, &rec) == 0)
{
....
return;
}
int count = read(...)
if (count)
{
....
return;
}
else
{
....
_connected = false;
close(_sockFd);
}
}
}
You're not closing the socket if the connect fails. So it remains open, occupying an FD, so next time you call socket() you get a new FD. You're also not breaking out of your loop when connect() succeeds, which is another leak. You're also not checking the result of read() for -1.
I have created a socket in C++ and I needed it to have certain connection timeout. So that's what is happening:
Create socket
Make it NON_BLOCKING
Call connect
It returns -1 and errno EINPROGRESS as expected
Call select
Returns >0, so connection has been made
Make the socket BLOCKING again
Code for this part is the following:
bool mastControl::prepareSocket(char * address, int port, int * sockfd) {
struct sockaddr_in serv_addr;
struct timeval timeout = {0,100000};
struct timeval connTimeout;
struct hostent * server = NULL;
fd_set socketSet;
socklen_t lon;
int sockOpt = 0;
long socketFlags = 0;
int buffersize = 8;
int res = 0;
int connectReturn = 0;
const int WAIT_TO_RECONN = 15;
server = gethostbyname(address);
*sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (*sockfd < 0) {
qDebug()<<"Impossible to open socket: "<<strerror(errno);
return false;
}
if (server == NULL) {
qDebug()<<"No such host: "<<strerror(h_errno);
return false;
}
// Initializating server direction struct:
bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
bcopy((char *)server->h_addr,
(char *)&serv_addr.sin_addr.s_addr,
server->h_length);
serv_addr.sin_port = htons(port);
// Making socked non-blocking in order to set a timeout value for connection:
if((socketFlags = fcntl(*sockfd, F_GETFL, NULL)) < 0){
qDebug()<<"Impossible to retrieve sockets descriptor flags "<<strerror(errno);
return false;
}
socketFlags |= O_NONBLOCK;
if(fcntl(*sockfd, F_SETFL, socketFlags) <0){
qDebug()<<"Impossible to update sockets descriptor flags: "<<strerror(errno);
return false;
}
connectReturn = connect(*sockfd,(struct sockaddr *) &serv_addr,sizeof(serv_addr));
if(connectReturn < 0){
if(errno == EINPROGRESS){
do{
// Establishing a 15 seconds timeout:
connTimeout.tv_sec = 15;
connTimeout.tv_usec = 0;
FD_ZERO(&socketSet); // Initialising set of sockets as an empty set
FD_SET(*sockfd, &socketSet); // Adding socket to set
connectReturn = select(*sockfd+1, NULL, &socketSet, NULL, &connTimeout);
if(connectReturn<0 && errno!=EINTR){ // Error in select
qDebug()<<"Connection error in select function: "<<strerror(errno);
return false;
}
else if(connectReturn>0){ // Socket selected for writing
lon = sizeof(int);
if(getsockopt(*sockfd, SOL_SOCKET, SO_ERROR, (void*)(&sockOpt), &lon) <0){
qDebug()<<"Unnable to get socket options: "<<strerror(errno);
return false;
}
// Checking the value returned:
if(sockOpt){
qDebug()<<"Error in delayed connection: "<<strerror(errno);
return false;
}
break;
}
else{ // Timeout
qDebug()<<"Connection timeout exceeded: "<<strerror(errno);
return false;
}
} while (1);
}
else{
qDebug()<<"Connection error: "<<strerror(errno);
sleep(WAIT_TO_RECONN); // Wait 15 seconds
return false;
}
}
//Connected
// Must set the socket as blocking again:
if((socketFlags = fcntl(*sockfd, F_GETFL, NULL)) < 0){
qDebug()<<"Impossible to retrieve sockets descriptor flags "<<strerror(errno);
return false;
}
socketFlags &= (~O_NONBLOCK);
if(fcntl(*sockfd, F_SETFL, socketFlags) <0){
qDebug()<<"Impossible to update sockets descriptor flags "<<strerror(errno);
return false;
}
if (setsockopt (*sockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout,
sizeof(timeout)) < 0) {
qDebug()<< "ERR - setsockopt failed";
return false;
}
if (setsockopt (*sockfd, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout,
sizeof(timeout)) < 0) {
qDebug()<< "ERR - setsockopt failed";
return false;
}
if ((res = setsockopt (*sockfd, SOL_SOCKET, SO_SNDBUF, &buffersize,
sizeof(buffersize))) == -1) {
qDebug()<< "ERR - setsockopt failed (SO_SNDBUF) = " << res;
return false;
}
//Socket Ready
return true;
}
That works ok. But then I have a loop where I'm calling a function which checks if there is a new packet received to read:
bool mastControl::findPacket(int sockfd, st_messageMastToPc * messageReceived, bool * connected) {
int n = 0;
bool messageFound = false;
char * buffer = (char *) messageReceived;
unsigned int pos = 0;
while ( ((n = read(sockfd, &(buffer[pos]), 1)) > 0) and not messageFound) {
//qDebug() << "read output " << n;
if (n == 1) {
pos++;
if ( (pos == 1) && (buffer[0] == 2)) {
// Some stuff...
} else if ( (pos == 2) && (buffer[1] == 2) ) {
// Some stuff...
} else if (pos >= uiMessageMastToPcSize) {
messageFound = true;
//Complete message received
} else if (pos < 2) {
// Reseting pos
pos = 0;
}
}
}
if (n < 0){
qDebug()<< "Disconnected. Reason #" << errno << ": " << strerror(errno);
*connected = false;
}
return messageFound;
}
Read function is giving EAGAIN as errno, which means "Resource temporarily unavailable". Then I'm supposing I am disconnected, and since boolean connected is now false, in the loop I will be trying to reconnect creating the socket again, ans so on.
So, in the first place I don't know why I'm receiving this error.
In the second place, I don't know if I am disconnected at all, or it's me the one who is disconnecting when creating the new socket after this error.
Any help?
EAGAIN does not mean you're disconnected, it just means "there's nothing to read now; try again later".
You could either unset O_NONBLOCK with fcntl(2) (making read wait until there's something available), or just wait on the socket with something like select(2) before calling read.
EDIT: Now that you've added more code, I can see that you're setting SO_RCVTIMEO for the socket. This can cause a blocking read to return EAGAIN (so if you don't want that to happen, simply leave SO_RCVTIMEO alone).
This is the code:
bool EncoderTLS::handshake()
{
int sock = getSocket();
SSL *ssl = SSL_new(ctx);
BIO *sbio = BIO_new_socket(sock, BIO_NOCLOSE);
SSL_set_bio(ssl, sbio, sbio);
int r;
int i = 0;
while(i < ATTEMPTS)
{
int s;
if((r = ((isServer) ? SSL_accept(ssl) :SSL_connect(ssl)) )<=0)
{
s = SSL_get_error(ssl,r);
if (SSL_get_error(ssl,r) == SSL_ERROR_SYSCALL)
{
if (errno == 0) break;
printf("errno = %d\n",errno);
perror("Syscall error");
}
}
if (s == SSL_ERROR_WANT_READ || s == SSL_ERROR_WANT_WRITE) usleep(10);
else
{
logger->log(Level::WARNING, "SSL handshake failed");
return false;
}
i++;
}
if (!isServer && SSL_get_verify_result(ssl) != X509_V_OK) //Server authentication
{
logger->log(Level::WARNING, "couldn't verify certificate");
printf("Error: %s\n", ERR_reason_error_string(ERR_get_error()));
return false;
}
BIO *test = BIO_new(BIO_s_mem());
SSL_set_bio(ssl, test, test);
SSL_write(ssl, "blablablablabla", 10);
char **p;
int length = BIO_get_mem_data(test,p);
printf("Printing encoding of 'blablablab', of length %d:\n", length);
for(int j=0; j<length; j++)
printf("%c", p[j]);
printf("\n");
return true;
}
I'd expect the data written by SSL_write towards the end would go into the BIO, and to memory. However, when I try to fetch the data from the BIO, it tells me it's length is 0.
What am I doing wrong?
Check what SSL_write() returns. Maybe it's SSL_ERROR_WANT_READ, in this case you must first read from your SSL connection and supply the read data to your SSL object. SSL_read() will do it for you if you call it before the second SSL_set_bio().
But when you will succeed with your SSL_write() - be aware that it won't be only encrypted data in your mem BIO. Protocol-related data will also be there.