在elasticsearch / kibana脚本字段中将IP(字符串)转换为long

时间:2018-11-04 15:39:25

标签: elasticsearch elasticsearch-painless

我在doc中有一个字段,它是ipv4(“ 1.2.3.4”)的字符串表示形式,该字段的名称为“ originating_ip”。 我正在尝试使用无痛语言编写脚本字段,以便添加新字段(originating_ip_calc)以具有上述IPv4的int(长整数)表示形式。

以下脚本可在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;

我也在看this question,正如您从上面的代码中看到的那样,它基于那里的答案之一。

还尝试使用InetAddress,但没有运气。

1 个答案:

答案 0 :(得分:4)

借助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 console将请求发送到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() is not considered safe to use(因为它隐式创建了regex模式)。他们建议使用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进行显式转换(因为我们要遍历一个char数组)。

为什么我们必须直接使用char数组?

因为显然painless也不允许.length的{​​{1}}方法:

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]." } } 呢?

尽管我在{@ {3}}和一些经验(如本答案中的上述内容)中进行了快速谷歌搜索,但找不到关于命名的任何历史记录,但我可以推断出它的设计旨在使无痛在生产中使用

由于资源和documentation page的使用,它的前身Groovy是一颗滴答作响的炸弹。因此,Elasticsearch团队创建了Java / Groovy脚本的非常有限的子集,该子集具有可预测的性能并且不包含那些安全漏洞,因此将其命名为painless

如果关于painless脚本语言有什么疑问,是受限沙箱


希望有帮助!