Hi.

I'm trying to implement UDP service (TFTP, etc) as an Apache module,
and would like to know official (= future compatible) way to
implement it.

After looking into 2.2.4-dev source, I came up with two different
implementations. Both works to some degree, with different drawbacks.

Type 1 implementation hooks into two very early stages to do it:
"create_server_config" and "command handler". By hooking in
early enough, I can assure UDP socket to exist in to-be-closed
"old_listeners", and revive it back to "ap_listeners" by calling
"ap_set_listener" explicitly (which is a handler for "Listen"
directive). This way, I can make UDP and TCP with the same port
number to coexist.

However, there's a drawback. In error_log file, I see lot's of

  (111)Connection refused: connect to listener on 0.0.0.0:<udpport>

errors generated by "dummy_connection" (in mpm_common.c). Also, code
isn't clean enough because some key structures aren't ready yet at
this stage - I need to use static variables so I can do some fixup
work at later stage.

Type 2 implementation deals with the above by using "Listen <udpport>"
as a helper directive. By letting "Listen <udpport>" allocate a slot
in "ap_listeners" early enough, UDP initialization can be delayed
back to "open_log" stage.

But as a drawback, I end up with unwanted extra HTTP service running
on TCP port with the same port number as UDP.

For complete module implementation, I still need to find out how
can I do bucket handling, logging, etc. But before going further,
I decided it'd be safer to ask for a recommended way (or at least
a hint for it).

Can anyone advise me on this?
Thanks in advance.
--
Taisuke Yamada <[EMAIL PROTECTED]>, http://rakugaki.org/
2268 E9A2 D4F9 014E F11D 1DF7 DCA3 83BC 78E5 CD3A

Message to my public address may not be handled in a timely manner.
For a direct contact, please use my private address on my namecard.
/**
 * Experimental UDP handling module for Apache 2.2
 * This version relies on built-in "Listen" directive.
 *
 * @version 0.02
 * @author  Taisuke Yamada
 */

#include "ap_listen.h"
#include "http_log.h"
#include "apr_buckets.h"
#include "util_filter.h"

module AP_MODULE_DECLARE_DATA udp_echo_module;

typedef struct {
    int port;
} udp_echo_config;

/**
 * "echo" UDP protocol handler
 */
static apr_status_t
udp_accept(void **csd, ap_listen_rec *lr, apr_pool_t *p) {
    char *buf;
    apr_size_t len = APR_BUCKET_BUFF_SIZE;
    
    buf = apr_pcalloc(p, len);
    apr_socket_recvfrom(lr->bind_addr, lr->sd, 0, buf, &len);
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, "recv: %d", len);
    apr_socket_sendto(lr->sd, lr->bind_addr, 0, buf, &len);
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, "send: %d", len);

    // Avoid bucket handling by returning an error.
    // It's not that bucket handling code fails with UDP (haven't tried yet)
    // - it's just I wanted simplicity for faster feasibility test.
    // I guess APR_SUCCESS or APR_EGENERAL is what's really expected.
    return APR_EOF;
}

/**
 * Simpler version of "alloc_listener" in listen.c.
 * I have to do it myself as built-in version always allocates
 * a socket as SOCK_STREAM socket.
 */
static ap_listen_rec *
alloc_listener_stub(apr_pool_t *p, int port) {
    ap_listen_rec  *new;
    apr_status_t rc;
    apr_sockaddr_t *sa;

    apr_sockaddr_info_get(&sa, NULL, APR_INET, port, 0, p);

    new = apr_palloc(p, sizeof(ap_listen_rec));
    new->accept_func = udp_accept;
    new->active      = 1;
    new->next        = NULL;
    new->bind_addr   = sa; /* dummy */
    new->protocol    = (const char *)apr_pstrdup(p, "udp");

    return new;
}

/**
 * Prepare socket as UDP socket. Error handling removed for clarity.
 */
static apr_status_t
config_socket(apr_pool_t *p, ap_listen_rec *lr, int port) {
    apr_sockaddr_t *sa;
    int one = 1;

    apr_sockaddr_info_get(&sa, NULL, APR_INET, port, 0, p);
    lr->bind_addr = sa;
    apr_socket_create(&lr->sd, lr->bind_addr->family, SOCK_DGRAM, 0, p);
    apr_socket_opt_set(lr->sd, APR_SO_NONBLOCK, 1);
    apr_socket_bind(lr->sd, lr->bind_addr);
    apr_socket_opt_set(lr->sd, APR_SO_REUSEADDR, one);

    return APR_SUCCESS;
}

static void *
create_server_config(apr_pool_t *p, server_rec *s) {
    udp_echo_config *config = apr_pcalloc(p, sizeof *config);
    config->port = 0;
    return config;
}

/**
 * Handler for "UDPEchoPort <port>" directive.
 */
static const char *
config_port(cmd_parms *cmd, void *dummy, const char *value) {
    udp_echo_config *config =
        ap_get_module_config(cmd->server->module_config, &udp_echo_module);

    config->port = atoi(value);

    return NULL;
}

static int
open_logs(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) {
    udp_echo_config *config =
        ap_get_module_config(s->module_config, &udp_echo_module);
    ap_listen_rec *lr, *last;

    if (config->port <= 0) return OK;

    // setup UDP port
    lr = alloc_listener_stub(s->process->pool, config->port);
    config_socket(s->process->pool, lr, config->port);

    // This code won't work unless "Listen <udpport>" is also set.
    // By having helper "Listen <udpport>", socket created here
    // will revive from "old_listeners" when "ap_setup_listeners" is
    // called from MPM.
    //
    // As a sideeffect of using "Listen" directive, TCP port with
    // the same port number will be opened for HTTP service.
    // I don't like it, but it seems this is needed by "dummy_connection"
    // in mpm_common.c.
    for (last = ap_listeners; last && last->next; last = last->next);
    if (last == NULL) { ap_listeners = lr; } else { last->next = lr; }

    return OK;
}

