Skip to content

Commit

Permalink
new syslog server with tcp and tls support, added tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Marcel Gebhardt committed Apr 20, 2018
1 parent f5b5ead commit d161969
Show file tree
Hide file tree
Showing 3 changed files with 289 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
language: go
go_import_path: github.com/Neo23x0/simplesyslog
notifications:
email: false
slack: nextron-systems:Q1f2uRSIFH0Sf5IzT884z4YR
111 changes: 111 additions & 0 deletions server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Simple Syslog Server that
// supports UDP, TCP and TLS.
//
// Marcel Gebhardt
// April 2018

package simplesyslog

import (
"crypto/tls"
"fmt"
"log/syslog"
"net"
"os"
"time"
)

// ConnectionType defines wheather to connect via UDP or TCP (or TLS)
type ConnectionType string

const (
// ConnectionUDP connects via UDP
ConnectionUDP ConnectionType = "udp"
// ConnectionTCP connects via TCP
ConnectionTCP ConnectionType = "tcp"
// ConnectionTLS connects via TLS
ConnectionTLS ConnectionType = "tls"
)

const (
// DefaultHostname will be used if hostname could not be determined
DefaultHostname string = "unknown"
// DefaultIP will be used if ip could not be determined
DefaultIP string = ""
)

// Server holds a connection to a specified address
type Server struct {
Hostname string // Hostname of the system
IP string // IP of the system
Rfc3164 bool // rfc standard for length reduction
Rfc5424 bool // rfc standard for length reduction
conn net.Conn // connection to the syslog server
}

// NewServer initializes a new server connection.
// Examples:
// - NewServer(ConnectionUDP, "172.0.0.1:514")
// - NewServer(ConnectionTCP, ":514")
// - NewServer(ConnectionTLS, "172.0.0.1:514")
func NewServer(connectionType ConnectionType, address string) (*Server, error) {
// Validate data
if connectionType != ConnectionUDP && connectionType != ConnectionTCP && connectionType != ConnectionTLS {
return nil, fmt.Errorf("unknown connection type '%s'", connectionType)
}
var (
conn net.Conn
err error
)
// connect via udp / tcp / tls
if connectionType == ConnectionTLS {
conn, err = tls.Dial(string(ConnectionTCP), address, &tls.Config{
InsecureSkipVerify: true,
})
} else {
conn, err = net.Dial(string(connectionType), address)
}
if err != nil {
return nil, err
}
// get hostname and ip of system
hostname, err := os.Hostname()
if err != nil {
hostname = DefaultHostname
}
ip, _, err := net.SplitHostPort(conn.LocalAddr().String())
if err != nil {
ip = DefaultIP
}
// return the server
return &Server{
Hostname: hostname,
IP: ip,
conn: conn,
}, nil
}

// Send sends a syslog message with a specified priority.
// Examples:
// - Send("foo", syslog.LOG_LOCAL0|syslog.LOG_NOTICE)
// - Send("bar", syslog.LOG_DAEMON|syslog.LOG_DEBUG)
func (server *Server) Send(message string, priority syslog.Priority) error {
timestamp := time.Now().Format("Jan _2 15:04:05")
hostnameCombi := fmt.Sprintf("%s/%s", server.Hostname, server.IP)
header := fmt.Sprintf("<%d>%s %s", int(priority), timestamp, hostnameCombi)
// RFC length reduction
if server.Rfc3164 && len(message) > 1024 {
message = fmt.Sprintf("%s...", message[:1020])
}
if server.Rfc5424 && len(message) > 2048 {
message = fmt.Sprintf("%s...", message[:2044])
}
// Send message
_, err := fmt.Fprintf(server.conn, "%s %s", header, message)
return err
}

// Close closes the server connection gracefully.
func (server *Server) Close() error {
return server.conn.Close()
}
173 changes: 173 additions & 0 deletions server_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package simplesyslog

