PowerShell Send-ZabbixTrap

Как и обещал, начал публиковать скрипты в репозитории, вот прямая ссылка на Send-ZabbixTrap

Итак, суть задачи.

Подумали мы и решили, что для одной из проверок мы не хотим использовать автообнаружение со стороны заббикса, мы хотим делать все на стороне проверяемого сервера без участия заббикса. Наш взгляд привлекла штатная утилита zabbix_sender.exe, но у него есть один неудобный момент — он может отправлять либо одно значение, либо файл с массивом значений. Неудобно, идем в интернет, информации не так много, но нашлось пара ссылок в документации и ссылка на русскоязычный форум с примером кода на C#. https://zabbix.org/wiki/Docs/protocols/zabbix_sender/2.0

О! C#  — значит есть вероятность отделаться малой кровью при портировании на PowerShell!

C#

string zabbixIp = "***.***.***.***";
int zabbixPort = 10051;

JavaScriptSerializer serializer = new JavaScriptSerializer();
string json = serializer.Serialize(
    new
    {
        request = "sender data",
        data = new[]
        {
            new
            {
                host = "myHost",
                key = "myItem",
                value = "1"
            }
        }
    });

byte[] header = Encoding.ASCII.GetBytes("ZBXD\x01");
byte[] length = BitConverter.GetBytes((long)json.Length);
byte[] data = Encoding.ASCII.GetBytes(json);

byte[] all = new byte[header.Length + length.Length + data.Length];

System.Buffer.BlockCopy(header, 0, all, 0, header.Length);
System.Buffer.BlockCopy(length, 0, all, header.Length, length.Length);
System.Buffer.BlockCopy(data, 0, all, header.Length + length.Length,
    data.Length);            

using (var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream,
    ProtocolType.Tcp))
{
	client.Connect(zabbixIp, zabbixPort);
	client.Send(all);


	// Заголовок

	byte[] buffer = new byte[5];
	_Receive(client, buffer, 0, buffer.Length, 10000);

	if ("ZBXD\x01" != Encoding.ASCII.GetString(buffer, 0, buffer.Length))
		throw new Exception("Invalid response");


	// Длинна сообщения

	buffer = new byte[8];
	_Receive(client, buffer, 0, buffer.Length, 10000);
	int dataLength = BitConverter.ToInt32(buffer, 0);

	if (dataLength == 0)
		throw new Exception("Invalid response");


	// Сообщение

	buffer = new byte[dataLength];
	_Receive(client, buffer, 0, buffer.Length, 10000);

	Console.WriteLine(Encoding.ASCII.GetString(buffer, 0, buffer.Length));
}
private static void _Receive(Socket socket, byte[] buffer, int offset, int size, int timeout)
{
	int startTickCount = Environment.TickCount;
	int received = 0;
	do
	{
		if (Environment.TickCount > startTickCount + timeout)
			throw new Exception("Timeout.");
		try
		{
			received += socket.Receive(buffer, offset + received, size - received, SocketFlags.None);
		}
		catch (SocketException ex)
		{
			if (ex.SocketErrorCode == SocketError.WouldBlock ||
				ex.SocketErrorCode == SocketError.IOPending ||
				ex.SocketErrorCode == SocketError.NoBufferSpaceAvailable)
			{
				// socket buffer is probably empty, wait and try again
				Thread.Sleep(30);
			}
			else
				throw ex;  // any serious error occurr
		}
	} while (received < size);
}

Смотрим найденный код, благодарим автора AlexB, изучаем и понимаем, что в C# абсолютно не сильны )) В целом все понятно, адаптирую код под пошик, но есть несколько неясных моментов:

  1. что это??
    byte[] header = Encoding.ASCII.GetBytes("ZBXD\x01")
    

    В документации черным по белому написано что сообщение состоит из HEADER = 5байт + DATALEN = 8байт + DATA = сколько-то байт

    <HEADER> - "ZBXD\x01" (5 bytes)
    <DATALEN> - data length (8 bytes). 1 will be formatted as 01/00/00/00/00/00/00/00 (eight bytes in HEX, 64 bit number)
    
  2. Зачем это?
    [byte[]]$All = New-Object System.Byte[] $([int]$Header.Length + [int]$Length.Length + [int]$Data.Length)
    [System.Buffer]::BlockCopy($Header, 0, $All, 0, $Header.Length)
    [System.Buffer]::BlockCopy($Length, 0, $All, $Header.Length, $Length.Length)
    [System.Buffer]::BlockCopy($Data, 0, $All, $Header.Length + $Length.Length, $Data.Length)
    
  3. Что написано в коде метода _Receive ???

