summaryrefslogtreecommitdiff
path: root/pkg/vpn/management/server.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/vpn/management/server.go')
-rw-r--r--pkg/vpn/management/server.go193
1 files changed, 193 insertions, 0 deletions
diff --git a/pkg/vpn/management/server.go b/pkg/vpn/management/server.go
new file mode 100644
index 0000000..f9183a7
--- /dev/null
+++ b/pkg/vpn/management/server.go
@@ -0,0 +1,193 @@
+// Copyright (c) 2016 Martin Atkins
+// Copyright (c) 2021 LEAP Encryption Access Project
+
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do
+// so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+package management
+
+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)
+}