Snippets

Dmitry Popov ZabbixSender

Created by Dmitry Popov last modified
namespace Utils
{
    using System;
    using System.Collections.Generic;
    using System.Net.Sockets;
    using System.Text;
    using System.Text.Json;
    using System.Text.Json.Serialization;
    using System.Threading.Tasks;

#pragma warning disable SA1629 // Documentation text should end with a period
#pragma warning disable CA1303 // Do not pass literals as localized parameters

    /// <summary>
    /// Sends data to Zabbix server (trapped items).
    /// </summary>
    /// <example>
    /// var sender = new ZabbixSender("zabbix.example.com");
    /// var response = await sender
    ///       .Add("MyHost", "testA", "1")
    ///       .Add("MyHost", "testB", "2")
    ///       .SendAsync();
    /// </example>
    /// <remarks>
    /// Licence: MIT
    /// Source: https://bitbucket.org/just_dmitry/workspace/snippets/KrkyG4/zabbixsender
    /// Version: 2020-04-29
    /// </remarks>
    public class ZabbixSender
    {
        private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions
        {
            IgnoreNullValues = true,
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
            PropertyNameCaseInsensitive = true,
            WriteIndented = false,
            Converters =
            {
                new DateTimeOffsetConverter(),
            },
        };

        private readonly string server;
        private readonly int port;
        private readonly object syncRoot = new object();
        private ZabbixRequest? request;

        /// <summary>
        /// Default constructor.
        /// </summary>
        /// <param name="server">Server hostname or IP address.</param>
        /// <param name="port">Port (default 10051).</param>
        public ZabbixSender(string server, int port = 10051)
        {
            if (string.IsNullOrEmpty(server))
            {
                throw new ArgumentNullException(nameof(server));
            }

            this.server = server;
            this.port = port;
        }

        public ZabbixSender Add(string host, string key, string value, DateTimeOffset? clock = null)
        {
            if (string.IsNullOrEmpty(host))
            {
                throw new ArgumentNullException(nameof(host));
            }

            if (string.IsNullOrEmpty(key))
            {
                throw new ArgumentNullException(nameof(key));
            }

            if (string.IsNullOrEmpty(value))
            {
                throw new ArgumentNullException(nameof(value));
            }

            lock (syncRoot)
            {
                if (request == null)
                {
                    request = new ZabbixRequest();
                }

                request.Data.Add(new Item { Host = host, Key = key, Value = value, Clock = clock ?? DateTimeOffset.UtcNow });
            }

            return this;
        }

        public async Task<string> SendAsync()
        {
            byte[] requestBody;

            lock (syncRoot)
            {
                if (request == null || request.Data.Count == 0)
                {
                    throw new InvalidOperationException("Nothing to send (call Add() first)");
                }

                request.Clock = DateTimeOffset.UtcNow;
                requestBody = JsonSerializer.SerializeToUtf8Bytes(request, JsonOptions);

                // теперь можно заполнять заново
                request = null;
            }

            var requestHeader = new byte[]
            {
                Convert.ToByte('Z'), Convert.ToByte('B'), Convert.ToByte('X'), Convert.ToByte('D'), 1,
                (byte)(requestBody.Length & 0xFF),
                (byte)((requestBody.Length >> 8) & 0xFF),
                (byte)((requestBody.Length >> 16) & 0xFF),
                (byte)((requestBody.Length >> 24) & 0xFF),
                0, 0, 0, 0,
            };

            using var tcpClient = new TcpClient(server, port);
            using var stream = tcpClient.GetStream();

            await stream.WriteAsync(requestHeader, 0, requestHeader.Length).ConfigureAwait(false);
            await stream.WriteAsync(requestBody, 0, requestBody.Length).ConfigureAwait(false);
            await stream.FlushAsync().ConfigureAwait(false);

            var responseHeaderBytes = new byte[13];
            await stream.ReadAsync(responseHeaderBytes, 0, responseHeaderBytes.Length).ConfigureAwait(false);

            var responseHeader = Encoding.ASCII.GetString(responseHeaderBytes.AsSpan(0..5));
            if (!string.Equals("ZBXD\x01", responseHeader, StringComparison.Ordinal))
            {
                throw new ApplicationException($"Invalid response header prefix: {responseHeader}");
            }

            var responseDataLength = BitConverter.ToInt32(responseHeaderBytes, 5);
            if (responseDataLength <= 0 || responseDataLength > 1000)
            {
                throw new ApplicationException($"Invalid response data length: {responseDataLength}");
            }

            var responseBytes = new byte[responseDataLength];
            await stream.ReadAsync(responseBytes, 0, responseBytes.Length).ConfigureAwait(false);

            var resp = JsonSerializer.Deserialize<ZabbixResponse>(responseBytes, JsonOptions);
            if (!string.Equals("success", resp.Response, StringComparison.Ordinal))
            {
                throw new ApplicationException($"Invalid response status: '{resp.Response}'");
            }

            if (string.IsNullOrEmpty(resp.Info))
            {
                throw new ApplicationException($"Invalid response info: '{resp.Info}'");
            }

            return resp.Info;
        }

        private class ZabbixRequest
        {
            public string Request { get; set; } = "sender data";

            public List<Item> Data { get; set; } = new List<Item>();

            public DateTimeOffset? Clock { get; set; }
        }

        private class Item
        {
            public string Host { get; set; } = string.Empty;

            public string Key { get; set; } = string.Empty;

            public string Value { get; set; } = string.Empty;

            public DateTimeOffset? Clock { get; set; }
        }

        private class ZabbixResponse
        {
            public string Response { get; set; } = string.Empty;

            public string Info { get; set; } = string.Empty;
        }

        private class DateTimeOffsetConverter : JsonConverter<DateTimeOffset>
        {
            public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
            {
                return DateTimeOffset.FromUnixTimeSeconds(reader.GetInt64());
            }

            public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options)
            {
                writer.WriteNumberValue(value.ToUnixTimeSeconds());
            }
        }
    }
}

Comments (0)

HTTPS SSH

You can clone a snippet to your computer for local editing. Learn more.