Я написал небольшое приложение, которое пингует удаленный сервер, отправляя ICMP-пакеты с помощью сокетов (Android-пакет Os). Я тестировал его на эмуляторе API 28, и он работал, но он не работает на API 22 (Android 5.1 Lollipop). Я протестировал его на VD, а также на моем смартфоне (тот же старый API) с тем же отрицательным результатом
android.system.ErrnoException: sendto failed: EINVAL (Invalid argument)
Я отлаживал приложение в разных версиях API, и единственное отличие, которое я заметил, заключается в том, что ByteBuffer.wrap()
создает экземпляр ByteArrayBuffer
в API 22 и экземпляр HeapArrayBuffer
в более новых версиях. Сама полезная нагрузка выглядит одинаково.
Вот пример кода (упрощенно). Также доступно тестовое приложение: https://github.com/alexeysirenko/android-sockets-icmp-ping-test
Вы можете попробовать запустить его на разных эмуляторах (API 22 и> 22) и увидеть разницу
private val timeoutMs = 5000
private val delayMs = 500L
private val ECHO_PORT = 80
private val POLLIN = (if (OsConstants.POLLIN == 0) 1 else OsConstants.POLLIN).toShort()
fun ping(host: String): Unit {
val inetAddress: InetAddress = InetAddress.getByName(host)
if (inetAddress is Inet6Address) throw Exception("IPv6 implementation omitted for simplicity")
val proto = OsConstants.IPPROTO_ICMP
val inet = OsConstants.AF_INET
val type = PacketBuilder.TYPE_ICMP_V4
val socketFileDescriptor = Os.socket(inet, OsConstants.SOCK_DGRAM, proto)
if (!socketFileDescriptor.valid()) throw Exception("Socket descriptor is invalid")
var sequenceNumber: Short = 0
for (i in 0..2) {
sequenceNumber++
val echoPacketBuilder =
PacketBuilder(type, "foobarbazquok".toByteArray())
.withSequenceNumber(sequenceNumber)
val buffer = echoPacketBuilder.build()
/**
* This is the command that throws an exception
*/
val bytesSent = Os.sendto(socketFileDescriptor, buffer,0, buffer.size, 0, inetAddress, ECHO_PORT)
// Response processing code omitted
}
}
class PacketBuilder(val type: Byte, val payload: ByteArray, val sequenceNumber: Short = 0, val identifier: Short = 0xDBB) {
private val MAX_PAYLOAD = 65507
private val CODE: Byte = 0
init {
if (payload.size > MAX_PAYLOAD) throw Exception("Payload limited to $MAX_PAYLOAD")
}
fun build(): ByteArray {
val buffer = ByteArray(8 + payload.size)
val byteBuffer = ByteBuffer.wrap(buffer)
byteBuffer.put(type)
byteBuffer.put(CODE)
val checkPos = byteBuffer.position()
byteBuffer.position(checkPos + 2)
byteBuffer.putShort(identifier)
byteBuffer.putShort(sequenceNumber)
byteBuffer.put(payload)
byteBuffer.putShort(checkPos, checksum(buffer))
byteBuffer.flip()
return buffer
}
fun withSequenceNumber(sequenceNumber: Short): PacketBuilder {
return PacketBuilder(type, payload, sequenceNumber, identifier)
}
/**
* RFC 1071 checksum
*/
private fun checksum(data: ByteArray): Short {
var sum = 0
// High bytes (even indices)
for (i in 0 until data.size step 2) {
sum += data[i].and(0xFF.toByte()).toInt() shl 8
sum = (sum and 0xFFFF) + (sum shr 16)
}
// Low bytes (odd indices)
for (i in 1 until data.size step 2) {
sum += data[i] and 0xFF.toByte()
sum = (sum and 0xFFFF) + (sum shr 16)
}
sum = (sum and 0xFFFF) + (sum shr 16)
return (sum xor 0xFFFF).toShort()
}
companion object {
val TYPE_ICMP_V4: Byte = 8
}
}
Если мой код неверен, то я ожидаю одинаковую ошибку на всех платформах, но, как я уже сказал, она работает на всех API, начиная с версии 22, и я не знаю точно, что вызывает эту проблему