Конвертируйте IP (строку) в long в полях, запрограммированных вasticsearch / kibana - PullRequest
0 голосов
/ 04 ноября 2018

У меня есть поле в документе, которое является строковым представлением ipv4 («1.2.3.4»), имя поля «originating_ip». Я пытаюсь использовать скриптовые поля, используя безболезненный язык, чтобы добавить новое поле (originating_ip_calc) для представления int (long) указанного IPv4.

Следующий скрипт работает в Groovy (и из того, что я понимаю, это должно в основном работать почти так же), но, похоже, почти не в этом конкретном случае.

​String[] ipAddressInArray = "1.2.3.4".split("\\.");

long result = 0;
for (int i = 0; i < ipAddressInArray.length; i++) {
    int power = 3 - i;
    int ip = Integer.parseInt(ipAddressInArray[i]);
    long longIP = (ip * Math.pow(256, power)).toLong();
    result = result + longIP;
}
return result;

Я также смотрю на этот вопрос , и, как видно из приведенного выше кода, он основан на одном из ответов там.

Также пытался работать с InetAddress, но безуспешно.

1 Ответ

0 голосов
/ 05 ноября 2018

С помощью безболезненных сценариев Elasticsearch вы можете использовать следующий код:

POST ip_search/doc/_search
{
  "query": {
    "match_all": {}
  },
  "script_fields": {
    "originating_ip_calc": {
      "script": {
        "source": """
String ip_addr = params['_source']['originating_ip'];
def ip_chars = ip_addr.toCharArray();
int chars_len = ip_chars.length;
long result = 0;
int cur_power = 0;
int last_dot = chars_len;
for(int i = chars_len -1; i>=-1; i--) {
  if (i == -1 || ip_chars[i] == (char) '.' ){
    result += (Integer.parseInt(ip_addr.substring(i+ 1, last_dot)) * Math.pow(256, cur_power));
    last_dot = i;
    cur_power += 1;
  }
}         
return result
""",
        "lang": "painless"
      }
    }
  },
  "_source": ["originating_ip"]
}

(обратите внимание, что я использовал консоль Kibana для отправки запроса в ES, он делает некоторые экранирования, чтобы сделать этот код допустимым JSON перед отправкой.)

Это даст ответ, подобный этому:

"hits": [
  {
    "_index": "ip_search",
    "_type": "doc",
    "_id": "2",
    "_score": 1,
    "_source": {
      "originating_ip": "10.0.0.1"
    },
    "fields": {
      "originating_ip_calc": [
        167772161
      ]
    }
  },
  {
    "_index": "ip_search",
    "_type": "doc",
    "_id": "1",
    "_score": 1,
    "_source": {
      "originating_ip": "1.2.3.4"
    },
    "fields": {
      "originating_ip_calc": [
        16909060
      ]
    }
  }
]

Но почему так должно быть?

Почему подход с .split не работает?

Если вы отправите код из вопроса в ES, он ответит с ошибкой, подобной этой:

      "script": "String[] ipAddressInArray = \"1.2.3.4\".split(\"\\\\.\");\n\nlong result = 0;\nfor (int i = 0; i < ipAddressInArray.length; i++) {\n    int power = 3 - i;\n    int ip = Integer.parseInt(ipAddressInArray[i]);\n    long longIP = (ip * Math.pow(256, power)).toLong();\n    result = result + longIP;\n}\nreturn result;",
      "lang": "painless",
      "caused_by": {
        "type": "illegal_argument_exception",
        "reason": "Unknown call [split] with [1] arguments on type [String]."

Это в основном связано с тем, что Java String.split() не считается безопасным для использования (поскольку он неявно создает шаблон регулярных выражений). Они предлагают использовать Pattern # split , но для этого в вашем индексе должны быть включены регулярные выражения.

По умолчанию они отключены:

      "script": "String[] ipAddressInArray = /\\./.split(\"1.2.3.4\");...
      "lang": "painless",
      "caused_by": {
        "type": "illegal_state_exception",
        "reason": "Regexes are disabled. Set [script.painless.regex.enabled] to [true] in elasticsearch.yaml to allow them. Be careful though, regexes break out of Painless's protection against deep recursion and long loops."

Почему мы должны делать явное приведение (char) '.'?

Итак, мы должны разбить строку на точки вручную. Простой подход - сравнить каждый символ строки с '.' (что в Java означает char литерал, а не String).

Но для painless это означает String. Поэтому мы должны сделать явное приведение к char (поскольку мы выполняем итерацию по массиву символов).

Почему мы должны работать с массивом символов напрямую?

Поскольку, очевидно, painless не допускает .length метод String, а также:

    "reason": {
      "type": "script_exception",
      "reason": "compile error",
      "script_stack": [
        "\"1.2.3.4\".length",
        "         ^---- HERE"
      ],
      "script": "\"1.2.3.4\".length",
      "lang": "painless",
      "caused_by": {
        "type": "illegal_argument_exception",
        "reason": "Unknown field [length] for type [String]."
      }
    }

Так почему же он называется painless?

Хотя я не могу найти какой-либо исторической заметки об именах после быстрого поиска в Google, на странице документации и некотором опыте (как выше в этом ответе) я могу сделать вывод, что он разработан безболезненно для использования в производстве .

Его предшественник, Groovy , был тикающей бомбой из-за использования ресурсов и уязвимостей безопасности . Поэтому команда Elasticsearch создала очень ограниченный набор сценариев Java / Groovy, который имел бы предсказуемую производительность и не содержал бы этих уязвимостей безопасности, и назвал его painless.

Если есть что-то правдивое в painless языке сценариев, является ли он ограниченным и песочницей .


Надеюсь, это поможет!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...