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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 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 120 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 165 166 167 168 169 170 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 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
|
package openvpn
import (
"bytes"
"fmt"
"io"
"net"
"strconv"
"time"
"github.com/apparentlymart/go-openvpn-mgmt/demux"
)
var newline = []byte{'\n'}
var successPrefix = []byte("SUCCESS: ")
var errorPrefix = []byte("ERROR: ")
var endMessage = []byte("END")
type MgmtClient struct {
wr io.Writer
replies <-chan []byte
}
// NewClient creates a new MgmtClient that communicates via the given
// io.ReadWriter and emits events on the given channel.
//
// eventCh should be a buffered channel with a sufficient buffer depth
// such that it cannot be filled under the expected event volume. Event
// volume depends on which events are enabled and how they are configured;
// some of the event-enabling functions have further discussion how frequently
// events are likely to be emitted, but the caller should also factor in
// how long its own event *processing* will take, since slow event
// processing will create back-pressure that could cause this buffer to
// fill faster.
//
// It probably goes without saying given the previous paragraph, but the
// caller *must* constantly read events from eventCh to avoid its buffer
// becoming full. Events and replies are received on the same channel
// from OpenVPN, so if writing to eventCh blocks then this will also block
// responses from the client's various command methods.
//
// eventCh will be closed to signal the closing of the client connection,
// whether due to graceful shutdown or to an error. In the case of error,
// a FatalEvent will be emitted on the channel as the last event before it
// is closed. Connection errors may also concurrently surface as error
// responses from the client's various command methods, should an error
// occur while we await a reply.
func NewClient(conn io.ReadWriter, eventCh chan<- Event) *MgmtClient {
replyCh := make(chan []byte)
rawEventCh := make(chan []byte) // not buffered because eventCh should be
go demux.Demultiplex(conn, replyCh, rawEventCh)
// Get raw events and upgrade them into proper event types before
// passing them on to the caller's event channel.
go func() {
for raw := range rawEventCh {
eventCh <- upgradeEvent(raw)
}
close(eventCh)
}()
return &MgmtClient{
// replyCh acts as the reader for our ReadWriter, so we only
// need to retain the io.Writer for it, so we can send commands.
wr: conn,
replies: replyCh,
}
}
// Dial is a convenience wrapper around NewClient that handles the common
// case of opening an TCP/IP socket to an OpenVPN management port and creating
// a client for it.
//
// See the NewClient docs for discussion about the requirements for eventCh.
//
// OpenVPN will create a suitable management port if launched with the
// following command line option:
//
// --management <ipaddr> <port>
//
// Address may an IPv4 address, an IPv6 address, or a hostname that resolves
// to either of these, followed by a colon and then a port number.
//
// When running on Unix systems it's possible to instead connect to a Unix
// domain socket. To do this, pass an absolute path to the socket as
// the target address, having run OpenVPN with the following options:
//
// --management /path/to/socket unix
//
func Dial(addr string, eventCh chan<- Event) (*MgmtClient, error) {
proto := "tcp"
if len(addr) > 0 && addr[0] == '/' {
proto = "unix"
}
conn, err := net.Dial(proto, addr)
if err != nil {
return nil, err
}
return NewClient(conn, eventCh), nil
}
// HoldRelease instructs OpenVPN to release any management hold preventing
// it from proceeding, but to retain the state of the hold flag such that
// the daemon will hold again if it needs to reconnect for any reason.
//
// OpenVPN can be instructed to activate a management hold on startup by
// running it with the following option:
//
// --management-hold
//
// Instructing OpenVPN to hold gives your client a chance to connect and
// do any necessary configuration before a connection proceeds, thus avoiding
// the problem of missed events.
//
// When OpenVPN begins holding, or when a new management client connects while
// a hold is already in effect, a HoldEvent will be emitted on the event
// channel.
func (c *MgmtClient) HoldRelease() error {
_, err := c.simpleCommand("hold release")
return err
}
// SetStateEvents either enables or disables asynchronous events for changes
// in the OpenVPN connection state.
//
// When enabled, a StateEvent will be emitted from the event channel each
// time the connection state changes. See StateEvent for more information
// on the event structure.
func (c *MgmtClient) SetStateEvents(on bool) error {
var err error
if on {
_, err = c.simpleCommand("state on")
} else {
_, err = c.simpleCommand("state off")
}
return err
}
// SetEchoEvents either enables or disables asynchronous events for "echo"
// commands sent from a remote server to our managed OpenVPN client.
//
// When enabled, an EchoEvent will be emitted from the event channel each
// time the server sends an echo command. See EchoEvent for more information.
func (c *MgmtClient) SetEchoEvents(on bool) error {
var err error
if on {
_, err = c.simpleCommand("echo on")
} else {
_, err = c.simpleCommand("echo off")
}
return err
}
// SetByteCountEvents either enables or disables ongoing asynchronous events
// for information on OpenVPN bandwidth usage.
//
// When enabled, a ByteCountEvent will be emitted at given time interval,
// (which may only be whole seconds) describing how many bytes have been
// transferred in each direction See ByteCountEvent for more information.
//
// Set the time interval to zero in order to disable byte count events.
func (c *MgmtClient) SetByteCountEvents(interval time.Duration) error {
msg := fmt.Sprintf("bytecount %d", int(interval.Seconds()))
_, err := c.simpleCommand(msg)
return err
}
// SendSignal sends a signal to the OpenVPN process via the management
// channel. In effect this causes the OpenVPN process to send a signal to
// itself on our behalf.
//
// OpenVPN accepts a subset of the usual UNIX signal names, including
// "SIGHUP", "SIGTERM", "SIGUSR1" and "SIGUSR2". See the OpenVPN manual
// page for the meaning of each.
//
// Behavior is undefined if the given signal name is not entirely uppercase
// letters. In particular, including newlines in the string is likely to
// cause very unpredictable behavior.
func (c *MgmtClient) SendSignal(name string) error {
msg := fmt.Sprintf("signal %q", name)
_, err := c.simpleCommand(msg)
return err
}
// LatestState retrieves the most recent StateEvent from the server. This
// can either be used to poll the state or it can be used to determine the
// initial state after calling SetStateEvents(true) but before the first
// state event is delivered.
func (c *MgmtClient) LatestState() (*StateEvent, error) {
err := c.sendCommand([]byte("state"))
if err != nil {
return nil, err
}
payload, err := c.readCommandResponsePayload()
if err != nil {
return nil, err
}
if len(payload) != 1 {
return nil, fmt.Errorf("Malformed OpenVPN 'state' response")
}
return &StateEvent{
body: payload[0],
}, nil
}
// Pid retrieves the process id of the connected OpenVPN process.
func (c *MgmtClient) Pid() (int, error) {
raw, err := c.simpleCommand("pid")
if err != nil {
return 0, err
}
if !bytes.HasPrefix(raw, []byte("pid=")) {
return 0, fmt.Errorf("malformed response from OpenVPN")
}
pid, err := strconv.Atoi(string(raw[4:]))
if err != nil {
return 0, fmt.Errorf("error parsing pid from OpenVPN: %s", err)
}
return pid, nil
}
func (c *MgmtClient) sendCommand(cmd []byte) error {
_, err := c.wr.Write(cmd)
if err != nil {
return err
}
_, err = c.wr.Write(newline)
return err
}
// sendCommandPayload can be called after sendCommand for
// commands that expect a multi-line input payload.
//
// The buffer given in 'payload' *must* end with a newline,
// or else the protocol will be broken.
func (c *MgmtClient) sendCommandPayload(payload []byte) error {
_, err := c.wr.Write(payload)
if err != nil {
return err
}
_, err = c.wr.Write(endMessage)
if err != nil {
return err
}
_, err = c.wr.Write(newline)
return err
}
func (c *MgmtClient) readCommandResult() ([]byte, error) {
reply, ok := <-c.replies
if !ok {
return nil, fmt.Errorf("connection closed while awaiting result")
}
if bytes.HasPrefix(reply, successPrefix) {
result := reply[len(successPrefix):]
return result, nil
}
if bytes.HasPrefix(reply, errorPrefix) {
message := reply[len(errorPrefix):]
return nil, ErrorFromServer(message)
}
return nil, fmt.Errorf("malformed result message")
}
func (c *MgmtClient) readCommandResponsePayload() ([][]byte, error) {
lines := make([][]byte, 0, 10)
for {
line, ok := <-c.replies
if !ok {
// We'll give the caller whatever we got before the connection
// closed, in case it's useful for debugging.
return lines, fmt.Errorf("connection closed before END recieved")
}
if bytes.Equal(line, endMessage) {
break
}
lines = append(lines, line)
}
return lines, nil
}
func (c *MgmtClient) simpleCommand(cmd string) ([]byte, error) {
err := c.sendCommand([]byte(cmd))
if err != nil {
return nil, err
}
return c.readCommandResult()
}
|