import (
"crypto/tls"
"crypto/x509"
"encoding/pem"
"io/ioutil"
"log/syslog"
"net"
"regexp"
"strconv"
"testing"
"time"
)

const host = "127.0.0.1:15140"

const tlsCRT = `-----BEGIN CERTIFICATE-----
MIICJzCCAZACCQCPGY+4vjNV0TANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJE
RTEPMA0GA1UECAwGSGVzc2VuMRIwEAYDVQQHDAlGcmFua2Z1cnQxEjAQBgNVBAoM
CUNvZGVoYXJkdDEQMA4GA1UEAwwHVGVzdGluZzAeFw0xODA0MjAwNzUwMzZaFw0x
OTA0MjEwNzUwMzZaMFgxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZIZXNzZW4xEjAQ
BgNVBAcMCUZyYW5rZnVydDESMBAGA1UECgwJQ29kZWhhcmR0MRAwDgYDVQQDDAdU
ZXN0aW5nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDcHWCPUdd4VfxorTjk
5g/97HiAdcVn2kbDEVv2aI9HdSbcC9DC209DoaX4/7+3cEf3TwE5RKu9acf8tDue
W8tAWvKH4wW7hIHiipfhFisuQeLe5NgXGqY+bs+B5+A0C/rKTrGkHu8hpXjFPY2y
rwyYMBPmIm44X53tzsNYzuQakQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAL5k/7HU
g2+6QcrV2K+2616D0ssgFrKqyG1dTy9w2+jZPQWcVNTRXGDkfdK/JlXzfEQwI/bZ
89GmxswWxoxoMi7ZMPAG2h66vMUwCFTjUGEihgX/qksnzglMTQHlgLGENfQfawy1
G1MpWJaW2ClQGr70dTTFeFJLSOANdAEqkTOC
-----END CERTIFICATE-----`

const tlsKEY = `-----BEGIN RSA PRIVATE KEY-----
MIICXgIBAAKBgQDcHWCPUdd4VfxorTjk5g/97HiAdcVn2kbDEVv2aI9HdSbcC9DC
209DoaX4/7+3cEf3TwE5RKu9acf8tDueW8tAWvKH4wW7hIHiipfhFisuQeLe5NgX
GqY+bs+B5+A0C/rKTrGkHu8hpXjFPY2yrwyYMBPmIm44X53tzsNYzuQakQIDAQAB
AoGBAI8JmCIKcRcF6YysZHh6+JFuBbCU179xHOLOeRBbSiCJhMMh+ntlwNCWTyDM
MW2nTVzsvkLU2TWxdABHryZtSFpJIq69euzRtEN3uFy5qJWGvlE6Tn+ps7XIbPsX
rppeclgV7a2nznrh+v1hfY/hgePyhuDsH0Hh5HB+L/gdb5DxAkEA81IPoy/UZWKg
wIGeGy0mWyb4rmIxJTxOQPHd+2iJaD173eY0PskWuMLHFjyVn2gqHdy88ZDe0Xh9
35SjOw5J7wJBAOeVvye1hnvOdgOHpuurDwvoTy/A+hUhzuzPKyUhNwENHk1d5L8D
w2n+onITuFhRUvJW+ZCn+8BcY2Q0FNUqY38CQQCs7WhhsQ+BkqvuxPAKHneBFtxs
iyqkbQysiXkbQXtOk0viM8ZzzNSSMRPvENXBufUczhGWmUBSnRDQgsHTqd8PAkAv
Wdbz75HHzrcikaH3nco9zQoj4XlAyODeWp2fweLVPDFt8DzNMZ/LFF1ypcWTiU1E
b7Qnd7Fp63oHCv8XdstRAkEA5Lf2nA2rEi5WxvSIea5KUzQp6Ut1aCLjHpdU5Pk7
4IqCgyaC9pPvCkL6rEOthAfh9nnPJp41zMk7jHz5zmRe6g==
-----END RSA PRIVATE KEY-----`

var tlsConfig *tls.Config

