upnp_igd.h 7.35 KB
Newer Older
Stepan Salenikovich's avatar
Stepan Salenikovich committed
1
/*
Sébastien Blin's avatar
Sébastien Blin committed
2
 *  Copyright (C) 2004-2019 Savoir-faire Linux Inc.
Guillaume Roguez's avatar
Guillaume Roguez committed
3
 *
Stepan Salenikovich's avatar
Stepan Salenikovich committed
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 *  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.
 */

Adrien Béraud's avatar
Adrien Béraud committed
21
22
23
24
25
#pragma once

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
Stepan Salenikovich's avatar
Stepan Salenikovich committed
26
27
28

#include <string>
#include <map>
29
#include <functional>
Adrien Béraud's avatar
Adrien Béraud committed
30
#include <chrono>
Stepan Salenikovich's avatar
Stepan Salenikovich committed
31
32
33

#include "noncopyable.h"
#include "ip_utils.h"
Adrien Béraud's avatar
Adrien Béraud committed
34
#include "string_utils.h"
Stepan Salenikovich's avatar
Stepan Salenikovich committed
35

Adrien Béraud's avatar
Adrien Béraud committed
36
namespace jami { namespace upnp {
Stepan Salenikovich's avatar
Stepan Salenikovich committed
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

enum class PortType {UDP,TCP};

/* defines a UPnP port mapping */
class Mapping {
public:
    constexpr static const char * 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;

    Mapping(
        uint16_t port_external = 0,
        uint16_t port_internal = 0,
        PortType type = PortType::UDP,
54
        const std::string& description = UPNP_DEFAULT_MAPPING_DESCRIPTION)
Eloi Bail's avatar
Eloi Bail committed
55
    : port_external_(port_external)
Stepan Salenikovich's avatar
Stepan Salenikovich committed
56
57
58
59
60
61
    , port_internal_(port_internal)
    , type_(type)
    , description_(description)
    {};

    /* move constructor and operator */
62
63
    Mapping(Mapping&&) noexcept;
    Mapping& operator=(Mapping&&) noexcept;
Stepan Salenikovich's avatar
Stepan Salenikovich committed
64
65
66

    ~Mapping() = default;

67
68
    friend bool operator== (const Mapping& cRedir1, const Mapping& cRedir2);
    friend bool operator!= (const Mapping& cRedir1, const Mapping& cRedir2);
Stepan Salenikovich's avatar
Stepan Salenikovich committed
69

Adrien Béraud's avatar
Adrien Béraud committed
70
    uint16_t      getPortExternal()    const { return port_external_; }
71
    std::string   getPortExternalStr() const { return std::to_string(port_external_); }
Adrien Béraud's avatar
Adrien Béraud committed
72
    uint16_t      getPortInternal()    const { return port_internal_; }
73
    std::string   getPortInternalStr() const { return std::to_string(port_internal_); }
Adrien Béraud's avatar
Adrien Béraud committed
74
    PortType      getType()            const { return type_; }
Stepan Salenikovich's avatar
Stepan Salenikovich committed
75
    std::string   getTypeStr()         const { return type_ == PortType::UDP ? "UDP" : "TCP"; }
Adrien Béraud's avatar
Adrien Béraud committed
76
    std::string   getDescription()     const { return description_; }
Stepan Salenikovich's avatar
Stepan Salenikovich committed
77
78

    std::string toString() const {
Eloi Bail's avatar
Eloi Bail committed
79
        return getPortExternalStr() + ":" + getPortInternalStr() + ", " + getTypeStr();
Stepan Salenikovich's avatar
Stepan Salenikovich committed
80
81
82
83
84
85
    };

    bool isValid() const {
        return port_external_ == 0 or port_internal_ == 0 ? false : true;
    };

86
    inline explicit operator bool() const {
Stepan Salenikovich's avatar
Stepan Salenikovich committed
87
88
89
        return isValid();
    }

Adrien Béraud's avatar
Adrien Béraud committed
90
91
92
93
94
#if HAVE_LIBNATPMP
    std::chrono::system_clock::time_point renewal_ {std::chrono::system_clock::time_point::min()};
    bool remove {false};
#endif

Stepan Salenikovich's avatar
Stepan Salenikovich committed
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
private:
    NON_COPYABLE(Mapping);

protected:
    uint16_t port_external_;
    uint16_t port_internal_;
    PortType type_; /* UPD or TCP */
    std::string description_;
};

/**
 * 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)
Eloi Bail's avatar
Eloi Bail committed
118
        : Mapping(mapping.getPortExternal()
Stepan Salenikovich's avatar
Stepan Salenikovich committed
119
120
121
122
123
124
125
126
127
128
129
        , mapping.getPortInternal()
        , mapping.getType()
        , mapping.getDescription())
        , users(users)
    {};
};

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

130
131
using IGDFoundCallback = std::function<void()>;

Stepan Salenikovich's avatar
Stepan Salenikovich committed
132
133
134
/* defines a UPnP capable Internet Gateway Device (a router) */
class IGD {
public:
Eloi Bail's avatar
Eloi Bail committed
135
136
137
    /* device address seen by IGD */
    IpAddr localIp;

Stepan Salenikovich's avatar
Stepan Salenikovich committed
138
139
140
141
142
143
144
145
    /* external IP of IGD; can change */
    IpAddr publicIp;

