A little more about how my proxy works,

    My proxy behaves like number two in the previous email.  It is purely a
bidirectional proxy with no clear tracking of the protocol.  With the only
exception being it reads the entire (unencrypted) Connect request from the
client, and then goes and connects to the server.  Once the connection to
the server is OK it creates two separate structures that are wrappers for
communication over the ssl protocol.  These structures are both aware of
each other, and can schedule a write, or a read with the other.

    It's multi threaded with non-blocking I/O.  I'm not sure exactly what
you mean by socket discovery, but I think you are asking how my program
determines when something is ready?  If that's the case then my program uses
a select statement to watch the file descriptor to see if it's ready for
read or write.  It uses a call back system to perform the correct action
based on which fd_set was ready.

     The issue with my file upload shouldn't be lying in the blocking issue
since it's non-blocking I/O.  It looks like the issue with a SSL_read call
from the perspective of the program as the client is returning zero during
the upload (which makes sense) but the way my proxy is handling connections
the only reason it could have read is if there was a SSL_get_error() that
returned a SSL_ERROR_WANT_READ code.  I am going to post my ssl_read and
ssl_write wrapper functions, and anything else that's important to those two
here:

void ProxySSLConnection::ssl_read()
{
    if (!_ssl)
        return;

    DataBuffer *buf = _other->_buffer;
    int ret = SSL_read(_ssl, buf->end(), buf->len_avail());
    if (ret > 0)
    {
        buf->len += ret;
        _other->schedule_send_buffer();
        schedule_read(&ProxySSLConnection::ssl_read);
    }
    else
    {
        handle_ssl_error(ret, &ProxySSLConnection::ssl_read, "read");
    }
}

void ProxySSLConnection::ssl_write()
{
    if (!_ssl)
        return;
    int ret = SSL_write(_ssl, _buffer->begin(), _buffer->len);
    //printf("WRITING: \n");
             //These lines are just debugging.
    //for(char * buf = _buffer->begin();buf != _buffer->end();buf++){
    //   printf("%c", *buf);
    //}
    if (ret > 0)
    {
        _buffer->erase_front(ret);
        if (!_buffer->empty())
        {
            schedule_write(&ProxySSLConnection::ssl_write);
        }
    }
    else
    {
        handle_ssl_error(ret, &ProxySSLConnection::ssl_write, "write");
    }
}


The handle_ssl_error function is:

void ProxySSLConnection::handle_ssl_error(int ret, handler_function handler,
const char * caller)
{
    int error = SSL_get_error(_ssl, ret);
    switch (error)
    {
        case SSL_ERROR_WANT_READ:
            schedule_read(handler);
            break;
        case SSL_ERROR_WANT_WRITE:
            schedule_write(handler);
            break;
        case SSL_ERROR_ZERO_RETURN:
            _proxy_thread->shutdown();
            break;
        default:
            unsigned long err = ERR_get_error();
            while (err != 0)
            {
                char* err_string = ERR_error_string(err, NULL);
                cout << err_string << endl;
                err = ERR_get_error();
            }
            close_connection();
            _proxy_thread->shutdown();
            break;
    }
}

If the following case is added to the switch then the upload is fixed, but
the threads never exit:

 case SSL_ERROR_SYSCALL: //This case is where the uploads fail.
                schedule_write(handler);
            break;

The caller variable is just for debug purposes.  As per Dave Thompson's
request I was using it to determine which call was causing the error, and
was then calling perror(caller) since I was getting a SSL_ERROR_SYSCALL with
a ret of 0, and the perror printed "read: Success".

Schedule Read and Write:

void ProxySSLConnection::schedule_write(handler_function handler)
{
    _write_handler = handler;
    _proxy_thread->register_write_callback(this);
}

void ProxySSLConnection::schedule_read(handler_function handler)
{
    _read_handler = handler;
    _proxy_thread->register_read_callback(this);
}

_proxy_thread is the calling thread that created the ProxySSLConnection
class. register_read and register_write callbacks are literally just doing
an FD_SET() on the file descriptor of the _ssl connection and a fd_set
called read_set, and write_set respectively.   In each ProxyThread there
select statement inside of a main loop that is woken up by either a signal
from another thread, or from a file descriptor being ready.  When the file
descriptor is ready the thread will call on_read or on_write of the
appropriate ProxySSLConnection

on_read, and on_write:

void ProxySSLConnection::on_read()
{
    handle_callback(&_read_handler);
}

void ProxySSLConnection::on_write()
{
    handle_callback(&_write_handler);
}

void ProxySSLConnection::handle_callback(handler_function *handler)
{
    if (*handler)
    {
        handler_function the_handler = *handler;
        *handler = NULL;
        (this->*the_handler)();
        schedule_send_buffer();
    }
}

If you need anything else to help you understand how my proxy works please
let me know and I will be happy to provide what I can.  I know that this can
be confusing, but I tried to lay it out in as logical of an order as
possible.  It's hard to type english descriptions of code.  Thank you for
any help you can give.

     -Sam

On Tue, Aug 31, 2010 at 9:59 PM, David Schwartz <dav...@webmaster.com>wrote:

>
> > I'm writing a SSL proxy (which is working great except for this issue)
> > and every time I got to attach a file in an email the connection resets,
> > and it gets caught in an infinite retransmit loop.
>
> There are two totally different ways you can make an SSL proxy, and to
> figure out your issue, we really need to know which type.
>
> 1) An SSL proxy can understand the underlying protocol, know which side is
> supposed to transmit when, and only try to read from that side. In this
> case, it's vital that the proxy correctly track the protocol and not be
> reading from one side when it's the other side's turn to send.
>
> 2) An SSL proxy can ignore the underlying protocol and not know which side
> is supposed to transmit when. In this case, the proxy must always be ready
> to read from either side. It must never block indefinitely trying to read
> from one side.
>
> You can also have a hybrid. For example, you can read only from the client
> side until you get the full request, and then once you process the request,
> you switch to bidirectional proxying.
>
> It is very common for people to naively assume that their code will
> magically know which side to read from. I assure, this is not the case.
> Unless you carefully track the protocol, all you know is that the client has
> to send some data first. But once it does, all bets are off -- again, unless
> you carefully track the protocol.
>
> Also, you don't mention whether your I/O is blocking or non-blocking, and
> if non-blocking, how your socket discovery works. This can be subtle with
> OpenSSL and your mistake might lie there. For example, if you using blocking
> I/O, you can't just block one thread in SSL_read in each direction, because
> if you do, there's nothing you can do when SSL_read returns (since the
> connection you need to send on is in use, potentially indefinitely, by the
> other thread).
>
> DS
>
> ______________________________________________________________________
> OpenSSL Project                                 http://www.openssl.org
> User Support Mailing List                    openssl-users@openssl.org
> Automated List Manager                           majord...@openssl.org
>



-- 
Sam Jantz
Software Engineer

Reply via email to