Skip to content

Commit 7c79987

Browse files
authored
Merge pull request #5 from kcen/master
support ipv6 client ips
2 parents c88e686 + 414324e commit 7c79987

File tree

4 files changed

+107
-2
lines changed

4 files changed

+107
-2
lines changed

lib/haproxy_log_parser/line.treetop

+54-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ module HAProxyLogParser
22
grammar Line
33
rule line
44
syslog_portion:([^\[]+ '[' integer ']: ')
5-
client_ip:ip4_address ':' client_port:integer ' '
5+
client_ip:ip_address ':' client_port:integer ' '
66
'[' accept_date '] '
77
suffix:(normal_suffix / error_suffix)
88
"\n"?
@@ -38,8 +38,60 @@ module HAProxyLogParser
3838
([0-9] 2..2 ':') 2..2 ([0-9] 2..2)
3939
end
4040

41+
rule ip_address
42+
# Asserting port is a little hacky but required because of the way
43+
# HAProxy writes IPv6 addresses in the logs. It uses a valid RFC5952
44+
# method of IP:PORT, but this complicates things by having valid IP:PORT
45+
# combos that are themselves valid IPv6 addresses.
46+
# e.g. 1::2:123
47+
ip4_address / ip6_address_assert_port
48+
end
49+
4150
rule ip4_address
42-
([0-9] 1..3 '.') 3..3 ([0-9] 1..3)
51+
dec_octet '.' dec_octet '.' dec_octet '.' dec_octet
52+
end
53+
54+
rule colon_port
55+
':' integer ' '
56+
end
57+
58+
rule ip6_address_assert_port
59+
# Taken from https://tools.ietf.org/html/rfc3986#section-3.2.2
60+
# Performs look-ahead assertion on the port, assuming format IP:PORT
61+
( h16 ':' ) 6..6 ls32 &colon_port
62+
/ '::' ( h16 ':' ) 5..5 ls32 &colon_port
63+
/ ( h16 )? '::' ( h16 ':' ) 4..4 ls32 &colon_port
64+
/ ( h16 ( ':' h16 ) ..1 )? '::' ( h16 ':' ) 3..3 ls32 &colon_port
65+
/ ( h16 ( ':' h16 ) ..2 )? '::' ( h16 ':' ) 2..2 ls32 &colon_port
66+
/ ( h16 ( ':' h16 ) ..3 )? '::' h16 ':' ls32 &colon_port
67+
/ ( h16 ( ':' h16 ) ..4 )? '::' ls32 &colon_port
68+
/ ( h16 ( ':' h16 ) ..5 )? '::' h16 &colon_port
69+
/ ( h16 ( ':' h16 ) ..6 )? '::' &colon_port
70+
end
71+
72+
rule ls32
73+
# least-significant 32 bits of address
74+
( h16 ':' h16 ) / ip4_address
75+
end
76+
77+
rule h16
78+
# 16 bits of address represented in hexadecimal
79+
hex_digit 1..4
80+
end
81+
82+
rule hex_digit
83+
[a-f0-9A-F]
84+
end
85+
86+
rule dec_octet
87+
'25' [0-5]
88+
/ '2' [0-4] dec_digit
89+
/ '1' dec_digit 2..2
90+
/ dec_digit 1..2
91+
end
92+
93+
rule dec_digit
94+
[0-9]
4395
end
4496

4597
rule accept_date

spec/haproxy_log_parser_spec.rb

+36
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
map { |line| line.split(',', 2) }
77
]
88

9+
TEST_IP_ADDRESSES = IO.readlines(File.join(File.dirname(__FILE__), 'test_ips.log')).
10+
map { |line| line.chomp }
11+
912
describe '.parse' do
1013
it 'parses the good1 case correctly' do
1114
entry = HAProxyLogParser.parse(LINES['good1'])
@@ -69,6 +72,39 @@
6972
expect(entry.http_request).to eq('GET /images/image.gif HTTP/1.1')
7073
end
7174

