This commit is contained in:
2025-09-29 06:30:39 +08:00
parent 20c229bfb7
commit 5e692bac6c
7 files changed
+138 -66

No files matched your search

+13 -8
View File
@@ -1,9 +1,9 @@
# native-mdns # native-mdns
A Kotlin multiplatform library for mdns announcer(mdns server), only broadcast is supported, A Kotlin multiplatform library for mdns announcer(mdns server), only broadcast is supported,
it depends on ktor-network it depends on ktor-network.
support PTR. Also, nmdns can be compiled to shared/static lib for other language usage, see [build.gradle.kts](build.gradle.kts)
Supported platforms: Supported platforms:
1. windows64(mingwx64) 1. windows64(mingwx64)
@@ -12,6 +12,11 @@ Supported platforms:
4. macosArm64 4. macosArm64
5. jvm(11 or later) 5. jvm(11 or later)
Supported srv type
1. A
2. PTR
> AAAA is not supported
AirPlay2(_airplay._tcp.local.) is tested AirPlay2(_airplay._tcp.local.) is tested
@@ -35,7 +40,7 @@ kotlin {
sourceSets { sourceSets {
commonMain.dependencies { commonMain.dependencies {
implementation("cn.rtast.nmdns:native-mdns:0.0.1") implementation("cn.rtast.nmdns:native-mdns:0.0.2")
} }
} }
} }
@@ -44,7 +49,7 @@ kotlin {
There is a simple airplay2 service There is a simple airplay2 service
```kotlin ```kotlin
val service = createService( val service = registerService(
serviceType = "_airplay._tcp.local.", serviceType = "_airplay._tcp.local.",
serviceName = "AP@RTAST", serviceName = "AP@RTAST",
hostname = "rtakland.local.", hostname = "rtakland.local.",
@@ -52,9 +57,9 @@ There is a simple airplay2 service
port = 7001, port = 7001,
bindAddress = "192.168.10.104", bindAddress = "192.168.10.104",
mdnsPort = 5356, mdnsPort = 5356,
) )
while (true) { while (true) {
service.broadcast() service.broadcast()
delay(2000L) delay(2000L) // or use cn.rtast.nmdns.sleep1(2) sleep 2 seconds
} }
``` ```
+41 -4
View File
@@ -6,19 +6,32 @@ plugins {
} }
group = "cn.rtast.nmdns" group = "cn.rtast.nmdns"
version = "0.0.1" version = "0.0.2"
repositories { repositories {
mavenCentral() mavenCentral()
} }
kotlin { kotlin {
linuxArm64() val nativeTargets = listOf(
linuxX64() linuxArm64(),
mingwX64() linuxX64(),
mingwX64(),
macosArm64() macosArm64()
)
jvm { compilerOptions { jvmTarget = JvmTarget.JVM_11 } } jvm { compilerOptions { jvmTarget = JvmTarget.JVM_11 } }
nativeTargets.forEach {
it.binaries {
staticLib {
baseName = "nmdns"
}
sharedLib {
baseName = "nmdns"
}
}
}
sourceSets { sourceSets {
commonMain.dependencies { commonMain.dependencies {
implementation("io.ktor:ktor-network:3.2.3") implementation("io.ktor:ktor-network:3.2.3")
@@ -42,3 +55,27 @@ publishing {
} }
} }
} }
/**
* link all targets static lib
* Apple target can only be compiled on macos
*/
tasks.register("linkStaticLibrary") {
group = "nmdns"
dependsOn(tasks.named("linkReleaseStaticLinuxArm64"))
dependsOn(tasks.named("linkReleaseStaticLinuxX64"))
dependsOn(tasks.named("linkReleaseStaticMacosArm64"))
dependsOn(tasks.named("linkReleaseStaticMingwX64"))
}
/**
* link all targrts to shared lib
* Apple target can only be compiled on macos
*/
tasks.register("linkSharedLibrary") {
group = "nmdns"
dependsOn(tasks.named("linkReleaseSharedLinuxArm64"))
dependsOn(tasks.named("linkReleaseSharedLinuxX64"))
dependsOn(tasks.named("linkReleaseSharedMacosArm64"))
dependsOn(tasks.named("linkReleaseSharedMingwX64"))
}
@@ -40,4 +40,7 @@ private val macAddresses = listOf(
"c3:c9:1c:3d:ae:dd" "c3:c9:1c:3d:ae:dd"
) )
/**
* get random mac address
*/
fun randomMacAddress(): String = macAddresses.random() fun randomMacAddress(): String = macAddresses.random()
@@ -13,58 +13,62 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.io.Buffer import kotlinx.io.Buffer
data class NMDNSServer( data class NMDNSAnnouncer(
/** /**
* coroutine dispatcher for running server * coroutine dispatcher for running server
*/ */
val dispatcher: CoroutineDispatcher = Dispatchers.Default, internal val dispatcher: CoroutineDispatcher = Dispatchers.Default,
/** /**
* service type * service type
* default airplay * airplay service type: _airplay._tcp.local.
*/ */
val serviceType: String = "_airplay._tcp.local.", internal val serviceType: String,
/** /**
* service name * service name
* finally: $serviceName.$serviceType
*/ */
val serviceName: String = "APS", internal val serviceName: String,
/** /**
* local hostname * local hostname
*/ */
val hostname: String, internal val hostname: String,
/** /**
* service ip, lan ip mostly * service ip, lan ip mostly
*/ */
val ipAddress: String, internal val ipAddress: String,
/** /**
* service port * service port
*/ */
val port: Int = 7004, internal val port: Int = 7004,
/** /**
* mdns server port * mdns server port
*/ */
val mdnsPort: Int = 3025, internal val mdnsPort: Int = 3025,
/** /**
* Txt record content * txt record content
*/ */
val txtRecords: List<String> = listOf( internal val txtRecords: List<String>,
"deviceid=01:23:45:67:89:AB",
"model=AppleTV3,2C",
"features=0x5A7FFFF7,0x1E",
"srcvers=220.68",
"pk=f3769a660475d27b4f6040381d784645e13e21c53e6d2da6a8c3d757086fc336"
),
/** /**
* mdns socket server * mdns socket server
*/ */
val server: BoundDatagramSocket, internal val server: BoundDatagramSocket,
/** /**
* srv packet * srv packet
*/ */
val packet: Buffer, internal val packet: Buffer,
) )
suspend fun createService( /**
* register service
* airplay2 txt records
* listOf(
* "deviceid=${randomMacAddress()}",
* "model=AppleTV3,2C",
* "features=0x5A7FFFF7,0x1E",
* "srcvers=220.68",
* "pk=f3769a660475d27b4f6040381d784645e13e21c53e6d2da6a8c3d757086fc336"
* )
*/
suspend fun registerService(
serviceType: String, serviceType: String,
serviceName: String, serviceName: String,
hostname: String, hostname: String,
@@ -72,25 +76,29 @@ suspend fun createService(
port: Int, port: Int,
mdnsPort: Int = 9872, mdnsPort: Int = 9872,
bindAddress: String, bindAddress: String,
txtRecords: List<String> = listOf( txtRecords: List<String>,
"deviceid=${randomMacAddress()}", ): NMDNSAnnouncer {
"model=AppleTV3,2C",
"features=0x5A7FFFF7,0x1E",
"srcvers=220.68",
"pk=f3769a660475d27b4f6040381d784645e13e21c53e6d2da6a8c3d757086fc336"
),
): NMDNSServer {
val manager = SelectorManager(Dispatchers.Default) val manager = SelectorManager(Dispatchers.Default)
val serverSocket = aSocket(manager).udp() val serverSocket = aSocket(manager).udp()
.bind(InetSocketAddress(bindAddress, mdnsPort)) .bind(InetSocketAddress(bindAddress, mdnsPort))
return NMDNSServer( return NMDNSAnnouncer(
Dispatchers.Default, serviceType, serviceName, Dispatchers.Default,
hostname, ipAddress, port, mdnsPort, serviceType,
txtRecords, serverSocket, buildPacket(serviceType, "$serviceName.$serviceType", hostname, ipAddress, port, txtRecords) serviceName,
hostname,
ipAddress,
port,
mdnsPort,
txtRecords,
serverSocket,
buildPacket(serviceType, "$serviceName.$serviceType", hostname, ipAddress, port, txtRecords)
) )
} }
suspend fun NMDNSServer.broadcast() { /**
* broadcast packet
*/
suspend fun NMDNSAnnouncer.broadcast() {
this.server.send(Datagram(packet.copy(), InetSocketAddress("224.0.0.251", 5353))) this.server.send(Datagram(packet.copy(), InetSocketAddress("224.0.0.251", 5353)))
} }
@@ -7,4 +7,8 @@
package cn.rtast.nmdns package cn.rtast.nmdns
/**
* block current thread
* unit: second
*/
internal expect fun sleep1(time: Int) internal expect fun sleep1(time: Int)
+11 -3
View File
@@ -8,7 +8,8 @@
package test package test
import cn.rtast.nmdns.broadcast import cn.rtast.nmdns.broadcast
import cn.rtast.nmdns.createService import cn.rtast.nmdns.randomMacAddress
import cn.rtast.nmdns.registerService
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.Test import org.junit.Test
@@ -40,14 +41,21 @@ class JvmTest {
// println("sent") // println("sent")
// delay(2000L) // delay(2000L)
// } // }
val service = createService( val service = registerService(
serviceType = "_airplay._tcp.local.", serviceType = "_airplay._tcp.local.",
serviceName = "AP@RTAST", serviceName = "AP@RTAST",
hostname = "rtakland.local.", hostname = "rtakland.local.",
ipAddress = "192.168.10.104", ipAddress = "192.168.10.104",
port = 7001, port = 7001,
bindAddress = "192.168.10.104", bindAddress = "192.168.10.104",
mdnsPort = 5356 mdnsPort = 5356,
txtRecords = listOf(
"deviceid=${randomMacAddress()}",
"model=AppleTV3,2C",
"features=0x5A7FFFF7,0x1E",
"srcvers=220.68",
"pk=f3769a660475d27b4f6040381d784645e13e21c53e6d2da6a8c3d757086fc336"
)
) )
while (true) { while (true) {
service.broadcast() service.broadcast()
+15 -8
View File
@@ -1,16 +1,16 @@
/* /*
* Copyright © 2025 RTAkland * Copyright © 2025 RTAkland
* Author: RTAkland * Author: RTAkland
* Date: 9/29/25 * Date: 9/29/25
*/ */
package test package test
import cn.rtast.nmdns.broadcast import cn.rtast.nmdns.broadcast
import cn.rtast.nmdns.createService import cn.rtast.nmdns.randomMacAddress
import cn.rtast.nmdns.registerService
import cn.rtast.nmdns.sleep1 import cn.rtast.nmdns.sleep1
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlin.test.Test import kotlin.test.Test
@@ -18,14 +18,21 @@ class LinuxTest {
@Test @Test
fun `test on linux`() { fun `test on linux`() {
runBlocking { runBlocking {
val service = createService( val service = registerService(
serviceType = "_airplay._tcp.local.", serviceType = "_airplay._tcp.local.",
serviceName = "AP@RTAST", serviceName = "AP@RTAST",
hostname = "rtakland.local.", hostname = "rtakland.local.",
ipAddress = "192.168.10.104", ipAddress = "192.168.10.104",
port = 7001, port = 7001,
bindAddress = "192.168.10.104", bindAddress = "192.168.10.104",
mdnsPort = 5356 mdnsPort = 5356,
txtRecords = listOf(
"deviceid=${randomMacAddress()}",
"model=AppleTV3,2C",
"features=0x5A7FFFF7,0x1E",
"srcvers=220.68",
"pk=f3769a660475d27b4f6040381d784645e13e21c53e6d2da6a8c3d757086fc336"
)
) )
while (true) { while (true) {
service.broadcast() service.broadcast()