command_socket_factory.cc 8.25 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.

#include <config/command_socket_factory.h>
16
#include <config/config_log.h>
Tomek Mrugalski's avatar
Tomek Mrugalski committed
17
18
19
#include <config/command_mgr.h>
#include <dhcp/iface_mgr.h>
#include <boost/bind.hpp>
20
21
22
#include <sys/socket.h>
#include <sys/un.h>
#include <string.h>
Tomek Mrugalski's avatar
Tomek Mrugalski committed
23
#include <errno.h>
Tomek Mrugalski's avatar
Tomek Mrugalski committed
24
#include <cstdio>
25
26
#include <fcntl.h>

27
using namespace isc::data;
28
29
30
31

namespace isc {
namespace config {

Tomek Mrugalski's avatar
Tomek Mrugalski committed
32
33
34
35
36
37
38
39
40
41
42
43
/// @brief Wrapper for UNIX stream sockets
///
/// There are two UNIX socket types: datagram-based (equivalent of UDP) and
/// stream-based (equivalent of TCP). This class represents stream-based
/// sockets. It opens up a unix-socket and waits for incoming connections.
/// Once incoming connection is detected, accept() system call is called
/// and a new socket for that particular connection is returned. A new
/// object of @ref ConnectionSocket is created.
class UnixCommandSocket : public CommandSocket {
public:
    /// @brief Default constructor
    ///
Tomek Mrugalski's avatar
Tomek Mrugalski committed
44
    /// Opens specified UNIX socket.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
45
    ///
Tomek Mrugalski's avatar
Tomek Mrugalski committed
46
47
48
    /// @param filename socket filename
    UnixCommandSocket(const std::string& filename)
        : filename_(filename) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
49

Tomek Mrugalski's avatar
Tomek Mrugalski committed
50
        // Create the socket and set it up.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
51
52
53
54
55
        sockfd_ = createUnixSocket(filename_);

        // Install this socket in Interface Manager.
        isc::dhcp::IfaceMgr::instance().addExternalSocket(sockfd_,
            boost::bind(&UnixCommandSocket::receiveHandler, this));
56
57
    }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
58
59
60
61
62
private:

    /// @brief Auxiliary method for creating a UNIX socket
    ///
    /// @param file_name specifies socket file path
Tomek Mrugalski's avatar
Tomek Mrugalski committed
63
    /// @return socket file descriptor
Tomek Mrugalski's avatar
Tomek Mrugalski committed
64
65
    int createUnixSocket(const std::string& file_name) {

66
67
68
69
70
71
72
73
74
75
        struct sockaddr_un addr;

        // string.size() returns number of bytes (without trailing zero)
        // we need 1 extra byte for terminating 0.
        if (file_name.size() > sizeof(addr.sun_path) - 1) {
            isc_throw(SocketError, "Failed to open socket: path specified ("
                      << file_name << ") is longer than allowed "
                      << (sizeof(addr.sun_path) - 1) << " bytes.");
        }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
76
77
        int fd = socket(AF_UNIX, SOCK_STREAM, 0);
        if (fd == -1) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
78
79
            isc_throw(isc::config::SocketError, "Failed to create AF_UNIX socket:"
                      << strerror(errno));
Tomek Mrugalski's avatar
Tomek Mrugalski committed
80
81
82
83
84
85
86
87
        }

        // Let's remove the old file. We don't care about any possible
        // errors here. The file should not be there if the file was
        // shut down properly.
        remove(file_name.c_str());

        // Set this socket to be non-blocking one.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
88
        if (fcntl(fd, F_SETFL, O_NONBLOCK) !=0 ) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
89
90
            const char* errmsg = strerror(errno);
            ::close(fd);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
91
            isc_throw(SocketError, "Failed to set non-block mode on unix socket "
Tomek Mrugalski's avatar
Tomek Mrugalski committed
92
                      << fd << ": " << errmsg);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
93
        }
Tomek Mrugalski's avatar
Tomek Mrugalski committed
94
95
96
97

        // Now bind the socket to the specified path.
        memset(&addr, 0, sizeof(addr));
        addr.sun_family = AF_UNIX;
98
        strncpy(addr.sun_path, file_name.c_str(), sizeof(addr.sun_path) - 1);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
99
        if (bind(fd, (struct sockaddr*)&addr, sizeof(addr))) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
100
            const char* errmsg = strerror(errno);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
101
102
103
            ::close(fd);
            remove(file_name.c_str());
            isc_throw(isc::config::SocketError, "Failed to bind socket " << fd
Tomek Mrugalski's avatar
Tomek Mrugalski committed
104
                      << " to " << file_name << ": " << errmsg);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
105
106
107
108
109
110
111
112
113
        }

        // One means that we allow at most 1 awaiting connections.
        // Any additional attempts will get ECONNREFUSED error.
        // That means that at any given time, there may be at most one controlling
        // connection.
        /// @todo: Make the number of parallel connections configurable.
        int status = listen(fd, 1);
        if (status < 0) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
114
            const char* errmsg = strerror(errno);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