75+
TEST_IP_ADDRESSES.each do |test_ip|
76+
it "parses generic case with test IP #{test_ip} correctly" do
77+
entry = HAProxyLogParser.parse(LINES['generic_ip_test'].sub(/IPADDRESS/, test_ip))
78+
expect(entry.client_ip).to eq(test_ip)
79+
expect(entry.client_port).to eq(50679)
80+
expect(entry.accept_date).to eq(Time.local(2012, 5, 21, 1, 35, 46, 146))
81+
expect(entry.frontend_name).to eq('webapp')
82+
expect(entry).to_not be_ssl
83+
expect(entry.backend_name).to eq('webapp_backend')
84+
expect(entry.server_name).to eq('web09')
85+
expect(entry.tq).to eq(27)
86+
expect(entry.tw).to eq(0)
87+
expect(entry.tc).to eq(1)
88+
expect(entry.tr).to eq(0)
89+
expect(entry.tt).to eq(217)
90+
expect(entry.status_code).to eq(200)
91+
expect(entry.bytes_read).to eq(1367)
92+
expect(entry.captured_request_cookie).to eq({'session' => 'abc'})
93+
expect(entry.captured_response_cookie).to eq({'session' => 'xyz'})
94+
expect(entry.termination_state).to eq('----')
95+
expect(entry.actconn).to eq(600)
96+
expect(entry.feconn).to eq(529)
97+
expect(entry.beconn).to eq(336)
98+
expect(entry.srv_conn).to eq(158)
99+
expect(entry.retries).to eq(0)
100+
expect(entry.srv_queue).to eq(0)
101+
expect(entry.backend_queue).to eq(0)
102+
expect(entry.captured_request_headers).to eq(['|| {5F41}', 'http://google.com/', ''])
103+
expect(entry.captured_response_headers).to eq([])
104+
expect(entry.http_request).to eq('GET /images/image.gif HTTP/1.1')
105+
end
106+
end
107+
72108
it 'parses connection error lines correctly' do
73109
entry = HAProxyLogParser.parse(LINES['error1'])
74110
expect(entry.client_ip).to eq('127.0.0.1')

spec/sample.log

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
good1,Aug 9 20:30:46 localhost haproxy[2022]: 10.0.8.2:34028 [09/Aug/2011:20:30:46.429] proxy-out~ proxy-out/cache1 1/0/2/126/+128 301 +223 - - LR-- 617/523/336/168/0 0/0 {www.sytadin.equipement.gouv.fr||http://trafic.1wt.eu/} {Apache|230|||http://www.sytadin.} "GET / HTTP/1.1"
22
good2,May 21 01:35:46 10.18.237.5 haproxy[26747]: 192.168.1.215:50679 [21/May/2012:01:35:46.146] webapp webapp_backend/web09 27/0/1/0/217 200 1367 session=abc session=xyz ---- 600/529/336/158/0 0/0 {#7C#7C #7B5F41#7D|http://google.com/|} "GET /images/image.gif HTTP/1.1"
3+
generic_ip_test,May 21 01:35:46 10.18.237.5 haproxy[26747]: IPADDRESS:50679 [21/May/2012:01:35:46.146] webapp webapp_backend/web09 27/0/1/0/217 200 1367 session=abc session=xyz ---- 600/529/336/158/0 0/0 {#7C#7C #7B5F41#7D|http://google.com/|} "GET /images/image.gif HTTP/1.1"
34
error1,Dec 3 18:27:14 localhost haproxy[6103]: 127.0.0.1:56059 [03/Dec/2012:17:35:10.380] frt/f1: Connection error during SSL handshake

spec/test_ips.log

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
12::3a:829
2+
123.41.31.2
3+
127.0.0.1
4+
2.3.4.50
5+
2001:db8::42:8329
6+
::ffff:192.0.2.128
7+
2001:0db8:85a3:08d3:1319:8a2e:0370:7334
8+
201:0db8:85a3::1319:8a2e:0370:7344
9+
2001:0DB8:0::0:1428:57ab
10+
2001:0DB8::1428:57ab
11+
::ffff:192.168.89.9
12+
::ffff:c0a8:5909
13+
::1.2.3.4
14+
::c0a8:5909
15+
::192.0.2.128
16+
1::2:123

0 commit comments

Comments
 (0)