add lua codec parsing

This commit is contained in:
Mark Puha 2025-02-09 09:50:24 +01:00
parent 6b0bbcc75c
commit f4bc11733d
3 changed files with 80 additions and 61 deletions

View file

@ -12,6 +12,7 @@ import (
type Lua struct { type Lua struct {
state *lua.State state *lua.State
packetCounter atomic.Int64 packetCounter atomic.Int64
base64LuaCode string
} }
type LuaParams struct { type LuaParams struct {
@ -31,7 +32,7 @@ func NewLua(params LuaParams) (*Lua, error) {
if err := state.DoString(string(luaCode)); err != nil { if err := state.DoString(string(luaCode)); err != nil {
return nil, fmt.Errorf("Error loading Lua code: %v\n", err) return nil, fmt.Errorf("Error loading Lua code: %v\n", err)
} }
return &Lua{state: state}, nil return &Lua{state: state, base64LuaCode: params.Base64LuaCode}, nil
} }
func (l *Lua) Close() { func (l *Lua) Close() {
@ -71,3 +72,7 @@ func (l *Lua) Parse(data []byte) ([]byte, error) {
return result, nil return result, nil
} }
func (l *Lua) Base64LuaCode() string {
return l.base64LuaCode
}

View file

@ -595,43 +595,43 @@ func (device *Device) resetProtocol() {
MessageTransportType = DefaultMessageTransportType MessageTransportType = DefaultMessageTransportType
} }
func (device *Device) handlePostConfig(tempASecCfg *aSecCfgType) (err error) { func (device *Device) handlePostConfig(tempAwgType *awgType) (err error) {
if !tempASecCfg.isSet { if !tempAwgType.aSecCfg.isSet {
return nil return nil
} }
isASecOn := false isASecOn := false
device.awg.aSecMux.Lock() device.awg.aSecMux.Lock()
if tempASecCfg.junkPacketCount < 0 { if tempAwgType.aSecCfg.junkPacketCount < 0 {
err = ipcErrorf( err = ipcErrorf(
ipc.IpcErrorInvalid, ipc.IpcErrorInvalid,
"JunkPacketCount should be non negative", "JunkPacketCount should be non negative",
) )
} }
device.awg.aSecCfg.junkPacketCount = tempASecCfg.junkPacketCount device.awg.aSecCfg.junkPacketCount = tempAwgType.aSecCfg.junkPacketCount
if tempASecCfg.junkPacketCount != 0 { if tempAwgType.aSecCfg.junkPacketCount != 0 {
isASecOn = true isASecOn = true
} }
device.awg.aSecCfg.junkPacketMinSize = tempASecCfg.junkPacketMinSize device.awg.aSecCfg.junkPacketMinSize = tempAwgType.aSecCfg.junkPacketMinSize
if tempASecCfg.junkPacketMinSize != 0 { if tempAwgType.aSecCfg.junkPacketMinSize != 0 {
isASecOn = true isASecOn = true
} }
if device.awg.aSecCfg.junkPacketCount > 0 && if device.awg.aSecCfg.junkPacketCount > 0 &&
tempASecCfg.junkPacketMaxSize == tempASecCfg.junkPacketMinSize { tempAwgType.aSecCfg.junkPacketMaxSize == tempAwgType.aSecCfg.junkPacketMinSize {
tempASecCfg.junkPacketMaxSize++ // to make rand gen work tempAwgType.aSecCfg.junkPacketMaxSize++ // to make rand gen work
} }
if tempASecCfg.junkPacketMaxSize >= MaxSegmentSize { if tempAwgType.aSecCfg.junkPacketMaxSize >= MaxSegmentSize {
device.awg.aSecCfg.junkPacketMinSize = 0 device.awg.aSecCfg.junkPacketMinSize = 0
device.awg.aSecCfg.junkPacketMaxSize = 1 device.awg.aSecCfg.junkPacketMaxSize = 1
if err != nil { if err != nil {
err = ipcErrorf( err = ipcErrorf(
ipc.IpcErrorInvalid, ipc.IpcErrorInvalid,
"JunkPacketMaxSize: %d; should be smaller than maxSegmentSize: %d; %w", "JunkPacketMaxSize: %d; should be smaller than maxSegmentSize: %d; %w",
tempASecCfg.junkPacketMaxSize, tempAwgType.aSecCfg.junkPacketMaxSize,
MaxSegmentSize, MaxSegmentSize,
err, err,
) )
@ -639,41 +639,41 @@ func (device *Device) handlePostConfig(tempASecCfg *aSecCfgType) (err error) {
err = ipcErrorf( err = ipcErrorf(
ipc.IpcErrorInvalid, ipc.IpcErrorInvalid,
"JunkPacketMaxSize: %d; should be smaller than maxSegmentSize: %d", "JunkPacketMaxSize: %d; should be smaller than maxSegmentSize: %d",
tempASecCfg.junkPacketMaxSize, tempAwgType.aSecCfg.junkPacketMaxSize,
MaxSegmentSize, MaxSegmentSize,
) )
} }
} else if tempASecCfg.junkPacketMaxSize < tempASecCfg.junkPacketMinSize { } else if tempAwgType.aSecCfg.junkPacketMaxSize < tempAwgType.aSecCfg.junkPacketMinSize {
if err != nil { if err != nil {
err = ipcErrorf( err = ipcErrorf(
ipc.IpcErrorInvalid, ipc.IpcErrorInvalid,
"maxSize: %d; should be greater than minSize: %d; %w", "maxSize: %d; should be greater than minSize: %d; %w",
tempASecCfg.junkPacketMaxSize, tempAwgType.aSecCfg.junkPacketMaxSize,
tempASecCfg.junkPacketMinSize, tempAwgType.aSecCfg.junkPacketMinSize,
err, err,
) )
} else { } else {
err = ipcErrorf( err = ipcErrorf(
ipc.IpcErrorInvalid, ipc.IpcErrorInvalid,
"maxSize: %d; should be greater than minSize: %d", "maxSize: %d; should be greater than minSize: %d",
tempASecCfg.junkPacketMaxSize, tempAwgType.aSecCfg.junkPacketMaxSize,
tempASecCfg.junkPacketMinSize, tempAwgType.aSecCfg.junkPacketMinSize,
) )
} }
} else { } else {
device.awg.aSecCfg.junkPacketMaxSize = tempASecCfg.junkPacketMaxSize device.awg.aSecCfg.junkPacketMaxSize = tempAwgType.aSecCfg.junkPacketMaxSize
} }
if tempASecCfg.junkPacketMaxSize != 0 { if tempAwgType.aSecCfg.junkPacketMaxSize != 0 {
isASecOn = true isASecOn = true
} }
if MessageInitiationSize+tempASecCfg.initPacketJunkSize >= MaxSegmentSize { if MessageInitiationSize+tempAwgType.aSecCfg.initPacketJunkSize >= MaxSegmentSize {
if err != nil { if err != nil {
err = ipcErrorf( err = ipcErrorf(
ipc.IpcErrorInvalid, ipc.IpcErrorInvalid,
`init header size(148) + junkSize:%d; should be smaller than maxSegmentSize: %d; %w`, `init header size(148) + junkSize:%d; should be smaller than maxSegmentSize: %d; %w`,
tempASecCfg.initPacketJunkSize, tempAwgType.aSecCfg.initPacketJunkSize,
MaxSegmentSize, MaxSegmentSize,
err, err,
) )
@ -681,24 +681,24 @@ func (device *Device) handlePostConfig(tempASecCfg *aSecCfgType) (err error) {
err = ipcErrorf( err = ipcErrorf(
ipc.IpcErrorInvalid, ipc.IpcErrorInvalid,
`init header size(148) + junkSize:%d; should be smaller than maxSegmentSize: %d`, `init header size(148) + junkSize:%d; should be smaller than maxSegmentSize: %d`,
tempASecCfg.initPacketJunkSize, tempAwgType.aSecCfg.initPacketJunkSize,
MaxSegmentSize, MaxSegmentSize,
) )
} }
} else { } else {
device.awg.aSecCfg.initPacketJunkSize = tempASecCfg.initPacketJunkSize device.awg.aSecCfg.initPacketJunkSize = tempAwgType.aSecCfg.initPacketJunkSize
} }
if tempASecCfg.initPacketJunkSize != 0 { if tempAwgType.aSecCfg.initPacketJunkSize != 0 {
isASecOn = true isASecOn = true
} }
if MessageResponseSize+tempASecCfg.responsePacketJunkSize >= MaxSegmentSize { if MessageResponseSize+tempAwgType.aSecCfg.responsePacketJunkSize >= MaxSegmentSize {
if err != nil { if err != nil {
err = ipcErrorf( err = ipcErrorf(
ipc.IpcErrorInvalid, ipc.IpcErrorInvalid,
`response header size(92) + junkSize:%d; should be smaller than maxSegmentSize: %d; %w`, `response header size(92) + junkSize:%d; should be smaller than maxSegmentSize: %d; %w`,
tempASecCfg.responsePacketJunkSize, tempAwgType.aSecCfg.responsePacketJunkSize,
MaxSegmentSize, MaxSegmentSize,
err, err,
) )
@ -706,52 +706,52 @@ func (device *Device) handlePostConfig(tempASecCfg *aSecCfgType) (err error) {
err = ipcErrorf( err = ipcErrorf(
ipc.IpcErrorInvalid, ipc.IpcErrorInvalid,
`response header size(92) + junkSize:%d; should be smaller than maxSegmentSize: %d`, `response header size(92) + junkSize:%d; should be smaller than maxSegmentSize: %d`,
tempASecCfg.responsePacketJunkSize, tempAwgType.aSecCfg.responsePacketJunkSize,
MaxSegmentSize, MaxSegmentSize,
) )
} }
} else { } else {
device.awg.aSecCfg.responsePacketJunkSize = tempASecCfg.responsePacketJunkSize device.awg.aSecCfg.responsePacketJunkSize = tempAwgType.aSecCfg.responsePacketJunkSize
} }
if tempASecCfg.responsePacketJunkSize != 0 { if tempAwgType.aSecCfg.responsePacketJunkSize != 0 {
isASecOn = true isASecOn = true
} }
if tempASecCfg.initPacketMagicHeader > 4 { if tempAwgType.aSecCfg.initPacketMagicHeader > 4 {
isASecOn = true isASecOn = true
device.log.Verbosef("UAPI: Updating init_packet_magic_header") device.log.Verbosef("UAPI: Updating init_packet_magic_header")
device.awg.aSecCfg.initPacketMagicHeader = tempASecCfg.initPacketMagicHeader device.awg.aSecCfg.initPacketMagicHeader = tempAwgType.aSecCfg.initPacketMagicHeader
MessageInitiationType = device.awg.aSecCfg.initPacketMagicHeader MessageInitiationType = device.awg.aSecCfg.initPacketMagicHeader
} else { } else {
device.log.Verbosef("UAPI: Using default init type") device.log.Verbosef("UAPI: Using default init type")
MessageInitiationType = DefaultMessageInitiationType MessageInitiationType = DefaultMessageInitiationType
} }
if tempASecCfg.responsePacketMagicHeader > 4 { if tempAwgType.aSecCfg.responsePacketMagicHeader > 4 {
isASecOn = true isASecOn = true
device.log.Verbosef("UAPI: Updating response_packet_magic_header") device.log.Verbosef("UAPI: Updating response_packet_magic_header")
device.awg.aSecCfg.responsePacketMagicHeader = tempASecCfg.responsePacketMagicHeader device.awg.aSecCfg.responsePacketMagicHeader = tempAwgType.aSecCfg.responsePacketMagicHeader
MessageResponseType = device.awg.aSecCfg.responsePacketMagicHeader MessageResponseType = device.awg.aSecCfg.responsePacketMagicHeader
} else { } else {
device.log.Verbosef("UAPI: Using default response type") device.log.Verbosef("UAPI: Using default response type")
MessageResponseType = DefaultMessageResponseType MessageResponseType = DefaultMessageResponseType
} }
if tempASecCfg.underloadPacketMagicHeader > 4 { if tempAwgType.aSecCfg.underloadPacketMagicHeader > 4 {
isASecOn = true isASecOn = true
device.log.Verbosef("UAPI: Updating underload_packet_magic_header") device.log.Verbosef("UAPI: Updating underload_packet_magic_header")
device.awg.aSecCfg.underloadPacketMagicHeader = tempASecCfg.underloadPacketMagicHeader device.awg.aSecCfg.underloadPacketMagicHeader = tempAwgType.aSecCfg.underloadPacketMagicHeader
MessageCookieReplyType = device.awg.aSecCfg.underloadPacketMagicHeader MessageCookieReplyType = device.awg.aSecCfg.underloadPacketMagicHeader
} else { } else {
device.log.Verbosef("UAPI: Using default underload type") device.log.Verbosef("UAPI: Using default underload type")
MessageCookieReplyType = DefaultMessageCookieReplyType MessageCookieReplyType = DefaultMessageCookieReplyType
} }
if tempASecCfg.transportPacketMagicHeader > 4 { if tempAwgType.aSecCfg.transportPacketMagicHeader > 4 {
isASecOn = true isASecOn = true
device.log.Verbosef("UAPI: Updating transport_packet_magic_header") device.log.Verbosef("UAPI: Updating transport_packet_magic_header")
device.awg.aSecCfg.transportPacketMagicHeader = tempASecCfg.transportPacketMagicHeader device.awg.aSecCfg.transportPacketMagicHeader = tempAwgType.aSecCfg.transportPacketMagicHeader
MessageTransportType = device.awg.aSecCfg.transportPacketMagicHeader MessageTransportType = device.awg.aSecCfg.transportPacketMagicHeader
} else { } else {
device.log.Verbosef("UAPI: Using default transport type") device.log.Verbosef("UAPI: Using default transport type")
@ -828,6 +828,7 @@ func (device *Device) handlePostConfig(tempASecCfg *aSecCfgType) (err error) {
if device.awg.isASecOn.IsSet() { if device.awg.isASecOn.IsSet() {
device.awg.junkCreator, err = NewJunkCreator(device) device.awg.junkCreator, err = NewJunkCreator(device)
} }
device.awg.luaAdapter = tempAwgType.luaAdapter
device.awg.aSecMux.Unlock() device.awg.aSecMux.Unlock()
return err return err

View file

@ -18,6 +18,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/amnezia-vpn/amneziawg-go/adapter"
"github.com/amnezia-vpn/amneziawg-go/ipc" "github.com/amnezia-vpn/amneziawg-go/ipc"
) )
@ -97,6 +98,9 @@ func (device *Device) IpcGetOperation(w io.Writer) error {
sendf("fwmark=%d", device.net.fwmark) sendf("fwmark=%d", device.net.fwmark)
} }
if device.awg.luaAdapter != nil {
sendf("lua_codec=%s", device.awg.luaAdapter.Base64LuaCode())
}
if device.isAdvancedSecurityOn() { if device.isAdvancedSecurityOn() {
if device.awg.aSecCfg.junkPacketCount != 0 { if device.awg.aSecCfg.junkPacketCount != 0 {
sendf("jc=%d", device.awg.aSecCfg.junkPacketCount) sendf("jc=%d", device.awg.aSecCfg.junkPacketCount)
@ -180,13 +184,13 @@ func (device *Device) IpcSetOperation(r io.Reader) (err error) {
peer := new(ipcSetPeer) peer := new(ipcSetPeer)
deviceConfig := true deviceConfig := true
tempASecCfg := aSecCfgType{} tempAwgTpe := awgType{}
scanner := bufio.NewScanner(r) scanner := bufio.NewScanner(r)
for scanner.Scan() { for scanner.Scan() {
line := scanner.Text() line := scanner.Text()
if line == "" { if line == "" {
// Blank line means terminate operation. // Blank line means terminate operation.
err := device.handlePostConfig(&tempASecCfg) err := device.handlePostConfig(&tempAwgTpe)
if err != nil { if err != nil {
return err return err
} }
@ -217,7 +221,7 @@ func (device *Device) IpcSetOperation(r io.Reader) (err error) {
var err error var err error
if deviceConfig { if deviceConfig {
err = device.handleDeviceLine(key, value, &tempASecCfg) err = device.handleDeviceLine(key, value, &tempAwgTpe)
} else { } else {
err = device.handlePeerLine(peer, key, value) err = device.handlePeerLine(peer, key, value)
} }
@ -225,7 +229,7 @@ func (device *Device) IpcSetOperation(r io.Reader) (err error) {
return err return err
} }
} }
err = device.handlePostConfig(&tempASecCfg) err = device.handlePostConfig(&tempAwgTpe)
if err != nil { if err != nil {
return err return err
} }
@ -237,7 +241,7 @@ func (device *Device) IpcSetOperation(r io.Reader) (err error) {
return nil return nil
} }
func (device *Device) handleDeviceLine(key, value string, tempASecCfg *aSecCfgType) error { func (device *Device) handleDeviceLine(key, value string, tempAwgTpe *awgType) error {
switch key { switch key {
case "private_key": case "private_key":
var sk NoisePrivateKey var sk NoisePrivateKey
@ -283,14 +287,23 @@ func (device *Device) handleDeviceLine(key, value string, tempASecCfg *aSecCfgTy
device.log.Verbosef("UAPI: Removing all peers") device.log.Verbosef("UAPI: Removing all peers")
device.RemoveAllPeers() device.RemoveAllPeers()
case "lua_codec":
device.log.Verbosef("UAPI: Updating lua_codec")
var err error
tempAwgTpe.luaAdapter, err = adapter.NewLua(adapter.LuaParams{
Base64LuaCode: value,
})
if err != nil {
return ipcErrorf(ipc.IpcErrorInvalid, "invalid lua_codec: %w", err)
}
case "jc": case "jc":
junkPacketCount, err := strconv.Atoi(value) junkPacketCount, err := strconv.Atoi(value)
if err != nil { if err != nil {
return ipcErrorf(ipc.IpcErrorInvalid, "faield to parse junk_packet_count %w", err) return ipcErrorf(ipc.IpcErrorInvalid, "faield to parse junk_packet_count %w", err)
} }
device.log.Verbosef("UAPI: Updating junk_packet_count") device.log.Verbosef("UAPI: Updating junk_packet_count")
tempASecCfg.junkPacketCount = junkPacketCount tempAwgTpe.aSecCfg.junkPacketCount = junkPacketCount
tempASecCfg.isSet = true tempAwgTpe.aSecCfg.isSet = true
case "jmin": case "jmin":
junkPacketMinSize, err := strconv.Atoi(value) junkPacketMinSize, err := strconv.Atoi(value)
@ -298,8 +311,8 @@ func (device *Device) handleDeviceLine(key, value string, tempASecCfg *aSecCfgTy
return ipcErrorf(ipc.IpcErrorInvalid, "faield to parse junk_packet_min_size %w", err) return ipcErrorf(ipc.IpcErrorInvalid, "faield to parse junk_packet_min_size %w", err)
} }
device.log.Verbosef("UAPI: Updating junk_packet_min_size") device.log.Verbosef("UAPI: Updating junk_packet_min_size")
tempASecCfg.junkPacketMinSize = junkPacketMinSize tempAwgTpe.aSecCfg.junkPacketMinSize = junkPacketMinSize
tempASecCfg.isSet = true tempAwgTpe.aSecCfg.isSet = true
case "jmax": case "jmax":
junkPacketMaxSize, err := strconv.Atoi(value) junkPacketMaxSize, err := strconv.Atoi(value)
@ -307,8 +320,8 @@ func (device *Device) handleDeviceLine(key, value string, tempASecCfg *aSecCfgTy
return ipcErrorf(ipc.IpcErrorInvalid, "faield to parse junk_packet_max_size %w", err) return ipcErrorf(ipc.IpcErrorInvalid, "faield to parse junk_packet_max_size %w", err)
} }
device.log.Verbosef("UAPI: Updating junk_packet_max_size") device.log.Verbosef("UAPI: Updating junk_packet_max_size")
tempASecCfg.junkPacketMaxSize = junkPacketMaxSize tempAwgTpe.aSecCfg.junkPacketMaxSize = junkPacketMaxSize
tempASecCfg.isSet = true tempAwgTpe.aSecCfg.isSet = true
case "s1": case "s1":
initPacketJunkSize, err := strconv.Atoi(value) initPacketJunkSize, err := strconv.Atoi(value)
@ -316,8 +329,8 @@ func (device *Device) handleDeviceLine(key, value string, tempASecCfg *aSecCfgTy
return ipcErrorf(ipc.IpcErrorInvalid, "faield to parse init_packet_junk_size %w", err) return ipcErrorf(ipc.IpcErrorInvalid, "faield to parse init_packet_junk_size %w", err)
} }
device.log.Verbosef("UAPI: Updating init_packet_junk_size") device.log.Verbosef("UAPI: Updating init_packet_junk_size")
tempASecCfg.initPacketJunkSize = initPacketJunkSize tempAwgTpe.aSecCfg.initPacketJunkSize = initPacketJunkSize
tempASecCfg.isSet = true tempAwgTpe.aSecCfg.isSet = true
case "s2": case "s2":
responsePacketJunkSize, err := strconv.Atoi(value) responsePacketJunkSize, err := strconv.Atoi(value)
@ -325,40 +338,40 @@ func (device *Device) handleDeviceLine(key, value string, tempASecCfg *aSecCfgTy
return ipcErrorf(ipc.IpcErrorInvalid, "faield to parse response_packet_junk_size %w", err) return ipcErrorf(ipc.IpcErrorInvalid, "faield to parse response_packet_junk_size %w", err)
} }
device.log.Verbosef("UAPI: Updating response_packet_junk_size") device.log.Verbosef("UAPI: Updating response_packet_junk_size")
tempASecCfg.responsePacketJunkSize = responsePacketJunkSize tempAwgTpe.aSecCfg.responsePacketJunkSize = responsePacketJunkSize
tempASecCfg.isSet = true tempAwgTpe.aSecCfg.isSet = true
case "h1": case "h1":
initPacketMagicHeader, err := strconv.ParseUint(value, 10, 32) initPacketMagicHeader, err := strconv.ParseUint(value, 10, 32)
if err != nil { if err != nil {
return ipcErrorf(ipc.IpcErrorInvalid, "faield to parse init_packet_magic_header %w", err) return ipcErrorf(ipc.IpcErrorInvalid, "faield to parse init_packet_magic_header %w", err)
} }
tempASecCfg.initPacketMagicHeader = uint32(initPacketMagicHeader) tempAwgTpe.aSecCfg.initPacketMagicHeader = uint32(initPacketMagicHeader)
tempASecCfg.isSet = true tempAwgTpe.aSecCfg.isSet = true
case "h2": case "h2":
responsePacketMagicHeader, err := strconv.ParseUint(value, 10, 32) responsePacketMagicHeader, err := strconv.ParseUint(value, 10, 32)
if err != nil { if err != nil {
return ipcErrorf(ipc.IpcErrorInvalid, "faield to parse response_packet_magic_header %w", err) return ipcErrorf(ipc.IpcErrorInvalid, "faield to parse response_packet_magic_header %w", err)
} }
tempASecCfg.responsePacketMagicHeader = uint32(responsePacketMagicHeader) tempAwgTpe.aSecCfg.responsePacketMagicHeader = uint32(responsePacketMagicHeader)
tempASecCfg.isSet = true tempAwgTpe.aSecCfg.isSet = true
case "h3": case "h3":
underloadPacketMagicHeader, err := strconv.ParseUint(value, 10, 32) underloadPacketMagicHeader, err := strconv.ParseUint(value, 10, 32)
if err != nil { if err != nil {
return ipcErrorf(ipc.IpcErrorInvalid, "faield to parse underload_packet_magic_header %w", err) return ipcErrorf(ipc.IpcErrorInvalid, "faield to parse underload_packet_magic_header %w", err)
} }
tempASecCfg.underloadPacketMagicHeader = uint32(underloadPacketMagicHeader) tempAwgTpe.aSecCfg.underloadPacketMagicHeader = uint32(underloadPacketMagicHeader)
tempASecCfg.isSet = true tempAwgTpe.aSecCfg.isSet = true
case "h4": case "h4":
transportPacketMagicHeader, err := strconv.ParseUint(value, 10, 32) transportPacketMagicHeader, err := strconv.ParseUint(value, 10, 32)
if err != nil { if err != nil {
return ipcErrorf(ipc.IpcErrorInvalid, "faield to parse transport_packet_magic_header %w", err) return ipcErrorf(ipc.IpcErrorInvalid, "faield to parse transport_packet_magic_header %w", err)
} }
tempASecCfg.transportPacketMagicHeader = uint32(transportPacketMagicHeader) tempAwgTpe.aSecCfg.transportPacketMagicHeader = uint32(transportPacketMagicHeader)
tempASecCfg.isSet = true tempAwgTpe.aSecCfg.isSet = true
default: default:
return ipcErrorf(ipc.IpcErrorInvalid, "invalid UAPI device key: %v", key) return ipcErrorf(ipc.IpcErrorInvalid, "invalid UAPI device key: %v", key)