【问题标题】:Convert IP (string) into long in elasticsearch/kibana scripted fields在 elasticsearch/kibana 脚本字段中将 IP(字符串)转换为 long
【发布时间】:2018-11-04 15:39:25
【问题描述】:

我在文档中有一个字段,它是 ipv4(“1.2.3.4”)的字符串表示形式,该字段的名称是“originating_ip”。 我正在尝试使用无痛语言的脚本字段,以便添加一个新字段 (originating_ip_calc) 以获得所述 IPv4 的 int (long) 表示。

以下脚本在 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,但没有成功。

【问题讨论】:

  • 您似乎了解 IPv6;可惜你没有要求更多。 long longIP = ip &lt;&lt; power*8L; result |= longIP;(左移,8L 的长结果,按位或)。

标签: elasticsearch elasticsearch-painless


【解决方案1】:

通过 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(因为它隐式地创建了正则表达式模式)。他们建议使用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 数组?

因为显然painless 也不允许使用String.length 方法:

    "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

虽然我在快速谷歌搜索后找不到任何关于命名的历史记录,但从documentation page 和一些经验(如上面的这个答案)我可以推断出它的设计是无痛的使用生产

它的前身,Groovy,由于资源使用和security vulnerabilities,是一颗定时炸弹。因此,Elasticsearch 团队创建了一个非常有限的 Java/Groovy 脚本子集,它具有可预测的性能并且不会包含这些安全漏洞,并将其命名为 painless

如果painless 脚本语言有任何真实性,那就是它是受限沙盒

【讨论】:

  • 惊人的答案!感谢那。由于某些原因,某些东西仍然无法正常工作。我会尝试理解是什么/为什么并在那之后批准答案:) 再次感谢!
  • 需要使用doc['originating_ip.keyword'].value 来获取originating_ip 字段的字符串值。谢谢!
猜你喜欢
  • 2018-05-07
  • 2016-04-06
  • 1970-01-01
  • 1970-01-01
  • 2020-12-02
  • 2019-06-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多