115
116
117
            ::close(fd);
            remove(file_name.c_str());
            isc_throw(isc::config::SocketError, "Failed to listen on socket fd="
Tomek Mrugalski's avatar
Tomek Mrugalski committed
118
                      << fd << ", filename=" << file_name << ": " << errmsg);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
119
120
121
122
123
124
        }

        // Woohoo! Socket opened, let's log it!
        LOG_INFO(command_logger, COMMAND_SOCKET_UNIX_OPEN).arg(fd).arg(file_name);

        return (fd);
125
126
    }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
127
    /// @brief Connection acceptor, a callback used to accept incoming connections.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
128
129
    ///
    /// This callback is used on a control socket. Once called, it will accept
Tomek Mrugalski's avatar
Tomek Mrugalski committed
130
131
132
    /// incoming connection, create a new socket for it and create an instance
    /// of ConnectionSocket, which will take care of the rest (i.e. install
    /// appropriate callback for that new socket in @ref isc::dhcp::IfaceMgr).
Tomek Mrugalski's avatar
Tomek Mrugalski committed
133
134
135
136
137
138
139
140
141
142
    void receiveHandler() {

        // This method is specific to receiving data over UNIX socket, so using
        // sockaddr_un instead of sockaddr_storage here is ok.
        struct sockaddr_un client_addr;
        socklen_t client_addr_len;
        client_addr_len = sizeof(client_addr);

        // Accept incoming connection. This will create a separate socket for
        // handling this specific connection.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
143
        int fd2 = accept(sockfd_, reinterpret_cast<struct sockaddr*>(&client_addr),
Tomek Mrugalski's avatar
Tomek Mrugalski committed
144
                         &client_addr_len);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
145
146
147
148
149
        if (fd2 == -1) {
            LOG_ERROR(command_logger, COMMAND_SOCKET_ACCEPT_FAIL)
                .arg(sockfd_).arg(strerror(errno));
            return;
        }
Tomek Mrugalski's avatar
Tomek Mrugalski committed
150
151
152
153
154
155

        // And now create an object that represents that new connection.
        CommandSocketPtr conn(new ConnectionSocket(fd2));

        // Not sure if this is really needed, but let's set it to non-blocking
        // mode.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
156
157
158
159
160
161
162
163
        if (fcntl(fd2, F_SETFL, O_NONBLOCK) != 0) {
            // Failed to set socket to non-blocking mode.
            LOG_ERROR(command_logger, COMMAND_SOCKET_FAIL_NONBLOCK)
                .arg(fd2).arg(sockfd_).arg(strerror(errno));

            conn.reset();
            return;
        }
Tomek Mrugalski's avatar
Tomek Mrugalski committed
164
165
166
167
168
169
170

        // Remember this socket descriptor. It will be needed when we shut down
        // the server.
        CommandMgr::instance().addConnection(conn);

        LOG_INFO(command_logger, COMMAND_SOCKET_CONNECTION_OPENED).arg(fd2)
            .arg(sockfd_);
171
    }
172

Tomek Mrugalski's avatar
Tomek Mrugalski committed
173
174
175
176
177
178
179
    // This method is called when we shutdown the connection.
    void close() {
        LOG_INFO(command_logger, COMMAND_SOCKET_UNIX_CLOSE).arg(sockfd_)
            .arg(filename_);

        isc::dhcp::IfaceMgr::instance().deleteExternalSocket(sockfd_);

Tomek Mrugalski's avatar
Tomek Mrugalski committed
180
181
        // Close should always succeed. We don't care if we're able to delete
        // the socket or not.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
182
183
        ::close(sockfd_);
        remove(filename_.c_str());
184
    }
185

Tomek Mrugalski's avatar
Tomek Mrugalski committed
186
    /// @brief UNIX filename representing this socket
Tomek Mrugalski's avatar
Tomek Mrugalski committed
187
188
    std::string filename_;
};
189

Tomek Mrugalski's avatar
Tomek Mrugalski committed
190
191
CommandSocketPtr
CommandSocketFactory::create(const isc::data::ConstElementPtr& socket_info) {
192
193
194
195
196
197
198
199
200
201
    if(!socket_info) {
        isc_throw(BadSocketInfo, "Missing socket_info parameters, can't create socket.");
    }

    ConstElementPtr type = socket_info->get("socket-type");
    if (!type) {
        isc_throw(BadSocketInfo, "Mandatory 'socket-type' parameter missing");
    }

    if (type->stringValue() == "unix") {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
202
203
204
205
206
207
208
209
210
211
212
        // UNIX socket is requested. It takes one parameter: socket-name that
        // specifies UNIX path of the socket.
        ConstElementPtr name = socket_info->get("socket-name");
        if (!name) {
            isc_throw(BadSocketInfo, "Mandatory 'socket-name' parameter missing");
        }
        if (name->getType() != Element::string) {
            isc_throw(BadSocketInfo, "'socket-name' parameter expected to be a string");
        }

        return (CommandSocketPtr(new UnixCommandSocket(name->stringValue())));
213
214
215
216
    } else {
        isc_throw(BadSocketInfo, "Specified socket type ('" + type->stringValue()
                  + "') is not supported.");
    }
217
218
219
220
}

};
};