summaryrefslogtreecommitdiffhomepage
path: root/uapi.go
diff options
context:
space:
mode:
authorMathias Hall-Andersen <mathias@hall-andersen.dk>2018-02-04 16:08:26 +0100
committerMathias Hall-Andersen <mathias@hall-andersen.dk>2018-02-04 16:08:26 +0100
commita0f54cbe5ac2cd8b8296c2c57c30029dd349cff0 (patch)
tree64574090d79ff3899c5c18e5268e450028e4656b /uapi.go
parent5871ec04deb8f4715cab37146940baa35c08cbee (diff)
Align with go library layout
Diffstat (limited to 'uapi.go')
-rw-r--r--uapi.go437
1 files changed, 437 insertions, 0 deletions
diff --git a/uapi.go b/uapi.go
new file mode 100644
index 0000000..caaa498
--- /dev/null
+++ b/uapi.go
@@ -0,0 +1,437 @@
+package main
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "net"
+ "strconv"
+ "strings"
+ "sync/atomic"
+ "time"
+)
+
+type IPCError struct {
+ Code int64
+}
+
+func (s *IPCError) Error() string {
+ return fmt.Sprintf("IPC error: %d", s.Code)
+}
+
+func (s *IPCError) ErrorCode() int64 {
+ return s.Code
+}
+
+func ipcGetOperation(device *Device, socket *bufio.ReadWriter) *IPCError {
+
+ device.log.Debug.Println("UAPI: Processing get operation")
+
+ // create lines
+
+ lines := make([]string, 0, 100)
+ send := func(line string) {
+ lines = append(lines, line)
+ }
+
+ func() {
+
+ // lock required resources
+
+ device.net.mutex.RLock()
+ defer device.net.mutex.RUnlock()
+
+ device.noise.mutex.RLock()
+ defer device.noise.mutex.RUnlock()
+
+ device.routing.mutex.RLock()
+ defer device.routing.mutex.RUnlock()
+
+ device.peers.mutex.Lock()
+ defer device.peers.mutex.Unlock()
+
+ // serialize device related values
+
+ if !device.noise.privateKey.IsZero() {
+ send("private_key=" + device.noise.privateKey.ToHex())
+ }
+
+ if device.net.port != 0 {
+ send(fmt.Sprintf("listen_port=%d", device.net.port))
+ }
+
+ if device.net.fwmark != 0 {
+ send(fmt.Sprintf("fwmark=%d", device.net.fwmark))
+ }
+
+ // serialize each peer state
+
+ for _, peer := range device.peers.keyMap {
+ peer.mutex.RLock()
+ defer peer.mutex.RUnlock()
+
+ send("public_key=" + peer.handshake.remoteStatic.ToHex())
+ send("preshared_key=" + peer.handshake.presharedKey.ToHex())
+ if peer.endpoint != nil {
+ send("endpoint=" + peer.endpoint.DstToString())
+ }
+
+ nano := atomic.LoadInt64(&peer.stats.lastHandshakeNano)
+ secs := nano / time.Second.Nanoseconds()
+ nano %= time.Second.Nanoseconds()
+
+ send(fmt.Sprintf("last_handshake_time_sec=%d", secs))
+ send(fmt.Sprintf("last_handshake_time_nsec=%d", nano))
+ send(fmt.Sprintf("tx_bytes=%d", peer.stats.txBytes))
+ send(fmt.Sprintf("rx_bytes=%d", peer.stats.rxBytes))
+ send(fmt.Sprintf("persistent_keepalive_interval=%d",
+ atomic.LoadUint64(&peer.persistentKeepaliveInterval),
+ ))
+
+ for _, ip := range device.routing.table.AllowedIPs(peer) {
+ send("allowed_ip=" + ip.String())
+ }
+
+ }
+ }()
+
+ // send lines (does not require resource locks)
+
+ for _, line := range lines {
+ _, err := socket.WriteString(line + "\n")
+ if err != nil {
+ return &IPCError{
+ Code: ipcErrorIO,
+ }
+ }
+ }
+
+ return nil
+}
+
+func ipcSetOperation(device *Device, socket *bufio.ReadWriter) *IPCError {
+ scanner := bufio.NewScanner(socket)
+ logError := device.log.Error
+ logDebug := device.log.Debug
+
+ var peer *Peer
+
+ dummy := false
+ deviceConfig := true
+
+ for scanner.Scan() {
+
+ // parse line
+
+ line := scanner.Text()
+ if line == "" {
+ return nil
+ }
+ parts := strings.Split(line, "=")
+ if len(parts) != 2 {
+ return &IPCError{Code: ipcErrorProtocol}
+ }
+ key := parts[0]
+ value := parts[1]
+
+ /* device configuration */
+
+ if deviceConfig {
+
+ switch key {
+ case "private_key":
+ var sk NoisePrivateKey
+ err := sk.FromHex(value)
+ if err != nil {
+ logError.Println("Failed to set private_key:", err)
+ return &IPCError{Code: ipcErrorInvalid}
+ }
+ logDebug.Println("UAPI: Updating device private key")
+ device.SetPrivateKey(sk)
+
+ case "listen_port":
+
+ // parse port number
+
+ port, err := strconv.ParseUint(value, 10, 16)
+ if err != nil {
+ logError.Println("Failed to parse listen_port:", err)
+ return &IPCError{Code: ipcErrorInvalid}
+ }
+
+ // update port and rebind
+
+ logDebug.Println("UAPI: Updating listen port")
+
+ device.net.mutex.Lock()
+ device.net.port = uint16(port)
+ device.net.mutex.Unlock()
+
+ if err := device.BindUpdate(); err != nil {
+ logError.Println("Failed to set listen_port:", err)
+ return &IPCError{Code: ipcErrorPortInUse}
+ }
+
+ case "fwmark":
+
+ // parse fwmark field
+
+ fwmark, err := func() (uint32, error) {
+ if value == "" {
+ return 0, nil
+ }
+ mark, err := strconv.ParseUint(value, 10, 32)
+ return uint32(mark), err
+ }()
+
+ if err != nil {
+ logError.Println("Invalid fwmark", err)
+ return &IPCError{Code: ipcErrorInvalid}
+ }
+
+ logDebug.Println("UAPI: Updating fwmark")
+
+ device.net.mutex.Lock()
+ device.net.fwmark = uint32(fwmark)
+ device.net.mutex.Unlock()
+
+ if err := device.BindUpdate(); err != nil {
+ logError.Println("Failed to update fwmark:", err)
+ return &IPCError{Code: ipcErrorPortInUse}
+ }
+
+ case "public_key":
+ // switch to peer configuration
+ logDebug.Println("UAPI: Transition to peer configuration")
+ deviceConfig = false
+
+ case "replace_peers":
+ if value != "true" {
+ logError.Println("Failed to set replace_peers, invalid value:", value)
+ return &IPCError{Code: ipcErrorInvalid}
+ }
+ logDebug.Println("UAPI: Removing all peers")
+ device.RemoveAllPeers()
+
+ default:
+ logError.Println("Invalid UAPI key (device configuration):", key)
+ return &IPCError{Code: ipcErrorInvalid}
+ }
+ }
+
+ /* peer configuration */
+
+ if !deviceConfig {
+
+ switch key {
+
+ case "public_key":
+ var publicKey NoisePublicKey
+ err := publicKey.FromHex(value)
+ if err != nil {
+ logError.Println("Failed to get peer by public_key:", err)
+ return &IPCError{Code: ipcErrorInvalid}
+ }
+
+ // ignore peer with public key of device
+
+ device.noise.mutex.RLock()
+ equals := device.noise.publicKey.Equals(publicKey)
+ device.noise.mutex.RUnlock()
+
+ if equals {
+ peer = &Peer{}
+ dummy = true
+ }
+
+ // find peer referenced
+
+ peer = device.LookupPeer(publicKey)
+
+ if peer == nil {
+ peer, err = device.NewPeer(publicKey)
+ if err != nil {
+ logError.Println("Failed to create new peer:", err)
+ return &IPCError{Code: ipcErrorInvalid}
+ }
+ logDebug.Println("UAPI: Created new peer:", peer.String())
+ }
+
+ peer.mutex.Lock()
+ peer.timer.handshakeDeadline.Reset(RekeyAttemptTime)
+ peer.mutex.Unlock()
+
+ case "remove":
+
+ // remove currently selected peer from device
+
+ if value != "true" {
+ logError.Println("Failed to set remove, invalid value:", value)
+ return &IPCError{Code: ipcErrorInvalid}
+ }
+ if !dummy {
+ logDebug.Println("UAPI: Removing peer:", peer.String())
+ device.RemovePeer(peer.handshake.remoteStatic)
+ }
+ peer = &Peer{}
+ dummy = true
+
+ case "preshared_key":
+
+ // update PSK
+
+ logDebug.Println("UAPI: Updating pre-shared key for peer:", peer.String())
+
+ peer.handshake.mutex.Lock()
+ err := peer.handshake.presharedKey.FromHex(value)
+ peer.handshake.mutex.Unlock()
+
+ if err != nil {
+ logError.Println("Failed to set preshared_key:", err)
+ return &IPCError{Code: ipcErrorInvalid}
+ }
+
+ case "endpoint":
+
+ // set endpoint destination
+
+ logDebug.Println("UAPI: Updating endpoint for peer:", peer.String())
+
+ err := func() error {
+ peer.mutex.Lock()
+ defer peer.mutex.Unlock()
+ endpoint, err := CreateEndpoint(value)
+ if err != nil {
+ return err
+ }
+ peer.endpoint = endpoint
+ peer.timer.handshakeDeadline.Reset(RekeyAttemptTime)
+ return nil
+ }()
+
+ if err != nil {
+ logError.Println("Failed to set endpoint:", value)
+ return &IPCError{Code: ipcErrorInvalid}
+ }
+
+ case "persistent_keepalive_interval":
+
+ // update keep-alive interval
+
+ logDebug.Println("UAPI: Updating persistent_keepalive_interval for peer:", peer.String())
+
+ secs, err := strconv.ParseUint(value, 10, 16)
+ if err != nil {
+ logError.Println("Failed to set persistent_keepalive_interval:", err)
+ return &IPCError{Code: ipcErrorInvalid}
+ }
+
+ old := atomic.SwapUint64(
+ &peer.persistentKeepaliveInterval,
+ secs,
+ )
+
+ // send immediate keep-alive
+
+ if old == 0 && secs != 0 {
+ if err != nil {
+ logError.Println("Failed to get tun device status:", err)
+ return &IPCError{Code: ipcErrorIO}
+ }
+ if device.isUp.Get() && !dummy {
+ peer.SendKeepAlive()
+ }
+ }
+
+ case "replace_allowed_ips":
+
+ logDebug.Println("UAPI: Removing all allowed IPs for peer:", peer.String())
+
+ if value != "true" {
+ logError.Println("Failed to set replace_allowed_ips, invalid value:", value)
+ return &IPCError{Code: ipcErrorInvalid}
+ }
+
+ if dummy {
+ continue
+ }
+
+ device.routing.mutex.Lock()
+ device.routing.table.RemovePeer(peer)
+ device.routing.mutex.Unlock()
+
+ case "allowed_ip":
+
+ logDebug.Println("UAPI: Adding allowed_ip to peer:", peer.String())
+
+ _, network, err := net.ParseCIDR(value)
+ if err != nil {
+ logError.Println("Failed to set allowed_ip:", err)
+ return &IPCError{Code: ipcErrorInvalid}
+ }
+
+ if dummy {
+ continue
+ }
+
+ ones, _ := network.Mask.Size()
+ device.routing.mutex.Lock()
+ device.routing.table.Insert(network.IP, uint(ones), peer)
+ device.routing.mutex.Unlock()
+
+ default:
+ logError.Println("Invalid UAPI key (peer configuration):", key)
+ return &IPCError{Code: ipcErrorInvalid}
+ }
+ }
+ }
+
+ return nil
+}
+
+func ipcHandle(device *Device, socket net.Conn) {
+
+ // create buffered read/writer
+
+ defer socket.Close()
+
+ buffered := func(s io.ReadWriter) *bufio.ReadWriter {
+ reader := bufio.NewReader(s)
+ writer := bufio.NewWriter(s)
+ return bufio.NewReadWriter(reader, writer)
+ }(socket)
+
+ defer buffered.Flush()
+
+ op, err := buffered.ReadString('\n')
+ if err != nil {
+ return
+ }
+
+ // handle operation
+
+ var status *IPCError
+
+ switch op {
+ case "set=1\n":
+ device.log.Debug.Println("Config, set operation")
+ status = ipcSetOperation(device, buffered)
+
+ case "get=1\n":
+ device.log.Debug.Println("Config, get operation")
+ status = ipcGetOperation(device, buffered)
+
+ default:
+ device.log.Error.Println("Invalid UAPI operation:", op)
+ return
+ }
+
+ // write status
+
+ if status != nil {
+ device.log.Error.Println(status)
+ fmt.Fprintf(buffered, "errno=%d\n\n", status.ErrorCode())
+ } else {
+ fmt.Fprintf(buffered, "errno=0\n\n")
+ }
+}