upnp.h 7.93 KB
Newer Older
Stepan Salenikovich's avatar
Stepan Salenikovich committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/*
 *  Copyright (C) 2004-2014 Savoir-Faire Linux Inc.
 *  Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
 *
 *  This program 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.
 *
 *  This program 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 this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
 *
 *  Additional permission under GNU GPL version 3 section 7:
 *
 *  If you modify this program, or any covered work, by linking or
 *  combining it with the OpenSSL project's OpenSSL library (or a
 *  modified version of that library), containing parts covered by the
 *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
 *  grants you additional permission to convey the resulting work.
 *  Corresponding Source for a non-source form of such a combination
 *  shall include the source code for the parts of OpenSSL used as well
 *  as that of the covered work.
 */

#ifndef UPNP_H_
#define UPNP_H_

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <string>
#include <map>
#include <memory>

#include "noncopyable.h"
#include "logger.h"
#include "ip_utils.h"

#if HAVE_UPNP
#include <miniupnpc/miniupnpc.h>
#endif

namespace ring { namespace upnp {

/* defines a UPnP capable Internet Gateway Device (a router) */
class IGD {
public:

#if HAVE_UPNP

58
59
60
    /* constructors */
    IGD() : datas_(), urls_() {};
    IGD(IGDdatas& d , UPNPUrls& u) : datas_(d), urls_(u) {};
Stepan Salenikovich's avatar
Stepan Salenikovich committed
61
62

    /* move constructor and operator */
63
64
    IGD(IGD&& other);
    IGD& operator=(IGD&& other);
Stepan Salenikovich's avatar
Stepan Salenikovich committed
65
66
67

    ~IGD();

68
69
    const IGDdatas& getDatas() const {return datas_;};
    const UPNPUrls& getURLs() const {return urls_;};
Stepan Salenikovich's avatar
Stepan Salenikovich committed
70
71
72
73
74
75
76
77
78
#else
    /* use default constructor and destructor */
#endif
    bool isEmpty() const;

private:
    NON_COPYABLE(IGD);

#if HAVE_UPNP
79
80
    IGDdatas datas_;
    UPNPUrls urls_;
Stepan Salenikovich's avatar
Stepan Salenikovich committed
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#endif

};

enum class PortType {UDP,TCP};

/* defines a UPnP port mapping */
class Mapping {
public:
    constexpr static char const * UPNP_DEFAULT_MAPPING_DESCRIPTION = "RING";
    /* TODO: what should the port range really be?
     * Should it be the ephemeral ports as defined by the system?
     */
    constexpr static uint16_t UPNP_PORT_MIN = 1024;
    constexpr static uint16_t UPNP_PORT_MAX = 65535;

    std::shared_ptr<const IGD> igd; /* the IGD associated with this mapping */
    IpAddr local_ip; /* the destination of the mapping */
    uint16_t port_external;
    uint16_t port_internal;
    PortType type; /* UPD or TCP */
    std::string description;

    Mapping(
        std::shared_ptr<const IGD> igd = std::make_shared<const IGD>(),
        IpAddr local_ip = IpAddr(),
        uint16_t port_external = 0,
        uint16_t port_internal = 0,
        PortType type = PortType::UDP,
        std::string description = UPNP_DEFAULT_MAPPING_DESCRIPTION)
    : igd(igd)
    , local_ip(local_ip)
    , port_external(port_external)
    , port_internal(port_internal)
    , type(type)
    , description(description)
    {};

    /* move constructor and operator */
Guillaume Roguez's avatar
Guillaume Roguez committed
120
    Mapping(Mapping&&) = default;
Stepan Salenikovich's avatar
Stepan Salenikovich committed
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
    Mapping& operator=(Mapping&&) = default;

    friend bool operator== (Mapping &cRedir1, Mapping &cRedir2);
    friend bool operator!= (Mapping &cRedir1, Mapping &cRedir2);

    std::string getExternalPort() const {return std::to_string(port_external);};
    std::string getInternalPort() const {return std::to_string(port_internal);};
    std::string getType() const { return type == PortType::UDP ? "UDP" : "TCP";};
    std::string toString() const {
        return local_ip.toString() + ", " + getExternalPort() + ":" + getInternalPort() + ", " + getType();
    };

