diff options
Diffstat (limited to 'vendor/github.com/apparentlymart')
8 files changed, 1424 insertions, 0 deletions
diff --git a/vendor/github.com/apparentlymart/go-openvpn-mgmt/demux/demuxer.go b/vendor/github.com/apparentlymart/go-openvpn-mgmt/demux/demuxer.go new file mode 100644 index 0000000..c57964e --- /dev/null +++ b/vendor/github.com/apparentlymart/go-openvpn-mgmt/demux/demuxer.go @@ -0,0 +1,63 @@ +package demux + +import ( + "bufio" + "io" +) + +var readErrSynthEvent = []byte("FATAL:Error reading from OpenVPN") + +// Demultiplex reads from the given io.Reader, assumed to be the client +// end of an OpenVPN Management Protocol connection, and splits it into +// distinct messages from OpenVPN. +// +// It then writes the raw message buffers into either replyCh or eventCh +// depending on whether each message is a reply to a client command or +// an asynchronous event notification. +// +// The buffers written to replyCh are entire raw message lines (without the +// trailing newlines), while the buffers written to eventCh are the raw +// event strings with the prototcol's leading '>' indicator omitted. +// +// The caller should usually provide buffered channels of sufficient buffer +// depth so that the reply channel will not be starved by slow event +// processing. +// +// Once the io.Reader signals EOF, eventCh will be closed, then replyCh +// will be closed, and then this function will return. +// +// As a special case, if a non-EOF error occurs while reading from the +// io.Reader then a synthetic "FATAL" event will be written to eventCh +// before the two buffers are closed and the function returns. This +// synthetic message will have the error message "Error reading from OpenVPN". +func Demultiplex(r io.Reader, replyCh, eventCh chan<- []byte) { + scanner := bufio.NewScanner(r) + for scanner.Scan() { + buf := scanner.Bytes() + + if len(buf) < 1 { + // Should never happen but we'll be robust and ignore this, + // rather than crashing below. + continue + } + + // Asynchronous messages always start with > to differentiate + // them from replies. + if buf[0] == '>' { + // Trim off the > when we post the message, since it's + // redundant after we've demuxed. + eventCh <- buf[1:] + } else { + replyCh <- buf + } + } + + if err := scanner.Err(); err != nil { + // Generate a synthetic FATAL event so that the caller can + // see that the connection was not gracefully closed. + eventCh <- readErrSynthEvent + } + + close(eventCh) + close(replyCh) +} diff --git a/vendor/github.com/apparentlymart/go-openvpn-mgmt/demux/demuxer_test.go b/vendor/github.com/apparentlymart/go-openvpn-mgmt/demux/demuxer_test.go new file mode 100644 index 0000000..45edac6 --- /dev/null +++ b/vendor/github.com/apparentlymart/go-openvpn-mgmt/demux/demuxer_test.go @@ -0,0 +1,218 @@ +package demux + +import ( + "bytes" + "fmt" + "io" + "reflect" + "testing" +) + +func TestDemultiplex(t *testing.T) { + type TestCase struct { + Input []string + ExpectedReplies []string + ExpectedEvents []string + } + + testCases := []TestCase{ + { + Input: []string{}, + ExpectedReplies: []string{}, + ExpectedEvents: []string{}, + }, + { + Input: []string{ + "SUCCESS: foo bar baz", + }, + ExpectedReplies: []string{ + "SUCCESS: foo bar baz", + }, + ExpectedEvents: []string{}, + }, + { + Input: []string{ + ">STATE:1234,ASSIGN_IP,,10.0.0.1,", + }, + ExpectedReplies: []string{}, + ExpectedEvents: []string{ + "STATE:1234,ASSIGN_IP,,10.0.0.1,", + }, + }, + { + Input: []string{ + ">STATE:1234,ASSIGN_IP,,10.0.0.1,", + ">STATE:5678,ASSIGN_IP,,10.0.0.1,", + ">STATE:9012,ASSIGN_IP,,10.0.0.1,", + }, + ExpectedReplies: []string{}, + ExpectedEvents: []string{ + "STATE:1234,ASSIGN_IP,,10.0.0.1,", + "STATE:5678,ASSIGN_IP,,10.0.0.1,", + "STATE:9012,ASSIGN_IP,,10.0.0.1,", + }, + }, + { + Input: []string{ + ">STATE:1234,ASSIGN_IP,,10.0.0.1,", + "SUCCESS: foo bar baz", + ">STATE:5678,ASSIGN_IP,,10.0.0.1,", + }, + ExpectedReplies: []string{ + "SUCCESS: foo bar baz", + }, + ExpectedEvents: []string{ + "STATE:1234,ASSIGN_IP,,10.0.0.1,", + "STATE:5678,ASSIGN_IP,,10.0.0.1,", + }, + }, + { + Input: []string{ + "SUCCESS: foo bar baz", + ">STATE:1234,ASSIGN_IP,,10.0.0.1,", + "SUCCESS: baz bar foo", + }, + ExpectedReplies: []string{ + "SUCCESS: foo bar baz", + "SUCCESS: baz bar foo", + }, + ExpectedEvents: []string{ + "STATE:1234,ASSIGN_IP,,10.0.0.1,", + }, + }, + } + + for i, testCase := range testCases { + r := mockReader(testCase.Input) + gotReplies, gotEvents := captureMsgs(r) + + if !reflect.DeepEqual(gotReplies, testCase.ExpectedReplies) { + t.Errorf( + "test %d returned incorrect replies\ngot %#v\nwant %#v", + i, gotReplies, testCase.ExpectedReplies, + ) + } + + if !reflect.DeepEqual(gotEvents, testCase.ExpectedEvents) { + t.Errorf( + "test %d returned incorrect events\ngot %#v\nwant %#v", + i, gotEvents, testCase.ExpectedEvents, + ) + } + } +} + +func TestDemultiplex_error(t *testing.T) { + r := &alwaysErroringReader{} + + gotReplies, gotEvents := captureMsgs(r) + + expectedReplies := []string{} + expectedEvents := []string{ + "FATAL:Error reading from OpenVPN", + } + + if !reflect.DeepEqual(gotReplies, expectedReplies) { + t.Errorf( + "incorrect replies\ngot %#v\nwant %#v", + gotReplies, expectedReplies, + ) + } + + if !reflect.DeepEqual(gotEvents, expectedEvents) { + t.Errorf( + "incorrect events\ngot %#v\nwant %#v", + gotEvents, expectedEvents, + ) + } +} + +func mockReader(msgs []string) io.Reader { + var buf []byte + for _, msg := range msgs { + buf = append(buf, []byte(msg)...) + buf = append(buf, '\n') + } + return bytes.NewReader(buf) +} + +func captureMsgs(r io.Reader) (replies, events []string) { + replyCh := make(chan []byte) + eventCh := make(chan []byte) + + replies = make([]string, 0) + events = make([]string, 0) + + go Demultiplex(r, replyCh, eventCh) + + for replyCh != nil || eventCh != nil { + select { + + case msg, ok := <-replyCh: + if ok { + replies = append(replies, string(msg)) + } else { + replyCh = nil + } + + case msg, ok := <-eventCh: + if ok { + events = append(events, string(msg)) + } else { + eventCh = nil + } + + } + + } + + return replies, events +} + +type alwaysErroringReader struct{} + +func (r *alwaysErroringReader) Read(buf []byte) (int, error) { + return 0, fmt.Errorf("mock error") +} + +// Somewhat-contrived example of blocking for a reply while concurrently +// processing asynchronous events. +func ExampleDemultiplex() { + // In a real caller we would have a net.IPConn as our reader, + // but we'll use a bytes reader here as a test. + r := bytes.NewReader([]byte( + // A reply to a hypothetical command interspersed between + // two asynchronous events. + ">HOLD:Waiting for hold release\nSUCCESS: foo\n>FATAL:baz\n", + )) + + // No strong need for buffering on this channel because usually + // a message sender will immediately block waiting for the + // associated response message. + replyCh := make(chan []byte) + + // Make sure the event channel buffer is deep enough that slow event + // processing won't significantly delay synchronous replies. If you + // process events quickly, or if you aren't sending any commands + // concurrently with acting on events, then this is not so important. + eventCh := make(chan []byte, 10) + + // Start demultiplexing the message stream in the background. + // This goroutine will exit once the reader signals EOF. + go Demultiplex(r, replyCh, eventCh) + + // Some coroutine has sent a hypothetical message to OpenVPN, + // and it can directly block until the associated reply arrives. + // The events will be concurrently handled by our event loop + // below while we wait for the reply to show up. + go func() { + replyMsgBuf := <-replyCh + fmt.Printf("Command reply: %s\n", string(replyMsgBuf)) + }() + + // Main event loop deals with the async events as they arrive, + // independently of any commands that are pending. + for msgBuf := range eventCh { + fmt.Printf("Event: %s\n", string(msgBuf)) + } +} diff --git a/vendor/github.com/apparentlymart/go-openvpn-mgmt/demux/doc.go b/vendor/github.com/apparentlymart/go-openvpn-mgmt/demux/doc.go new file mode 100644 index 0000000..263a267 --- /dev/null +++ b/vendor/github.com/apparentlymart/go-openvpn-mgmt/demux/doc.go @@ -0,0 +1,12 @@ +// Package demux implements low-level demultiplexing of the stream of +// messages sent from OpenVPN on the management channel. +// +// OpenVPN's protocol includes two different kinds of message from the OpenVPN +// process: replies to commands sent by the management client, and asynchronous +// event notifications. +// +// This package's purpose is to split these messages into two separate streams, +// so that functions executing command/response sequences can just block +// on the reply channel while an event loop elsewhere deals with any async +// events that might show up. +package demux diff --git a/vendor/github.com/apparentlymart/go-openvpn-mgmt/openvpn/client.go b/vendor/github.com/apparentlymart/go-openvpn-mgmt/openvpn/client.go new file mode 100644 index 0000000..7768004 --- /dev/null +++ b/vendor/github.com/apparentlymart/go-openvpn-mgmt/openvpn/client.go @@ -0,0 +1,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() +} diff --git a/vendor/github.com/apparentlymart/go-openvpn-mgmt/openvpn/error.go b/vendor/github.com/apparentlymart/go-openvpn-mgmt/openvpn/error.go new file mode 100644 index 0000000..554e2ef --- /dev/null +++ b/vendor/github.com/apparentlymart/go-openvpn-mgmt/openvpn/error.go @@ -0,0 +1,11 @@ +package openvpn + +type ErrorFromServer []byte + +func (err ErrorFromServer) Error() string { + return string(err) +} + +func (err ErrorFromServer) String() string { + return string(err) +} diff --git a/vendor/github.com/apparentlymart/go-openvpn-mgmt/openvpn/event.go b/vendor/github.com/apparentlymart/go-openvpn-mgmt/openvpn/event.go new file mode 100644 index 0000000..66625f3 --- /dev/null +++ b/vendor/github.com/apparentlymart/go-openvpn-mgmt/openvpn/event.go @@ -0,0 +1,299 @@ +package openvpn + +import ( + "bytes" + "fmt" + "strconv" +) + +var eventSep = []byte(":") +var fieldSep = []byte(",") +var byteCountEventKW = []byte("BYTECOUNT") +var byteCountCliEventKW = []byte("BYTECOUNT_CLI") +var clientEventKW = []byte("CLIENT") +var echoEventKW = []byte("ECHO") +var fatalEventKW = []byte("FATAL") +var holdEventKW = []byte("HOLD") +var infoEventKW = []byte("INFO") +var logEventKW = []byte("LOG") +var needOkEventKW = []byte("NEED-OK") +var needStrEventKW = []byte("NEED-STR") +var passwordEventKW = []byte("PASSWORD") +var stateEventKW = []byte("STATE") + +type Event interface { + String() string +} + +// UnknownEvent represents an event of a type that this package doesn't +// know about. +// +// Future versions of this library may learn about new event types, so a +// caller should exercise caution when making use of events of this type +// to access unsupported behavior. Backward-compatibility is *not* +// guaranteed for events of this type. +type UnknownEvent struct { + keyword []byte + body []byte +} + +func (e *UnknownEvent) Type() string { + return string(e.keyword) +} + +func (e *UnknownEvent) Body() string { + return string(e.body) +} + +func (e *UnknownEvent) String() string { + return fmt.Sprintf("%s: %s", e.keyword, e.body) +} + +// MalformedEvent represents a message from the OpenVPN process that is +// presented as an event but does not comply with the expected event syntax. +// +// Events of this type should never be seen but robust callers will accept +// and ignore them, possibly generating some kind of debugging message. +// +// One reason for potentially seeing events of this type is when the target +// program is actually not an OpenVPN process at all, but in fact this client +// has been connected to a different sort of server by mistake. +type MalformedEvent struct { + raw []byte +} + +func (e *MalformedEvent) String() string { + return fmt.Sprintf("Malformed Event %q", e.raw) +} + +// HoldEvent is a notification that the OpenVPN process is in a management +// hold and will not continue connecting until the hold is released, e.g. +// by calling client.HoldRelease() +type HoldEvent struct { + body []byte +} + +func (e *HoldEvent) String() string { + return string(e.body) +} + +// StateEvent is a notification of a change of connection state. It can be +// used, for example, to detect if the OpenVPN connection has been interrupted +// and the OpenVPN process is attempting to reconnect. +type StateEvent struct { + body []byte + + // bodyParts is populated only on first request, giving us the + // separate comma-separated elements of the message. Not all + // fields are populated for all states. + bodyParts [][]byte +} + +func (e *StateEvent) RawTimestamp() string { + parts := e.parts() + return string(parts[0]) +} + +func (e *StateEvent) NewState() string { + parts := e.parts() + return string(parts[1]) +} + +func (e *StateEvent) Description() string { + parts := e.parts() + return string(parts[2]) +} + +// LocalTunnelAddr returns the IP address of the local interface within +// the tunnel, as a string that can be parsed using net.ParseIP. +// +// This field is only populated for events whose NewState returns +// either ASSIGN_IP or CONNECTED. +func (e *StateEvent) LocalTunnelAddr() string { + parts := e.parts() + return string(parts[3]) +} + +// RemoteAddr returns the non-tunnel IP address of the remote +// system that has connected to the local OpenVPN process. +// +// This field is only populated for events whose NewState returns +// CONNECTED. +func (e *StateEvent) RemoteAddr() string { + parts := e.parts() + return string(parts[4]) +} + +func (e *StateEvent) String() string { + newState := e.NewState() + switch newState { + case "ASSIGN_IP": + return fmt.Sprintf("%s: %s", newState, e.LocalTunnelAddr()) + case "CONNECTED": + return fmt.Sprintf("%s: %s", newState, e.RemoteAddr()) + default: + desc := e.Description() + if desc != "" { + return fmt.Sprintf("%s: %s", newState, desc) + } else { + return newState + } + } +} + +func (e *StateEvent) parts() [][]byte { + if e.bodyParts == nil { + // State messages currently have only five segments, but + // we'll ask for 5 so any additional fields that might show + // up in newer versions will gather in an element we're + // not actually using. + e.bodyParts = bytes.SplitN(e.body, fieldSep, 6) + + // Prevent crash if the server has sent us a malformed + // status message. This should never actually happen if + // the server is behaving itself. + if len(e.bodyParts) < 5 { + expanded := make([][]byte, 5) + copy(expanded, e.bodyParts) + e.bodyParts = expanded + } + } + return e.bodyParts +} + +// EchoEvent is emitted by an OpenVPN process running in client mode when +// an "echo" command is pushed to it by the server it has connected to. +// +// The format of the echo message is free-form, since this message type is +// intended to pass application-specific data from the server-side config +// into whatever client is consuming the management prototcol. +// +// This event is emitted only if the management client has turned on events +// of this type using client.SetEchoEvents(true) +type EchoEvent struct { + body []byte +} + +func (e *EchoEvent) RawTimestamp() string { + sepIndex := bytes.Index(e.body, fieldSep) + if sepIndex == -1 { + return "" + } + return string(e.body[:sepIndex]) +} + +func (e *EchoEvent) Message() string { + sepIndex := bytes.Index(e.body, fieldSep) + if sepIndex == -1 { + return "" + } + return string(e.body[sepIndex+1:]) +} + +func (e *EchoEvent) String() string { + return fmt.Sprintf("ECHO: %s", e.Message()) +} + +// ByteCountEvent represents a periodic snapshot of data transfer in bytes +// on a VPN connection. +// +// For OpenVPN *servers*, events are emitted for each client and the method +// ClientId identifies thet client. +// +// For other OpenVPN modes, events are emitted only once per interval for the +// single connection managed by the target process, and ClientId returns +// the empty string. +type ByteCountEvent struct { + hasClient bool + body []byte + + // populated on first call to parts() + bodyParts [][]byte +} + +func (e *ByteCountEvent) ClientId() string { + if !e.hasClient { + return "" + } + + return string(e.parts()[0]) +} + +func (e *ByteCountEvent) BytesIn() int { + index := 0 + if e.hasClient { + index = 1 + } + str := string(e.parts()[index]) + val, _ := strconv.Atoi(str) + // Ignore error, since this should never happen if OpenVPN is + // behaving itself. + return val +} + +func (e *ByteCountEvent) BytesOut() int { + index := 1 + if e.hasClient { + index = 2 + } + str := string(e.parts()[index]) + val, _ := strconv.Atoi(str) + // Ignore error, since this should never happen if OpenVPN is + // behaving itself. + return val +} + +func (e *ByteCountEvent) String() string { + if e.hasClient { + return fmt.Sprintf("Client %s: %d in, %d out", e.ClientId(), e.BytesIn(), e.BytesOut()) + } else { + return fmt.Sprintf("%d in, %d out", e.BytesIn(), e.BytesOut()) + } +} + +func (e *ByteCountEvent) parts() [][]byte { + if e.bodyParts == nil { + e.bodyParts = bytes.SplitN(e.body, fieldSep, 4) + + wantCount := 2 + if e.hasClient { + wantCount = 3 + } + + // Prevent crash if the server has sent us a malformed + // message. This should never actually happen if the + // server is behaving itself. + if len(e.bodyParts) < wantCount { + expanded := make([][]byte, wantCount) + copy(expanded, e.bodyParts) + e.bodyParts = expanded + } + } + return e.bodyParts +} + +func upgradeEvent(raw []byte) Event { + splitIdx := bytes.Index(raw, eventSep) + if splitIdx == -1 { + // Should never happen, but we'll handle it robustly if it does. + return &MalformedEvent{raw} + } + + keyword := raw[:splitIdx] + body := raw[splitIdx+1:] + + switch { + case bytes.Equal(keyword, stateEventKW): + return &StateEvent{body: body} + case bytes.Equal(keyword, holdEventKW): + return &HoldEvent{body} + case bytes.Equal(keyword, echoEventKW): + return &EchoEvent{body} + case bytes.Equal(keyword, byteCountEventKW): + return &ByteCountEvent{hasClient: false, body: body} + case bytes.Equal(keyword, byteCountCliEventKW): + return &ByteCountEvent{hasClient: true, body: body} + default: + return &UnknownEvent{keyword, body} + } +} diff --git a/vendor/github.com/apparentlymart/go-openvpn-mgmt/openvpn/event_test.go b/vendor/github.com/apparentlymart/go-openvpn-mgmt/openvpn/event_test.go new file mode 100644 index 0000000..81a02e1 --- /dev/null +++ b/vendor/github.com/apparentlymart/go-openvpn-mgmt/openvpn/event_test.go @@ -0,0 +1,346 @@ +package openvpn + +import ( + "fmt" + "testing" +) + +// A key requirement of our event parsing is that it must never cause a +// panic, even if the OpenVPN process sends us malformed garbage. +// +// Therefore most of the tests in here are testing various tortured error +// cases, which are all expected to produce an event object, though the +// contents of that event object will be nonsensical if the OpenVPN server +// sends something nonsensical. + +func TestMalformedEvent(t *testing.T) { + testCases := [][]byte{ + []byte(""), + []byte("HTTP/1.1 200 OK"), + []byte(" "), + []byte("\x00"), + } + + for i, testCase := range testCases { + event := upgradeEvent(testCase) + + var malformed *MalformedEvent + var ok bool + if malformed, ok = event.(*MalformedEvent); !ok { + t.Errorf("test %d got %T; want %T", i, event, malformed) + continue + } + + wantString := fmt.Sprintf("Malformed Event %q", testCase) + if gotString := malformed.String(); gotString != wantString { + t.Errorf("test %d String returned %q; want %q", i, gotString, wantString) + } + } +} + +func TestUnknownEvent(t *testing.T) { + type TestCase struct { + Input []byte + WantType string + WantBody string + } + testCases := []TestCase{ + { + Input: []byte("DUMMY:baz"), + WantType: "DUMMY", + WantBody: "baz", + }, + { + Input: []byte("DUMMY:"), + WantType: "DUMMY", + WantBody: "", + }, + { + Input: []byte("DUMMY:abc,123,456"), + WantType: "DUMMY", + WantBody: "abc,123,456", + }, + } + + for i, testCase := range testCases { + event := upgradeEvent(testCase.Input) + + var unk *UnknownEvent + var ok bool + if unk, ok = event.(*UnknownEvent); !ok { + t.Errorf("test %d got %T; want %T", i, event, unk) + continue + } + + if got, want := unk.Type(), testCase.WantType; got != want { + t.Errorf("test %d Type returned %q; want %q", i, got, want) + } + if got, want := unk.Body(), testCase.WantBody; got != want { + t.Errorf("test %d Body returned %q; want %q", i, got, want) + } + } +} + +func TestHoldEvent(t *testing.T) { + testCases := [][]byte{ + []byte("HOLD:"), + []byte("HOLD:waiting for hold release"), + } + + for i, testCase := range testCases { + event := upgradeEvent(testCase) + + var hold *HoldEvent + var ok bool + if hold, ok = event.(*HoldEvent); !ok { + t.Errorf("test %d got %T; want %T", i, event, hold) + continue + } + } +} + +func TestEchoEvent(t *testing.T) { + type TestCase struct { + Input []byte + WantTimestamp string + WantMessage string + } + testCases := []TestCase{ + { + Input: []byte("ECHO:123,foo"), + WantTimestamp: "123", + WantMessage: "foo", + }, + { + Input: []byte("ECHO:123,"), + WantTimestamp: "123", + WantMessage: "", + }, + { + Input: []byte("ECHO:,foo"), + WantTimestamp: "", + WantMessage: "foo", + }, + { + Input: []byte("ECHO:,"), + WantTimestamp: "", + WantMessage: "", + }, + { + Input: []byte("ECHO:"), + WantTimestamp: "", + WantMessage: "", + }, + } + + for i, testCase := range testCases { + event := upgradeEvent(testCase.Input) + + var echo *EchoEvent + var ok bool + if echo, ok = event.(*EchoEvent); !ok { + t.Errorf("test %d got %T; want %T", i, event, echo) + continue + } + + if got, want := echo.RawTimestamp(), testCase.WantTimestamp; got != want { + t.Errorf("test %d RawTimestamp returned %q; want %q", i, got, want) + } + if got, want := echo.Message(), testCase.WantMessage; got != want { + t.Errorf("test %d Message returned %q; want %q", i, got, want) + } + } +} + +func TestStateEvent(t *testing.T) { + type TestCase struct { + Input []byte + WantTimestamp string + WantState string + WantDesc string + WantLocalAddr string + WantRemoteAddr string + } + testCases := []TestCase{ + { + Input: []byte("STATE:"), + WantTimestamp: "", + WantState: "", + WantDesc: "", + WantLocalAddr: "", + WantRemoteAddr: "", + }, + { + Input: []byte("STATE:,"), + WantTimestamp: "", + WantState: "", + WantDesc: "", + WantLocalAddr: "", + WantRemoteAddr: "", + }, + { + Input: []byte("STATE:,,,,"), + WantTimestamp: "", + WantState: "", + WantDesc: "", + WantLocalAddr: "", + WantRemoteAddr: "", + }, + { + Input: []byte("STATE:123,CONNECTED,good,172.16.0.1,192.168.4.1"), + WantTimestamp: "123", + WantState: "CONNECTED", + WantDesc: "good", + WantLocalAddr: "172.16.0.1", + WantRemoteAddr: "192.168.4.1", + }, + { + Input: []byte("STATE:123,RECONNECTING,SIGHUP,,"), + WantTimestamp: "123", + WantState: "RECONNECTING", + WantDesc: "SIGHUP", + WantLocalAddr: "", + WantRemoteAddr: "", + }, + { + Input: []byte("STATE:123,RECONNECTING,SIGHUP,,,extra"), + WantTimestamp: "123", + WantState: "RECONNECTING", + WantDesc: "SIGHUP", + WantLocalAddr: "", + WantRemoteAddr: "", + }, + } + + for i, testCase := range testCases { + event := upgradeEvent(testCase.Input) + + var st *StateEvent + var ok bool + if st, ok = event.(*StateEvent); !ok { + t.Errorf("test %d got %T; want %T", i, event, st) + continue + } + + if got, want := st.RawTimestamp(), testCase.WantTimestamp; got != want { + t.Errorf("test %d RawTimestamp returned %q; want %q", i, got, want) + } + if got, want := st.NewState(), testCase.WantState; got != want { + t.Errorf("test %d NewState returned %q; want %q", i, got, want) + } + if got, want := st.Description(), testCase.WantDesc; got != want { + t.Errorf("test %d Description returned %q; want %q", i, got, want) + } + if got, want := st.LocalTunnelAddr(), testCase.WantLocalAddr; got != want { + t.Errorf("test %d LocalTunnelAddr returned %q; want %q", i, got, want) + } + if got, want := st.RemoteAddr(), testCase.WantRemoteAddr; got != want { + t.Errorf("test %d RemoteAddr returned %q; want %q", i, got, want) + } + } +} + +func TestByteCountEvent(t *testing.T) { + type TestCase struct { + Input []byte + WantClientId string + WantBytesIn int + WantBytesOut int + } + testCases := []TestCase{ + { + Input: []byte("BYTECOUNT:"), + WantClientId: "", + WantBytesIn: 0, + WantBytesOut: 0, + }, + { + Input: []byte("BYTECOUNT:123,456"), + WantClientId: "", + WantBytesIn: 123, + WantBytesOut: 456, + }, + { + Input: []byte("BYTECOUNT:,"), + WantClientId: "", + WantBytesIn: 0, + WantBytesOut: 0, + }, + { + Input: []byte("BYTECOUNT:5,"), + WantClientId: "", + WantBytesIn: 5, + WantBytesOut: 0, + }, + { + Input: []byte("BYTECOUNT:,6"), + WantClientId: "", + WantBytesIn: 0, + WantBytesOut: 6, + }, + { + Input: []byte("BYTECOUNT:6"), + WantClientId: "", + WantBytesIn: 6, + WantBytesOut: 0, + }, + { + Input: []byte("BYTECOUNT:wrong,bad"), + WantClientId: "", + WantBytesIn: 0, + WantBytesOut: 0, + }, + { + Input: []byte("BYTECOUNT:1,2,3"), + WantClientId: "", + WantBytesIn: 1, + WantBytesOut: 2, + }, + { + // Intentionally malformed BYTECOUNT event sent as BYTECOUNT_CLI + Input: []byte("BYTECOUNT_CLI:123,456"), + WantClientId: "123", + WantBytesIn: 456, + WantBytesOut: 0, + }, + { + Input: []byte("BYTECOUNT_CLI:"), + WantClientId: "", + WantBytesIn: 0, + WantBytesOut: 0, + }, + { + Input: []byte("BYTECOUNT_CLI:abc123,123,456"), + WantClientId: "abc123", + WantBytesIn: 123, + WantBytesOut: 456, + }, + { + Input: []byte("BYTECOUNT_CLI:abc123,123"), + WantClientId: "abc123", + WantBytesIn: 123, + WantBytesOut: 0, + }, + } + + for i, testCase := range testCases { + event := upgradeEvent(testCase.Input) + + var bc *ByteCountEvent + var ok bool + if bc, ok = event.(*ByteCountEvent); !ok { + t.Errorf("test %d got %T; want %T", i, event, bc) + continue + } + + if got, want := bc.ClientId(), testCase.WantClientId; got != want { + t.Errorf("test %d ClientId returned %q; want %q", i, got, want) + } + if got, want := bc.BytesIn(), testCase.WantBytesIn; got != want { + t.Errorf("test %d BytesIn returned %d; want %d", i, got, want) + } + if got, want := bc.BytesOut(), testCase.WantBytesOut; got != want { + t.Errorf("test %d BytesOut returned %d; want %d", i, got, want) + } + } +} diff --git a/vendor/github.com/apparentlymart/go-openvpn-mgmt/openvpn/server.go b/vendor/github.com/apparentlymart/go-openvpn-mgmt/openvpn/server.go new file mode 100644 index 0000000..d7defd4 --- /dev/null +++ b/vendor/github.com/apparentlymart/go-openvpn-mgmt/openvpn/server.go @@ -0,0 +1,172 @@ +package openvpn + +import ( + "net" + "time" +) + +// MgmtListener accepts incoming connections from OpenVPN. +// +// The primary way to instantiate this type is via the function Listen. +// See its documentation for more information. +type MgmtListener struct { + l net.Listener +} + +// NewMgmtListener constructs a MgmtListener from an already-established +// net.Listener. In most cases it will be more convenient to use +// the function Listen. +func NewMgmtListener(l net.Listener) *MgmtListener { + return &MgmtListener{l} +} + +// Listen opens a listen port and awaits incoming connections from OpenVPN +// processes. +// +// OpenVPN will behave in this manner when launched with the following options: +// +// --management ipaddr port --management-client +// +// Note that in this case the terminology is slightly confusing, since from +// the standpoint of TCP/IP it is OpenVPN that is the client and our program +// that is the server, but once the connection is established the channel +// is indistinguishable from the situation where OpenVPN exposed a management +// *server* and we connected to it. Thus we still refer to our program as +// the "client" and OpenVPN as the "server" once the connection is established. +// +// When running on Unix systems it's possible to instead listen on a Unix +// domain socket. To do this, pass an absolute path to the socket as +// the listen address, and then run OpenVPN with the following options: +// +// --management /path/to/socket unix --management-client +// +func Listen(laddr string) (*MgmtListener, error) { + proto := "tcp" + if len(laddr) > 0 && laddr[0] == '/' { + proto = "unix" + } + listener, err := net.Listen(proto, laddr) + if err != nil { + return nil, err + } + + return NewMgmtListener(listener), nil +} + +// Accept waits for and returns the next connection. +func (l *MgmtListener) Accept() (*IncomingConn, error) { + conn, err := l.l.Accept() + if err != nil { + return nil, err + } + + return &IncomingConn{conn}, nil +} + +// Close closes the listener. Any blocked Accept operations +// will be blocked and each will return an error. +func (l *MgmtListener) Close() error { + return l.l.Close() +} + +// Addr returns the listener's network address. +func (l *MgmtListener) Addr() net.Addr { + return l.l.Addr() +} + +// Serve will await new connections and call the given handler +// for each. +// +// Serve does not return unless the listen port is closed; a non-nil +// error is always returned. +func (l *MgmtListener) Serve(handler IncomingConnHandler) error { + defer l.Close() + + var tempDelay time.Duration + + for { + incoming, err := l.Accept() + if err != nil { + if ne, ok := err.(net.Error); ok && ne.Temporary() { + if tempDelay == 0 { + tempDelay = 5 * time.Millisecond + } else { + tempDelay *= 2 + } + if max := 1 * time.Second; tempDelay > max { + tempDelay = max + } + + // Wait a while before we try again. + time.Sleep(tempDelay) + continue + } else { + // Listen socket is permanently closed or errored, + // so it's time for us to exit. + return err + } + } + + // always reset our retry delay once we successfully read + tempDelay = 0 + + go handler.ServeOpenVPNMgmt(*incoming) + } +} + +type IncomingConn struct { + conn net.Conn +} + +// Open initiates communication with the connected OpenVPN process, +// and establishes the channel on which events will be delivered. +// +// See the documentation for NewClient for discussion about the requirements +// for eventCh. +func (ic IncomingConn) Open(eventCh chan<- Event) *MgmtClient { + return NewClient(ic.conn, eventCh) +} + +// Close abruptly closes the socket connected to the OpenVPN process. +// +// This is a rather abrasive way to close the channel, intended for rejecting +// unwanted incoming clients that may or may not speak the OpenVPN protocol. +// +// Once communication is accepted and established, it is generally better +// to close the connection gracefully using commands on the client returned +// from Open. +func (ic IncomingConn) Close() error { + return ic.conn.Close() +} + +type IncomingConnHandler interface { + ServeOpenVPNMgmt(IncomingConn) +} + +// IncomingConnHandlerFunc is an adapter to allow the use of ordinary +// functions as connection handlers. +// +// Given a function with the appropriate signature, IncomingConnHandlerFunc(f) +// is an IncomingConnHandler that calls f. +type IncomingConnHandlerFunc func(IncomingConn) + +func (f IncomingConnHandlerFunc) ServeOpenVPNMgmt(i IncomingConn) { + f(i) +} + +// ListenAndServe creates a MgmtListener for the given listen address +// and then calls AcceptAndServe on it. +// +// This is just a convenience wrapper. See the AcceptAndServe method for +// more details. Just as with AcceptAndServe, this function does not return +// except on error; in addition to the error cases handled by AcceptAndServe, +// this function may also fail if the listen socket cannot be established +// in the first place. +func ListenAndServe(laddr string, handler IncomingConnHandler) error { + listener, err := Listen(laddr) + if err != nil { + return err + } + + return listener.Serve(handler) +} |