Разберёмся в чем сможем, а в чем нет — сделаем лучше 🙂

1-е:

Поскольку мне совершенно не хотелось учиться писать на C#, я решил пойти на zabbix по ssh и  разобрать полученные снифером данные, как действовал и создатель кода на До-диезе, хотя может быть просто мне было интереснее первый раз в жизни открыть tcpdump, чем пятый раз C# 🙂 Я подобрал подходящую мне комбинацию ключей tcpdump, что бы получить только полезную информацию и вот что в итоге

root@zabbix:/# tcpdump -i eth1.32 -n -e -X -A src 10.10.70.163 and tcp port 10051
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth1.32, link-type EN10MB (Ethernet), capture size 262144 bytes
17:34:09.681796 00:15:5d:0a:33:5e > 00:15:5d:0a:6b:31, ethertype IPv4 (0x0800), length 66: 10.10.70.163.54543 > 10.10.20.27.10051: Flags [S], seq 3984953204, win 8192, options [mss 1360,nop,wscale 8,nop,nop,sackOK], length 0
        0x0000:  4500 0034 037d 4000 7f06 da75 0a0a 07a3  E..4.}@....u....
        0x0010:  0a0a 021b d50f 2743 ed85 8f74 0000 0000  ......'C...t....
        0x0020:  8002 2000 b855 0000 0204 0550 0103 0308  .....U.....P....
        0x0030:  0101 0402                                ....
17:34:09.685874 00:15:5d:0a:33:5e > 00:15:5d:0a:6b:31, ethertype IPv4 (0x0800), length 60: 10.10.70.163.54543 > 10.10.20.27.10051: Flags [.], ack 3681358517, win 260, length 0
        0x0000:  4500 0028 037e 4000 7f06 da80 0a0a 07a3  E..(.~@.........
        0x0010:  0a0a 021b d50f 2743 ed85 8f75 db6d 12b5  ......'C...u.m..
        0x0020:  5010 0104 298e 0000 0000 0000 0000       P...).........