static void
register_hooks(apr_pool_t *p) {
    ap_hook_open_logs(open_logs,  NULL, NULL, APR_HOOK_MIDDLE);
}

static const command_rec
commands[] = {
    AP_INIT_TAKE1("UDPEchoPort", config_port, NULL, RSRC_CONF,
                  "Run an UDP echo server on this port"),
    { NULL }
};

module AP_MODULE_DECLARE_DATA udp_echo_module = {
    STANDARD20_MODULE_STUFF,
    NULL,                  /* create per-directory config structure */
    NULL,                  /* merge per-directory config structures */
    create_server_config,  /* create per-server config structure */
    NULL,                  /* merge per-server config structures */
    commands,              /* command apr_table_t */
    register_hooks         /* register hooks */
};
/**
 * Experimental UDP handling module for Apache 2.2
 *
 * @version 0.01
 * @author  Taisuke Yamada
 */

#include "ap_listen.h"
#include "http_log.h"
#include "apr_buckets.h"
#include "util_filter.h"

module AP_MODULE_DECLARE_DATA udp_echo_module;

typedef struct {
    int port;
} udp_echo_config;

static process_rec *GP;
static ap_listen_rec *LR;

/**
 * "echo" UDP protocol handler
 */
static apr_status_t
udp_accept(void **csd, ap_listen_rec *lr, apr_pool_t *p) {
    char *buf;
    apr_size_t len = APR_BUCKET_BUFF_SIZE;
    
    buf = apr_pcalloc(p, len);
    apr_socket_recvfrom(lr->bind_addr, lr->sd, 0, buf, &len);
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, "recv: %d", len);
    apr_socket_sendto(lr->sd, lr->bind_addr, 0, buf, &len);
    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, "send: %d", len);

    // Avoid bucket handling by returning an error.
    // It's not that bucket handling code fails with UDP (haven't tried yet)
    // - it's just I wanted simplicity for faster feasibility test.
    // I guess APR_SUCCESS or APR_EGENERAL is what's really expected.
    return APR_EOF;
}

/**
 * Simpler version of "alloc_listener" in listen.c.
 * I have to do it myself as built-in version always allocates
 * a socket as SOCK_STREAM socket.
 */
static ap_listen_rec *
alloc_listener_stub(apr_pool_t *p, int port) {
    ap_listen_rec  *new;
    apr_status_t rc;
    apr_sockaddr_t *sa;

    apr_sockaddr_info_get(&sa, NULL, APR_INET, port, 0, p);

    new = apr_palloc(p, sizeof(ap_listen_rec));
    new->accept_func = udp_accept;
    new->active      = 1;
    new->next        = NULL;
    new->bind_addr   = sa; /* dummy */
    new->protocol    = (const char *)apr_pstrdup(p, "udp");

    return new;
}

/**
 * Prepare UDP socket. Error handling removed for clarity.
 */
static apr_status_t
config_socket(apr_pool_t *p, ap_listen_rec *lr, int port) {
    apr_sockaddr_t *sa;
    int one = 1;

    apr_sockaddr_info_get(&sa, NULL, APR_INET, port, 0, p);
    lr->bind_addr = sa;
    apr_socket_create(&lr->sd, lr->bind_addr->family, SOCK_DGRAM, 0, p);
    apr_socket_opt_set(lr->sd, APR_SO_NONBLOCK, 1);
    apr_socket_bind(lr->sd, lr->bind_addr);
    apr_socket_opt_set(lr->sd, APR_SO_REUSEADDR, one);

    return APR_SUCCESS;
}

static void *
create_server_config(apr_pool_t *p, server_rec *s) {
    ap_listen_rec *last;
    udp_echo_config *config = apr_pcalloc(p, sizeof *config);

    // Push UDP socket into "ap_listeners".
    // By pushing it before "ap_listen_pre_config" call by MPM,
    // I can assure this socket to be in "old_listeners" by the time
    // I reach "config_port" command handler.
    GP = s->process;
    LR = alloc_listener_stub(GP->pool, 0);
    for (last = ap_listeners; last && last->next; last = last->next);
    if (last == NULL) { ap_listeners = LR; } else { last->next = LR; }

    config->port = 0;
    return config;
}

/**
 * Handler for "UDPEchoPort <port>" directive.
 */
static const char *
config_port(cmd_parms *cmd, void *dummy, const char *value) {
    char *args[] = { (char *)value };
    udp_echo_config *config =
        ap_get_module_config(cmd->server->module_config, &udp_echo_module);

    // Now the port is known, I can update stub "ap_listen_rec" entry
    // which should now be in "old_listeners".
    config->port = atoi(value);
    config_socket(GP->pool, LR, config->port);

    // ...and by this call, I can revive UDP socket in "old_listeners"
    // back to "ap_listeners" without creating TCP socket at the same port.
    ap_set_listener(cmd, dummy, 1, args);

    return NULL;
}

static const command_rec
commands[] = {
    AP_INIT_TAKE1("UDPEchoPort", config_port, NULL, RSRC_CONF,
                  "Run an UDP echo server on this port"),
    { NULL }
};

module AP_MODULE_DECLARE_DATA udp_echo_module = {
    STANDARD20_MODULE_STUFF,
    NULL,                  /* create per-directory config structure */
    NULL,                  /* merge per-directory config structures */
    create_server_config,  /* create per-server config structure */
    NULL,                  /* merge per-server config structures */
    commands,              /* command apr_table_t */
    NULL                   /* register hooks */
};

Reply via email to