diff options
Diffstat (limited to 'uapi.go')
-rw-r--r-- | uapi.go | 437 |
1 files changed, 437 insertions, 0 deletions
@@ -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") + } +} |