listen.c 5.58 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
/*
    Copyright (C) 2016  Jeremy White <jwhite@codeweavers.com>
    All rights reserved.

    This file is part of x11spice

    x11spice is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    x11spice is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with x11spice.  If not, see <http://www.gnu.org/licenses/>.
*/

21
/*----------------------------------------------------------------------------
22 23 24
**  listen.c
**      This file provides functions to listen for the address given.
**  This mostly involves trying to find an open port we can use for our server
25 26
**--------------------------------------------------------------------------*/

27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <errno.h>

42
#include "listen.h"
43 44 45 46
#include "x11spice.h"

#define SPICE_URI_PREFIX    "spice://"

47
int listen_parse(const char *listen_spec, char **addr, int *port_start, int *port_end)
48 49 50 51 52 53 54 55 56 57 58
{
    int leading = 0;
    int trailing = 0;
    int hyphen = 0;
    const char *p;
    int len;

    *port_start = *port_end = -1;
    *addr = NULL;

    /* Allow form of spice:// */
59 60 61
    if (strlen(listen_spec) > strlen(SPICE_URI_PREFIX))
        if (memcmp(listen_spec, SPICE_URI_PREFIX, strlen(SPICE_URI_PREFIX)) == 0)
            listen_spec += strlen(SPICE_URI_PREFIX);
62

63
    p = listen_spec + strlen(listen_spec) - 1;
64
    /* Look for a form of NNNN-NNNN at the end of the line */
65
    for (; p >= listen_spec && *p; p--) {
66
        /* Skip trailing white space */
67
        if (isspace(*p) && !hyphen && !trailing)
68
            continue;
69

70 71 72 73
        /* We're looking for only digits and a hyphen */
        if (*p != '-' && !isdigit(*p))
            break;

74
        if (*p == '-') {
75
            if (hyphen)
76 77 78 79 80
                return X11SPICE_ERR_PARSE;
            hyphen++;
            if (trailing > 0)
                *port_end = strtol(p + 1, NULL, 0);
            continue;
81
        }
82 83 84 85 86

        if (hyphen)
            leading++;
        else
            trailing++;
87 88 89 90 91 92 93 94 95
    }

    if (leading && hyphen)
        *port_start = strtol(p + 1, NULL, 0);

    if (trailing && !hyphen)
        *port_end = strtol(p + 1, NULL, 0);

    /* If we had a hyphen, make sure we had a NNNN-NNN pattern too... */
96
    if (hyphen && (!leading || !trailing))
97 98 99
        return X11SPICE_ERR_PARSE;

    /* If we got a port range, make sure we had either no address provided,
100
       or a clear addr:NNNN-NNNN specficiation */
101
    if (leading || trailing)
102
        if (p > listen_spec && *p != ':')
103 104
            return X11SPICE_ERR_PARSE;

105
    if (p > listen_spec && *p == ':')
106 107
        p--;

108
    len = p - listen_spec + 1;
109
    if (len > 0) {
110
        *addr = calloc(1, len + 1);
111
        memcpy(*addr, listen_spec, len);
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
    }

    return 0;
}

static int try_port(const char *addr, int port)
{
    static const int on = 1, off = 0;
    struct addrinfo ai, *res, *e;
    char portbuf[33];
    int sock, rc;

    memset(&ai, 0, sizeof(ai));
    ai.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
    ai.ai_socktype = SOCK_STREAM;
    ai.ai_family = 0;

    snprintf(portbuf, sizeof(portbuf), "%d", port);
    rc = getaddrinfo(addr && strlen(addr) ? addr : NULL, portbuf, &ai, &res);
    if (rc != 0)
        return -1;

    for (e = res; e != NULL; e = e->ai_next) {
        sock = socket(e->ai_family, e->ai_socktype, e->ai_protocol);
        if (sock < 0)
            continue;

139
        setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *) &on, sizeof(on));
140 141
        /* listen on both ipv4 and ipv6 */
        if (e->ai_family == PF_INET6)
142
            setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (void *) &off, sizeof(off));
143 144

        if (bind(sock, e->ai_addr, e->ai_addrlen) == 0) {
145
            char uaddr[INET6_ADDRSTRLEN + 1];
146
            char uport[33];
147
            rc = getnameinfo((struct sockaddr *) e->ai_addr, e->ai_addrlen,
148 149 150 151 152 153 154 155 156 157 158
                             uaddr, INET6_ADDRSTRLEN, uport, sizeof(uport) - 1,
                             NI_NUMERICHOST | NI_NUMERICSERV);
            if (rc == 0)
                printf("bound to %s:%s\n", uaddr, uport);
            else
                printf("cannot resolve address spice-server is bound to\n");

            freeaddrinfo(res);
            goto listen;
        }
        close(sock);
159 160 161 162 163 164 165

        /*
        **  Oddly, you seem to get situations where the ipv6 bind will fail,
        **   with address in use; you can then try again and bind to the ipv4,
        **   but you then go on to get other failures.
        */
        break;
166 167 168 169 170 171 172
    }

    freeaddrinfo(res);
    return -1;

listen:
    if (listen(sock, SOMAXCONN) != 0) {
173
        perror("Error in listen");
174 175 176 177 178 179 180
        close(sock);
        return -1;
    }

    return sock;
}

181
int listen_find_open_port(const char *addr, int start, int end, int *port)
182 183 184 185 186 187 188 189 190
{
    int i;
    int rc;

    if (start == -1)
        start = end;
    if (end == -1)
        end = start;

191
    for (i = start; i <= end; i++) {
192
        rc = try_port(addr, i);
193
        if (rc >= 0) {
194
            *port = i;
195 196 197 198 199 200 201
            printf("URI=%s:%d\n", addr ? addr : "", i);
            return rc;
        }
    }

    return -1;
}