    bool isValid() const {
        return igd->isEmpty() or port_external == 0 or port_internal == 0 ? false : true;
    };
private:
    NON_COPYABLE(Mapping);
};

/**
 * GlobalMapping is like a mapping, but it tracks the number of global users,
 * ie: the number of upnp:Controller which are using this mapping
 * this is usually only relevant for accounts (not calls) as multiple SIP accounts
 * can use the same SIP port and we don't want to delete a mapping from the router
 * if other accounts are using it
 */
class GlobalMapping : public Mapping {
public:
    /* number of users of this mapping;
     * this is only relevant when multiple accounts are using the same SIP port */
    unsigned users;
    GlobalMapping(const Mapping& mapping, unsigned users = 1)
        : Mapping(mapping.igd, mapping.local_ip, mapping.port_external, mapping.port_internal, mapping.type, mapping.description)
        , users(users)
    {};
};

class Controller {
public:
    /* constructor */
    Controller();
    /* destructor */
    ~Controller();

165
166
167
168
169
170
    /**
     * Return whether or not this controller has a valid IGD,
     * if 'flase' then all requests will fail
     */
    bool hasValidIGD();

Stepan Salenikovich's avatar
Stepan Salenikovich committed
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
    /**
     * tries to add mapping from and to the port_desired
     * if unique == true, makes sure the client is not using this port already
     * if the mapping fails, tries other available ports until success
     *
     * tries to use a random port between 1024 < > 65535 if desired port fails
     *
     * maps port_desired to port_local; if use_same_port == true, makes sure that
     * that the extranl and internal ports are the same
     */
    bool addAnyMapping(uint16_t port_desired, uint16_t port_local, PortType type, bool use_same_port, bool unique, uint16_t *port_used);

    /**
     * addAnyMapping with the local port being the same as the external port
     */
    bool addAnyMapping(uint16_t port_desired, PortType type, bool unique, uint16_t *port_used);

    /**
     * removes all mappings added by this instance
     */
    void removeMappings();

    /**
     * removes all mappings with the local IP and the given description
     */
    void removeMappingsByLocalIPAndDescription(const std::string& description = Mapping::UPNP_DEFAULT_MAPPING_DESCRIPTION);

    /**
     * tries to get the external ip of the IGD (router)
     */
    IpAddr getExternalIP();

private:
    /* subclasses to make it easier to differentiate and cast maps of port mappings */
    class PortMapLocal : public std::map<uint16_t, Mapping> {};
    class UDPMapLocal : public PortMapLocal {};
    class TCPMapLocal : public PortMapLocal {};
    class PortMapGlobal : public std::map<uint16_t, GlobalMapping> {};
    class UDPMapGlobal: public PortMapGlobal {};
    class TCPMapGlobal : public PortMapGlobal {};

    /**
     * The IGD being used by this instance;
     * TODO: Currently, we assume that the IGD will not change once it has been selected;
     *       however, the user could switch routers while the client is running
     */
    std::shared_ptr<IGD> defaultIGD_;

    /**
     * list of mappings created by this instance
     * the key is the external port number, as there can only be one mapping
     * at a time for each external port
     */
    UDPMapLocal udpInstanceMappings_;
    TCPMapLocal tcpInstanceMappings_;

    /**
     * list of all mappings
     */
    std::shared_ptr<UDPMapGlobal> udpGlobalMappings_;
    std::shared_ptr<TCPMapGlobal> tcpGlobalMappings_;

    /**
     * tries to add mapping
     */
    bool addMapping(uint16_t port_external, uint16_t port_internal, PortType type = PortType::UDP, int *upnp_error = nullptr);

    /**
     * Try to remove all mappings of the given type
     */
    void removeMappings(PortType type);

    /**
     * chooses a random port that is not yet used by the daemon for UPnP
     */
    uint16_t chooseRandomPort(PortType type);
};

std::shared_ptr<IGD> getIGD();

}} // namespace ring::upnp

Guillaume Roguez's avatar
Guillaume Roguez committed
253
#endif /* UPNP_H_ */