429 lines
10 KiB
C
429 lines
10 KiB
C
|
|
#include "announce.h"
|
|
#include "network.h"
|
|
|
|
#include <time.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
|
|
#include <avahi-client/client.h>
|
|
#include <avahi-client/lookup.h>
|
|
#include <avahi-client/publish.h>
|
|
|
|
#include <avahi-common/alternative.h>
|
|
#include <avahi-common/error.h>
|
|
#include <avahi-common/malloc.h>
|
|
#include <avahi-common/thread-watch.h>
|
|
|
|
#define SERVICE_TYPE "_sdlbomber._udp"
|
|
|
|
static AvahiClient *client = NULL;
|
|
static AvahiThreadedPoll *threaded_poll = NULL;
|
|
static AvahiEntryGroup *group = NULL;
|
|
static AvahiServiceBrowser *browser = NULL;
|
|
|
|
static char* name = NULL;
|
|
|
|
static uint16_t port = 0;
|
|
static uint32_t version = 0;
|
|
|
|
static gamelistentry buffer_glentries[10];
|
|
static int buffer_glsize = 0;
|
|
static char buffer_glchanged[10];
|
|
|
|
gamelistentry gamelistentries[10];
|
|
int gamelistsize = 0;
|
|
|
|
static void myerror(AvahiClient *c, const char *s) {
|
|
fprintf(stderr, "Error in: %s\n (%s)", s, avahi_strerror(avahi_client_errno(client)));
|
|
}
|
|
|
|
static void create_services(AvahiClient *c);
|
|
|
|
static void entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata) {
|
|
group = g;
|
|
|
|
switch (state) {
|
|
case AVAHI_ENTRY_GROUP_ESTABLISHED :
|
|
break;
|
|
|
|
case AVAHI_ENTRY_GROUP_COLLISION :
|
|
{
|
|
char *n = avahi_alternative_service_name(name);
|
|
avahi_free(name);
|
|
name = n;
|
|
}
|
|
|
|
/* And recreate the services */
|
|
avahi_entry_group_reset(group);
|
|
create_services(avahi_entry_group_get_client(g));
|
|
break;
|
|
|
|
case AVAHI_ENTRY_GROUP_FAILURE :
|
|
fprintf(stderr, "Entry group failure: %s\n", avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))));
|
|
|
|
avahi_threaded_poll_quit(threaded_poll);
|
|
break;
|
|
|
|
case AVAHI_ENTRY_GROUP_UNCOMMITED:
|
|
case AVAHI_ENTRY_GROUP_REGISTERING:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void create_services(AvahiClient *c) {
|
|
int ret;
|
|
|
|
if (!group) {
|
|
if (!(group = avahi_entry_group_new(c, entry_group_callback, NULL))) {
|
|
myerror(c, "avahi_entry_group_new");
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
again:
|
|
if (avahi_entry_group_is_empty(group)) {
|
|
char buf_version[128];
|
|
snprintf(buf_version, sizeof(buf_version), "version=%X", (unsigned int) version);
|
|
if ((ret = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, name, SERVICE_TYPE, NULL, NULL, port, buf_version, NULL)) < 0) {
|
|
|
|
if (ret == AVAHI_ERR_COLLISION)
|
|
goto collision;
|
|
|
|
fprintf(stderr, "Failed to add "SERVICE_TYPE": %s\n", avahi_strerror(ret));
|
|
goto fail;
|
|
}
|
|
|
|
if ((ret = avahi_entry_group_commit(group)) < 0) {
|
|
fprintf(stderr, "Failed to commit entry group: %s\n", avahi_strerror(ret));
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
return;
|
|
|
|
collision:
|
|
{
|
|
char *n = avahi_alternative_service_name(name);
|
|
avahi_free(name);
|
|
name = n;
|
|
}
|
|
avahi_entry_group_reset(group);
|
|
goto again;
|
|
|
|
fail:
|
|
avahi_threaded_poll_quit(threaded_poll);
|
|
}
|
|
|
|
static void client_callback(AvahiClient *c, AvahiClientState state, void * userdata) {
|
|
client = c;
|
|
switch (state) {
|
|
case AVAHI_CLIENT_S_RUNNING:
|
|
if (port != 0) create_services(c);
|
|
break;
|
|
case AVAHI_CLIENT_FAILURE:
|
|
myerror(c, "client failure");
|
|
avahi_threaded_poll_quit(threaded_poll);
|
|
break;
|
|
case AVAHI_CLIENT_S_COLLISION:
|
|
case AVAHI_CLIENT_S_REGISTERING:
|
|
if (group) {
|
|
avahi_entry_group_reset(group);
|
|
}
|
|
break;
|
|
case AVAHI_CLIENT_CONNECTING:
|
|
break;
|
|
}
|
|
}
|
|
|
|
int registergame(const char *playername, uint16_t p, const unsigned char v[4]) {
|
|
if (name) avahi_free(name);
|
|
name = avahi_strdup(playername);
|
|
|
|
port = p;
|
|
memcpy(&version, v, 4);
|
|
version = htonl(version);
|
|
|
|
avahi_threaded_poll_lock(threaded_poll);
|
|
create_services(client);
|
|
avahi_threaded_poll_unlock(threaded_poll);
|
|
|
|
return 1;
|
|
}
|
|
|
|
void unregistergame(void) {
|
|
port = 0;
|
|
|
|
avahi_threaded_poll_lock(threaded_poll);
|
|
if (group) avahi_entry_group_reset(group);
|
|
group = NULL;
|
|
avahi_threaded_poll_unlock(threaded_poll);
|
|
}
|
|
|
|
static void remove_game_with_name(const char *name) {
|
|
int i;
|
|
|
|
for (i = 0; i < buffer_glsize; i++) {
|
|
if (0 == strcmp(buffer_glentries[i].name, name)) {
|
|
/* Remove it */
|
|
buffer_glsize--;
|
|
if (i != buffer_glsize) {
|
|
buffer_glentries[i] = buffer_glentries[buffer_glsize];
|
|
buffer_glchanged[i] = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void resolve_callback(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtocol protocol, AvahiResolverEvent event,
|
|
const char *name, const char *type, const char *domain, const char *host_name, const AvahiAddress *address,
|
|
uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags flags, void* userdata) {
|
|
int i;
|
|
uint32_t want_version;
|
|
assert(r);
|
|
|
|
if (protocol != AVAHI_PROTO_INET) goto done; /* ignore non IPv4 for now */
|
|
if (buffer_glsize >= GAMELIST_MAXSIZE) goto done;
|
|
|
|
memcpy(&want_version, gameversion, 4);
|
|
want_version = htonl(want_version);
|
|
|
|
/* Called whenever a service has been resolved successfully or timed out */
|
|
|
|
switch (event) {
|
|
case AVAHI_RESOLVER_FAILURE:
|
|
fprintf(stderr, "(Resolver) Failed to resolve service '%s' of type '%s' in domain '%s': %s\n", name, type, domain, avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r))));
|
|
break;
|
|
|
|
case AVAHI_RESOLVER_FOUND: {
|
|
gamelistentry *ge;
|
|
unsigned int version;
|
|
int have_version = 0;
|
|
AvahiStringList *psl;
|
|
for (psl = txt ; psl ; psl = psl->next) {
|
|
if (0 == strncmp("version=", (const char*) psl->text, 8)) {
|
|
sscanf((const char*) psl->text, "version=%X", &version);
|
|
if (version != want_version) goto done; /* version mismatch */
|
|
have_version = 1;
|
|
}
|
|
}
|
|
if (!have_version) goto done;
|
|
remove_game_with_name(name);
|
|
i = buffer_glsize++;
|
|
ge = &buffer_glentries[i];
|
|
buffer_glchanged[i] = 1;
|
|
memset(ge, 0, sizeof(*ge));
|
|
ge->netname.sin_addr.s_addr = address->data.ipv4.address;
|
|
ge->netname.sin_family = AF_INET;
|
|
ge->netname.sin_port = port;
|
|
strncpy(ge->name, name, 15);
|
|
}
|
|
}
|
|
|
|
done:
|
|
avahi_service_resolver_free(r);
|
|
}
|
|
|
|
static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event,
|
|
const char *name, const char *type, const char *domain, AvahiLookupResultFlags flags, void* userdata) {
|
|
|
|
assert(b);
|
|
|
|
/* Called whenever a new services becomes available on the LAN or is removed from the LAN */
|
|
|
|
switch (event) {
|
|
case AVAHI_BROWSER_FAILURE:
|
|
|
|
fprintf(stderr, "(Browser) %s\n", avahi_strerror(avahi_client_errno(client)));
|
|
avahi_threaded_poll_quit(threaded_poll);
|
|
return;
|
|
|
|
case AVAHI_BROWSER_NEW:
|
|
/* fprintf(stderr, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain); */
|
|
|
|
/* We ignore the returned resolver object. In the callback
|
|
function we free it. If the server is terminated before
|
|
the callback function is called the server will free
|
|
the resolver for us. */
|
|
|
|
if (!(avahi_service_resolver_new(client, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, resolve_callback, userdata)))
|
|
fprintf(stderr, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_client_errno(client)));
|
|
|
|
break;
|
|
|
|
case AVAHI_BROWSER_REMOVE:
|
|
remove_game_with_name(name);
|
|
break;
|
|
|
|
case AVAHI_BROWSER_ALL_FOR_NOW:
|
|
break;
|
|
case AVAHI_BROWSER_CACHE_EXHAUSTED:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void freefoundgames(void) {
|
|
memset(gamelistentries, 0, sizeof(gamelistentries));
|
|
memset(buffer_glentries, 0, sizeof(buffer_glentries));
|
|
memset(buffer_glchanged, 0, sizeof(buffer_glchanged));
|
|
|
|
gamelistsize = 0;
|
|
buffer_glsize = 0;
|
|
}
|
|
|
|
int searchgames(void) {
|
|
freefoundgames();
|
|
|
|
avahi_threaded_poll_lock(threaded_poll);
|
|
|
|
if (NULL == (browser = avahi_service_browser_new(client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, SERVICE_TYPE, NULL, 0, browse_callback, NULL))) {
|
|
fprintf(stderr, "Failed to create service browser: %s\n", avahi_strerror(avahi_client_errno(client)));
|
|
avahi_threaded_poll_unlock(threaded_poll);
|
|
return 0;
|
|
}
|
|
|
|
avahi_threaded_poll_unlock(threaded_poll);
|
|
|
|
usleep(200000);
|
|
find_more_games();
|
|
|
|
return 1;
|
|
}
|
|
|
|
int find_more_games(void) {
|
|
int i, res = 0;
|
|
|
|
avahi_threaded_poll_lock(threaded_poll);
|
|
|
|
for (i = 0; i < buffer_glsize; i++) {
|
|
if (!buffer_glchanged[i]) continue;
|
|
buffer_glchanged[i] = 0;
|
|
gamelistentries[i] = buffer_glentries[i];
|
|
res = 1;
|
|
}
|
|
if (gamelistsize != buffer_glsize) {
|
|
res = 1;
|
|
gamelistsize = buffer_glsize;
|
|
}
|
|
|
|
avahi_threaded_poll_unlock(threaded_poll);
|
|
|
|
return res;
|
|
}
|
|
|
|
void stop_search(void) {
|
|
avahi_threaded_poll_lock(threaded_poll);
|
|
avahi_service_browser_free(browser);
|
|
avahi_threaded_poll_unlock(threaded_poll);
|
|
}
|
|
|
|
int initannouncer(void) {
|
|
if (!(threaded_poll = avahi_threaded_poll_new())) {
|
|
fprintf(stderr, "avahi_threaded_poll_new failed\n");
|
|
return 0;
|
|
}
|
|
|
|
if (!(client = avahi_client_new(avahi_threaded_poll_get(threaded_poll), 0, client_callback, NULL, NULL))) {
|
|
fprintf(stderr, "avahi_client_new failed\n");
|
|
avahi_threaded_poll_free(threaded_poll);
|
|
threaded_poll = NULL;
|
|
return 0;
|
|
}
|
|
|
|
if (avahi_threaded_poll_start(threaded_poll) < 0) {
|
|
fprintf(stderr, "avahi_threaded_poll_start failed\n");
|
|
avahi_client_free(client);
|
|
avahi_threaded_poll_free(threaded_poll);
|
|
client = NULL;
|
|
threaded_poll = NULL;
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
void freeannouncer(void) {
|
|
freefoundgames();
|
|
|
|
avahi_threaded_poll_stop(threaded_poll);
|
|
if (client) avahi_client_free(client);
|
|
if (threaded_poll) avahi_threaded_poll_free(threaded_poll);
|
|
client = NULL;
|
|
threaded_poll = NULL;
|
|
|
|
avahi_free(name);
|
|
name = NULL;
|
|
}
|
|
|
|
#if 0
|
|
int openmatcher()
|
|
{
|
|
struct hostent *hostptr;
|
|
if(matcheropened) return 1;
|
|
hostptr=gethostbyname(mname);
|
|
if(!hostptr)
|
|
{
|
|
hostptr=gethostbyaddr(mname,strlen(mname),AF_INET);
|
|
if(!hostptr)
|
|
return 0;
|
|
}
|
|
memset(&matchername,0,sizeof(matchername));
|
|
matchername.sin_family=AF_INET;
|
|
matchername.sin_port=htons(PORT);
|
|
memcpy(&matchername.sin_addr,hostptr->h_addr,hostptr->h_length);
|
|
matcheropened=1;
|
|
return 1;
|
|
}
|
|
|
|
int registergame()
|
|
{
|
|
long now;
|
|
int size;
|
|
long lastreg;
|
|
|
|
if(!openmatcher()) return 0;
|
|
pulseoff();
|
|
now=longtime();
|
|
lastreg=now-1;
|
|
while(longtime()-now<10)
|
|
{
|
|
if(longtime()-lastreg>=1)
|
|
{
|
|
lastreg=longtime();
|
|
putmsg(&matchername,regpacket,REGISTERLEN);
|
|
}
|
|
size=getmsg(1000);
|
|
if(size<REGISTERLEN+1) continue;
|
|
if(mesg[0]!=PKT_ACK) continue;
|
|
if(memcmp(regpacket,mesg+1,REGISTERLEN)) continue;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
int unregistergame()
|
|
{
|
|
long now;
|
|
int size;
|
|
|
|
|
|
if(!openmatcher()) return 0;
|
|
pulseoff();
|
|
now=longtime();
|
|
clearreg();
|
|
while(longtime()-now<10)
|
|
{
|
|
putmsg(&matchername,regpacket,REGISTERLEN);
|
|
size=getmsg(1000);
|
|
if(size<REGISTERLEN+1) continue;
|
|
if(mesg[0]!=PKT_ACK) continue;
|
|
if(memcmp(regpacket,mesg+1,REGISTERLEN)) continue;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
|
|
}
|
|
#endif
|