Не удалось отправить пакет ICMP через сокет на Android API 22 (Lollipop) - PullRequest
3 голосов
/ 04 мая 2019

Я написал небольшое приложение, которое пингует удаленный сервер, отправляя 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, и я не знаю точно, что вызывает эту проблему

...