17:34:09.686323 00:15:5d:0a:33:5e > 00:15:5d:0a:6b:31, ethertype IPv4 (0x0800), length 365: 10.10.70.163.54543 > 10.10.20.27.10051: Flags [P.], seq 0:311, ack 1, win 260, length 311
        0x0000:  4500 015f 037f 4000 7f06 d948 0a0a 07a3  E.._..@....H....
        0x0010:  0a0a 021b d50f 2743 ed85 8f75 db6d 12b5  ......'C...u.m..
        0x0020:  5018 0104 aa85 0000 5a42 5844 012a 0100  P.......ZBXD.*..
        0x0030:  0000 0000 007b 2272 6571 7565 7374 223a  .....{"request":
        0x0040:  2273 656e 6465 7220 6461 7461 222c 2264  "sender.data","d
        0x0050:  6174 6122 3a5b 7b22 686f 7374 223a 2253  ata":[{"host":"S
        0x0060:  5059 4453 514c 3032 222c 226b 6579 223a  PYDSQL02","key":
        0x0070:  2274 7261 7022 2c22 7661 6c75 6522 3a22  "trap","value":"
        0x0080:  506f 7765 7253 6865 6c6c 3121 2121 202d  PowerShell1!!!.-
        0x0090:  203f 3f3f 3f22 7d2c 7b22 686f 7374 223a  .????"},{"host":
        0x00a0:  2253 5059 4453 514c 3032 222c 226b 6579  "SPYDSQL02","key
        0x00b0:  223a 2274 7261 7022 2c22 7661 6c75 6522  ":"trap","value"
        0x00c0:  3a22 506f 7765 7253 6865 6c6c 3221 2121  :"PowerShell2!!!
        0x00d0:  202d 203f 3f3f 3f22 7d2c 7b22 686f 7374  .-.????"},{"host
        0x00e0:  223a 2253 5059 4453 514c 3032 222c 226b  ":"SPYDSQL02","k
        0x00f0:  6579 223a 2274 7261 7022 2c22 7661 6c75  ey":"trap","valu
        0x0100:  6522 3a22 506f 7765 7253 6865 6c6c 3321  e":"PowerShell3!
        0x0110:  2121 202d 203f 3f3f 3f22 7d2c 7b22 686f  !!.-.????"},{"ho
        0x0120:  7374 223a 2253 5059 4453 514c 3032 222c  st":"SPYDSQL02",
        0x0130:  226b 6579 223a 2274 7261 7022 2c22 7661  "key":"trap","va
        0x0140:  6c75 6522 3a22 506f 7765 7253 6865 6c6c  lue":"PowerShell
        0x0150:  3421 2121 202d 203f 3f3f 3f22 7d5d 7d    4!!!.-.????"}]}
17:34:09.692378 00:15:5d:0a:33:5e > 00:15:5d:0a:6b:31, ethertype IPv4 (0x0800), length 60: 10.10.70.163.54543 > 10.10.20.27.10051: Flags [.], ack 105, win 259, length 0
        0x0000:  4500 0028 0381 4000 7f06 da7d 0a0a 07a3  E..(..@....}....
        0x0010:  0a0a 021b d50f 2743 ed85 90ac db6d 131d  ......'C.....m..
        0x0020:  5010 0103 27f0 0000 0000 0000 0000       P...'.........
17:34:09.692413 00:15:5d:0a:33:5e > 00:15:5d:0a:6b:31, ethertype IPv4 (0x0800), length 60: 10.10.70.163.54543 > 10.10.20.27.10051: Flags [F.], seq 311, ack 105, win 259, length 0
        0x0000:  4500 0028 0383 4000 7f06 da7b 0a0a 07a3  E..(..@....{....
        0x0010:  0a0a 021b d50f 2743 ed85 90ac db6d 131d  ......'C.....m..
        0x0020:  5011 0103 27ef 0000 0000 0000 0000       P...'.........
^C
5 packets captured
5 packets received by filter
0 packets dropped by kernel
root@zabbix:/# 

Действительно «5a 42 58 44 01…», ладно,  пробуем «‘ZBXD1’ | Format-Hex» и получаем «5A 42 58 44 31…»
Хм, не вариант. Просто к ‘ZBXD’ добавляем 1 байт:

@([System.Text.Encoding]::ASCII.GetBytes('ZBXD')) + [byte]1

и вуаля!

PS> @([System.Text.Encoding]::ASCII.GetBytes('ZBXD')) + [byte]1 | Format-Hex
           00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000   5A                                               Z
           00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000   42                                               B
           00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000   58                                               X
           00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000   44                                               D
           00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000   01                                               .
PS>

2-е:

Зачем эти жуткие манипуляции с «[System.Buffer]::BlockCopy()» я совсем не понял, у нас есть массивы байт $Header, $Length и $Data и в пошике мы можем просто сложить с помощью… Внимание! Дааа! …знака «+» 🙂 То есть вместо 4-х строк ужаса у нас получается одна короткая и понятная.

$All = $Header + $Length + $Data

3-е:

Метод  _Receive мне совсем непонятен, поэтому пришлось написать с нуля

# Создаем пустой массив байт для получения ответа. Число взял на глаз, даже не считал, но вроде хватает )) Возможно для расширения функциаонала потребуется больше, но для сендера больше не нужно
[byte[]]$Buffer = New-Object System.Byte[] 1000
# Получаю количество принятых байт
[int]$ReceivedLength = $Socket.Receive($Buffer)
# отбрасываю первые 13 байт, т.к. в них заголовки и отбрасываю все, что больше чем полученное, по индексу в массиве
$Received = [System.Text.Encoding]::ASCII.GetString(@($Buffer[13 .. ($ReceivedLength - 1)]))
# конвертирую в Json
$Received | ConvertFrom-Json

Дальше создаем параметры, проверки параметров, проверки входящих данных, проверки отправки и получения и сохраняем все это в файл, который можно разлить на все сервера, но я рекомендую обновиться до PowerShell_v5 и использовать

Install-Script Send-ZabbixTrap -Force

Работает так:

Send-ZabbixTrap

Get-Help подскажет варианты использования ))

Код функции:

Function Send-ZabbixTrap {
[CmdletBinding(DefaultParameterSetName="Set0")]
param(
    [alias("z")]
    [string]$Server = '172.16.5.2',
    
    [alias("p")]
    [ValidateRange(1,65535)]
    [int]$Port = '10051',
    
    [parameter(ParameterSetName="Set1")]
    [alias("s")]
    [string]$HostName,
    
    [parameter(ParameterSetName="Set1")]
    [alias("k")]
    [string]$Key,
    
    [parameter(ParameterSetName="Set1")]
    [alias("o")]
    [string]$Value,
    
    [parameter(ParameterSetName="Set2")]$InputObject,
    
    [parameter(ParameterSetName="Set2",HelpMessage='Enter 3 string values for mapping object property to json headers. Default "host","key","value"')]
    [ValidateCount(3,3)]
    [string[]]$Header = @('host','key','value'),
    
    [parameter(ParameterSetName="Set3")]
    [string]$JsonString,
    
    [parameter(HelpMessage='On generating Json-string for preview')]
    [switch]$OnlyPreview
)

    if ( [bool]($HostName -or $Key -or $Value) ) {
            if(! [bool]($HostName -and $Key -and $Value) ) {
                Write-Error 'HostName, Key and Value must not be null';
                break
            } else {
                $Json = [pscustomobject][ordered]@{
                    'request' = 'sender data' ;
                    'data' = @([pscustomobject][ordered]@{'host' = $HostName;'key' = $Key;'value' = $Value})
                } | ConvertTo-Json -Compress
    
        }
    } elseif ($InputObject) {
        $Json = [pscustomobject][ordered]@{
                    'request' = 'sender data' ;
                    'data' = @(
                        $InputObject | Select-Object -Property @(
                            @{'Name' = 'host'; Expression = {$_.$($Header[0])}},
                            @{'Name' = 'key'; Expression = {$_.$($Header[1])}},
                            @{'Name' = 'value'; Expression = {$_.$($Header[2])}}
                        )
                    )
                } | ConvertTo-Json -Compress
    } elseif ($JsonString) {
        $Json = $JsonString | ConvertFrom-Json | ConvertTo-Json -Compress
    } else {
        Write-Error 'Input data not found';
        break
    }
    
    if(!$Json){
        Write-Error 'Can not convert InputData to Json string';
        break
    }
    
    if($OnlyPreview){
        $Json | ConvertFrom-Json | ConvertTo-Json;
        break
    }
    
    try {
        [byte[]]$Header = @([System.Text.Encoding]::ASCII.GetBytes('ZBXD')) + [byte]1
        [byte[]]$Length = @([System.BitConverter]::GetBytes($([long]$Json.Length)))
        [byte[]]$Data = @([System.Text.Encoding]::ASCII.GetBytes($Json))
        
        $All = $Header + $Length + $Data
        
    } catch {
        Write-Error 'Can not convert Json string to byte';
        break
    }
    
    try {
        $Socket = New-Object System.Net.Sockets.Socket ([System.Net.Sockets.AddressFamily]::InterNetwork, [System.Net.Sockets.SocketType]::Stream, [System.Net.Sockets.ProtocolType]::Tcp)
        $Socket.Connect($Server,$Port)
        $Socket.Send($All) | Out-Null
        [byte[]]$Buffer = New-Object System.Byte[] 1000
        [int]$ReceivedLength = $Socket.Receive($Buffer)
        $Socket.Close()
    } catch {
        Write-Error 'TCP-level Error connecting, sending or receiving';
        break
    }
    
    $Received = [System.Text.Encoding]::ASCII.GetString(@($Buffer[13 .. ($ReceivedLength - 1)]))
    
    try{
        $Received | ConvertFrom-Json
    } catch {
        Write-Warning 'It is not possible to convert the output to a Json string, maybe the server has rejected invalid data'
        $Received
    }
}
Реклама

PowerShell Send-ZabbixTrap: Один комментарий

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход /  Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход /  Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход /  Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход /  Изменить )

w

Connecting to %s