    /* port mappings associated with this IGD */
    PortMapGlobal udpMappings;
    PortMapGlobal tcpMappings;

    /* constructors */
Eloi Bail's avatar
Eloi Bail committed
146
    IGD() {}
Adrien Béraud's avatar
Adrien Béraud committed
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161

    /* move constructor and operator */
    IGD(IGD&&) = default;
    IGD& operator=(IGD&&) = default;

    virtual ~IGD() = default;

private:
    NON_COPYABLE(IGD);
};

#if HAVE_LIBUPNP

class UPnPIGD : public IGD {
public:
162
163
164
165
166
167
168
169
170
171
172
173
174
175
    UPnPIGD(std::string&& UDN,
        std::string&& baseURL,
        std::string&& friendlyName,
        std::string&& serviceType,
        std::string&& serviceId,
        std::string&& controlURL,
        std::string&& eventSubURL)
        : UDN_(std::move(UDN))
        , baseURL_(std::move(baseURL))
        , friendlyName_(std::move(friendlyName))
        , serviceType_(std::move(serviceType))
        , serviceId_(std::move(serviceId))
        , controlURL_(std::move(controlURL))
        , eventSubURL_(std::move(eventSubURL))
Eloi Bail's avatar
Eloi Bail committed
176
        {}
Stepan Salenikovich's avatar
Stepan Salenikovich committed
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196

    const std::string& getUDN() const { return UDN_; };
    const std::string& getBaseURL() const { return baseURL_; };
    const std::string& getFriendlyName() const { return friendlyName_; };
    const std::string& getServiceType() const { return serviceType_; };
    const std::string& getServiceId() const { return serviceId_; };
    const std::string& getControlURL() const { return controlURL_; };
    const std::string& getEventSubURL() const { return eventSubURL_; };

private:
    /* root device info */
    std::string UDN_ {}; /* used to uniquely identify this UPnP device */
    std::string baseURL_ {};
    std::string friendlyName_ {};

    /* port forwarding service info */
    std::string serviceType_ {};
    std::string serviceId_ {};
    std::string controlURL_ {};
    std::string eventSubURL_ {};
Adrien Béraud's avatar
Adrien Béraud committed
197
198
199
200
201
202
203
204
205
206
207
};

#endif

#if HAVE_LIBNATPMP

using clock = std::chrono::system_clock;
using time_point = clock::time_point;

class PMPIGD : public IGD {
public:
208
    void clear() {
Adrien Béraud's avatar
Adrien Béraud committed
209
210
211
        toRemove_.clear();
        udpMappings.clear();
        tcpMappings.clear();
212
213
214
215
    }

    void clearMappings() {
        clear();
Adrien Béraud's avatar
Adrien Béraud committed
216
217
218
219
220
221
222
223
224
225
226
227
228
        clearAll_ = true;
    }

    GlobalMapping* getNextMappingToRenew() const {
        const GlobalMapping* mapping {nullptr};
        for (const auto& m : udpMappings)
            if (!mapping or m.second.renewal_ < mapping->renewal_)
                mapping = &m.second;
        for (const auto& m : tcpMappings)
            if (!mapping or m.second.renewal_ < mapping->renewal_)
                mapping = &m.second;
        return (GlobalMapping*)mapping;
    }
Stepan Salenikovich's avatar
Stepan Salenikovich committed
229

Adrien Béraud's avatar
Adrien Béraud committed
230
231
232
233
234
235
236
237
238
    time_point getRenewalTime() const {
        const auto next = getNextMappingToRenew();
        auto nextTime = std::min(renewal_, next ? next->renewal_ : time_point::max());
        return toRemove_.empty() ? nextTime : std::min(nextTime, time_point::min());
    }

    time_point renewal_ {time_point::min()};
    std::vector<GlobalMapping> toRemove_ {};
    bool clearAll_ {false};
Stepan Salenikovich's avatar
Stepan Salenikovich committed
239
240
};

Adrien Béraud's avatar
Adrien Béraud committed
241
#endif
242

Adrien Béraud's avatar
Adrien Béraud committed
243
}} // namespace jami::upnp