var messageRegex = regexp.MustCompile(`<133>[A-Z][a-z]{2} (([0-9]{2})|( [0-9])) [0-9]{2}:[0-9]{2}:[0-9]{2} testing\/127\.0\.0\.1 foo bar baz`)

func TestNewServer(t *testing.T) {
testNewServerTCP(t, false)
testNewServerTCP(t, true)
testNewServerUDP(t)
}

func testNewServerUDP(t *testing.T) {
t.Logf("testing udp")
serverAddr, err := net.ResolveUDPAddr("udp", host)
if err != nil {
t.Fatalf("could not resolve udp addr: %s", err)
}
conn, err := net.ListenUDP("udp", serverAddr)
if err != nil {
t.Fatalf("could not listen udp: %s", err)
}
if err := conn.SetReadDeadline(time.Now().Add(time.Second * 5)); err != nil {
t.Fatalf("could not set read deadline: %s", err)
}
defer conn.Close()
go func() {
/*
* Send the message 'foo bar baz' to the syslog server
*/
server, err := NewServer(ConnectionUDP, host)
if err != nil {
t.Fatalf("could not initialize server: %s", err)
}
server.Hostname = "testing" // overwrite hostname for testing
defer server.Close()
if err := server.Send("foo bar baz", syslog.LOG_LOCAL0|syslog.LOG_NOTICE); err != nil {
t.Fatalf("could not send message: %s", err)
}
}()
buf := make([]byte, 1024)
n, _, err := conn.ReadFrom(buf)
if err != nil {
t.Fatalf("could not read udp: %s", err)
}
b := buf[:n]
if !messageRegex.MatchString(string(b)) {
t.Fatalf("wrong message: %s", string(b))
} else {
t.Logf("correct message: '%s'", string(b))
}
}

func testNewServerTCP(t *testing.T, useTLS bool) {
t.Logf("testing tcp with tls '%s'", strconv.FormatBool(useTLS))
var (
listener net.Listener
err error
)
if useTLS {
listener, err = tls.Listen("tcp", host, tlsConfig)
} else {
listener, err = net.Listen("tcp", host)
}
if err != nil {
t.Fatalf("could not listen: %s", err)
}
defer listener.Close()
go func() {
/*
* Send the message 'foo bar baz' to the syslog server
*/
connectionType := ConnectionTCP
if useTLS {
connectionType = ConnectionTLS
}
server, err := NewServer(connectionType, host)
if err != nil {
t.Fatalf("could not initialize server: %s", err)
}
server.Hostname = "testing" // overwrite hostname for testing
defer server.Close()
if err := server.Send("foo bar baz", syslog.LOG_LOCAL0|syslog.LOG_NOTICE); err != nil {
t.Fatalf("could not send message: %s", err)
}
}()
for {
conn, err := listener.Accept()
if err != nil {
t.Fatalf("could not accept connection: %s", err)
}
defer conn.Close()
b, err := ioutil.ReadAll(conn)
if err != nil {
t.Fatalf("could not read all: %s", err)
}
if !messageRegex.MatchString(string(b)) {
t.Fatalf("wrong message: %s", string(b))
} else {
t.Logf("correct message: '%s'", string(b))
}
return
}
}

func init() {
pemCert, _ := pem.Decode([]byte(tlsCRT))
if pemCert == nil {
panic("no test tls certificate")
}
pemKey, _ := pem.Decode([]byte(tlsKEY))
if pemKey == nil {
panic("no test tls key")
}
privateKey, err := x509.ParsePKCS1PrivateKey(pemKey.Bytes)
if err != nil {
panic("invalid test tls key")
}
cert := &tls.Certificate{
Certificate: [][]byte{pemCert.Bytes},
PrivateKey: privateKey,
}
tlsConfig = &tls.Config{
Certificates: []tls.Certificate{*cert},
MinVersion: tls.VersionTLS11,
}
}

0 comments on commit d161969

Please sign in to comment.