Skip to content

Commit d161969

Browse files
author
Marcel Gebhardt
committed
new syslog server with tcp and tls support, added tests
1 parent f5b5ead commit d161969

File tree

3 files changed

+289
-0
lines changed

3 files changed

+289
-0
lines changed

.travis.yml

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
language: go
2+
go_import_path: github.com/Neo23x0/simplesyslog
3+
notifications:
4+
email: false
5+
slack: nextron-systems:Q1f2uRSIFH0Sf5IzT884z4YR

server.go

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Simple Syslog Server that
2+
// supports UDP, TCP and TLS.
3+
//
4+
// Marcel Gebhardt
5+
// April 2018
6+
7+
package simplesyslog
8+
9+
import (
10+
"crypto/tls"
11+
"fmt"
12+
"log/syslog"
13+
"net"
14+
"os"
15+
"time"
16+
)
17+
18+
// ConnectionType defines wheather to connect via UDP or TCP (or TLS)
19+
type ConnectionType string
20+
21+
const (
22+
// ConnectionUDP connects via UDP
23+
ConnectionUDP ConnectionType = "udp"
24+
// ConnectionTCP connects via TCP
25+
ConnectionTCP ConnectionType = "tcp"
26+
// ConnectionTLS connects via TLS
27+
ConnectionTLS ConnectionType = "tls"
28+
)
29+
30+
const (
31+
// DefaultHostname will be used if hostname could not be determined
32+
DefaultHostname string = "unknown"
33+
// DefaultIP will be used if ip could not be determined
34+
DefaultIP string = ""
35+
)
36+
37+
// Server holds a connection to a specified address
38+
type Server struct {
39+
Hostname string // Hostname of the system
40+
IP string // IP of the system
41+
Rfc3164 bool // rfc standard for length reduction
42+
Rfc5424 bool // rfc standard for length reduction
43+
conn net.Conn // connection to the syslog server
44+
}
45+
46+
// NewServer initializes a new server connection.
47+
// Examples:
48+
// - NewServer(ConnectionUDP, "172.0.0.1:514")
49+
// - NewServer(ConnectionTCP, ":514")
50+
// - NewServer(ConnectionTLS, "172.0.0.1:514")
51+
func NewServer(connectionType ConnectionType, address string) (*Server, error) {
52+
// Validate data
53+
if connectionType != ConnectionUDP && connectionType != ConnectionTCP && connectionType != ConnectionTLS {
54+
return nil, fmt.Errorf("unknown connection type '%s'", connectionType)
55+
}
56+
var (
57+
conn net.Conn
58+
err error
59+
)
60+
// connect via udp / tcp / tls
61+
if connectionType == ConnectionTLS {
62+
conn, err = tls.Dial(string(ConnectionTCP), address, &tls.Config{
63+
InsecureSkipVerify: true,
64+
})
65+
} else {
66+
conn, err = net.Dial(string(connectionType), address)
67+
}
68+
if err != nil {
69+
return nil, err
70+
}
71+
// get hostname and ip of system
72+
hostname, err := os.Hostname()
73+
if err != nil {
74+
hostname = DefaultHostname
75+
}
76+
ip, _, err := net.SplitHostPort(conn.LocalAddr().String())
77+
if err != nil {
78+
ip = DefaultIP
79+
}
80+
// return the server
81+
return &Server{
82+
Hostname: hostname,
83+
IP: ip,
84+
conn: conn,
85+
}, nil
86+
}
87+
88+
// Send sends a syslog message with a specified priority.
89+
// Examples:
90+
// - Send("foo", syslog.LOG_LOCAL0|syslog.LOG_NOTICE)
91+
// - Send("bar", syslog.LOG_DAEMON|syslog.LOG_DEBUG)
92+
func (server *Server) Send(message string, priority syslog.Priority) error {
93+
timestamp := time.Now().Format("Jan _2 15:04:05")
94+
hostnameCombi := fmt.Sprintf("%s/%s", server.Hostname, server.IP)
95+
header := fmt.Sprintf("<%d>%s %s", int(priority), timestamp, hostnameCombi)
96+
// RFC length reduction
97+
if server.Rfc3164 && len(message) > 1024 {
98+
message = fmt.Sprintf("%s...", message[:1020])
99+
}
100+
if server.Rfc5424 && len(message) > 2048 {
101+
message = fmt.Sprintf("%s...", message[:2044])
102+
}
103+
// Send message
104+
_, err := fmt.Fprintf(server.conn, "%s %s", header, message)
105+
return err
106+
}
107+
108+
// Close closes the server connection gracefully.
109+
func (server *Server) Close() error {
110+
return server.conn.Close()
111+
}

server_test.go

+173
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
package simplesyslog
2+
3+
import (
4+
"crypto/tls"
5+
"crypto/x509"
6+
"encoding/pem"
7+
"io/ioutil"
8+
"log/syslog"
9+
"net"
10+
"regexp"
11+
"strconv"
12+
"testing"
13+
"time"
14+
)
15+
16+
const host = "127.0.0.1:15140"
17+
18+
const tlsCRT = `-----BEGIN CERTIFICATE-----
19+
MIICJzCCAZACCQCPGY+4vjNV0TANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJE
20+
RTEPMA0GA1UECAwGSGVzc2VuMRIwEAYDVQQHDAlGcmFua2Z1cnQxEjAQBgNVBAoM
21+
CUNvZGVoYXJkdDEQMA4GA1UEAwwHVGVzdGluZzAeFw0xODA0MjAwNzUwMzZaFw0x
22+
OTA0MjEwNzUwMzZaMFgxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZIZXNzZW4xEjAQ
23+
BgNVBAcMCUZyYW5rZnVydDESMBAGA1UECgwJQ29kZWhhcmR0MRAwDgYDVQQDDAdU
24+
ZXN0aW5nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDcHWCPUdd4VfxorTjk
25+
5g/97HiAdcVn2kbDEVv2aI9HdSbcC9DC209DoaX4/7+3cEf3TwE5RKu9acf8tDue
26+
W8tAWvKH4wW7hIHiipfhFisuQeLe5NgXGqY+bs+B5+A0C/rKTrGkHu8hpXjFPY2y
27+
rwyYMBPmIm44X53tzsNYzuQakQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAL5k/7HU
28+
g2+6QcrV2K+2616D0ssgFrKqyG1dTy9w2+jZPQWcVNTRXGDkfdK/JlXzfEQwI/bZ
29+
89GmxswWxoxoMi7ZMPAG2h66vMUwCFTjUGEihgX/qksnzglMTQHlgLGENfQfawy1
30+
G1MpWJaW2ClQGr70dTTFeFJLSOANdAEqkTOC
31+
-----END CERTIFICATE-----`
32+
33+
const tlsKEY = `-----BEGIN RSA PRIVATE KEY-----
34+
MIICXgIBAAKBgQDcHWCPUdd4VfxorTjk5g/97HiAdcVn2kbDEVv2aI9HdSbcC9DC
35+
209DoaX4/7+3cEf3TwE5RKu9acf8tDueW8tAWvKH4wW7hIHiipfhFisuQeLe5NgX
36+
GqY+bs+B5+A0C/rKTrGkHu8hpXjFPY2yrwyYMBPmIm44X53tzsNYzuQakQIDAQAB
37+
AoGBAI8JmCIKcRcF6YysZHh6+JFuBbCU179xHOLOeRBbSiCJhMMh+ntlwNCWTyDM
38+
MW2nTVzsvkLU2TWxdABHryZtSFpJIq69euzRtEN3uFy5qJWGvlE6Tn+ps7XIbPsX
39+
rppeclgV7a2nznrh+v1hfY/hgePyhuDsH0Hh5HB+L/gdb5DxAkEA81IPoy/UZWKg
40+
wIGeGy0mWyb4rmIxJTxOQPHd+2iJaD173eY0PskWuMLHFjyVn2gqHdy88ZDe0Xh9
41+
35SjOw5J7wJBAOeVvye1hnvOdgOHpuurDwvoTy/A+hUhzuzPKyUhNwENHk1d5L8D
42+
w2n+onITuFhRUvJW+ZCn+8BcY2Q0FNUqY38CQQCs7WhhsQ+BkqvuxPAKHneBFtxs
43+
iyqkbQysiXkbQXtOk0viM8ZzzNSSMRPvENXBufUczhGWmUBSnRDQgsHTqd8PAkAv
44+
Wdbz75HHzrcikaH3nco9zQoj4XlAyODeWp2fweLVPDFt8DzNMZ/LFF1ypcWTiU1E
45+
b7Qnd7Fp63oHCv8XdstRAkEA5Lf2nA2rEi5WxvSIea5KUzQp6Ut1aCLjHpdU5Pk7
46+
4IqCgyaC9pPvCkL6rEOthAfh9nnPJp41zMk7jHz5zmRe6g==
47+
-----END RSA PRIVATE KEY-----`
48+
49+
var tlsConfig *tls.Config
50+
51+
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`)
52+
53+
func TestNewServer(t *testing.T) {
54+
testNewServerTCP(t, false)
55+
testNewServerTCP(t, true)
56+
testNewServerUDP(t)
57+
}
58+
59+
func testNewServerUDP(t *testing.T) {
60+
t.Logf("testing udp")
61+
serverAddr, err := net.ResolveUDPAddr("udp", host)
62+
if err != nil {
63+
t.Fatalf("could not resolve udp addr: %s", err)
64+
}
65+
conn, err := net.ListenUDP("udp", serverAddr)
66+
if err != nil {
67+
t.Fatalf("could not listen udp: %s", err)
68+
}
69+
if err := conn.SetReadDeadline(time.Now().Add(time.Second * 5)); err != nil {
70+
t.Fatalf("could not set read deadline: %s", err)
71+
}
72+
defer conn.Close()
73+
go func() {
74+
/*
75+
* Send the message 'foo bar baz' to the syslog server
76+
*/
77+
server, err := NewServer(ConnectionUDP, host)
78+
if err != nil {
79+
t.Fatalf("could not initialize server: %s", err)
80+
}
81+
server.Hostname = "testing" // overwrite hostname for testing
82+
defer server.Close()
83+
if err := server.Send("foo bar baz", syslog.LOG_LOCAL0|syslog.LOG_NOTICE); err != nil {
84+
t.Fatalf("could not send message: %s", err)
85+
}
86+
}()
87+
buf := make([]byte, 1024)
88+
n, _, err := conn.ReadFrom(buf)
89+
if err != nil {
90+
t.Fatalf("could not read udp: %s", err)
91+
}
92+
b := buf[:n]
93+
if !messageRegex.MatchString(string(b)) {
94+
t.Fatalf("wrong message: %s", string(b))
95+
} else {
96+
t.Logf("correct message: '%s'", string(b))
97+
}
98+
}
99+
100+
func testNewServerTCP(t *testing.T, useTLS bool) {
101+
t.Logf("testing tcp with tls '%s'", strconv.FormatBool(useTLS))
102+
var (
103+
listener net.Listener
104+
err error
105+
)
106+
if useTLS {
107+
listener, err = tls.Listen("tcp", host, tlsConfig)
108+
} else {
109+
listener, err = net.Listen("tcp", host)
110+
}
111+
if err != nil {
112+
t.Fatalf("could not listen: %s", err)
113+
}
114+
defer listener.Close()
115+
go func() {
116+
/*
117+
* Send the message 'foo bar baz' to the syslog server
118+
*/
119+
connectionType := ConnectionTCP
120+
if useTLS {
121+
connectionType = ConnectionTLS
122+
}
123+
server, err := NewServer(connectionType, host)
124+
if err != nil {
125+
t.Fatalf("could not initialize server: %s", err)
126+
}
127+
server.Hostname = "testing" // overwrite hostname for testing
128+
defer server.Close()
129+
if err := server.Send("foo bar baz", syslog.LOG_LOCAL0|syslog.LOG_NOTICE); err != nil {
130+
t.Fatalf("could not send message: %s", err)
131+
}
132+
}()
133+
for {
134+
conn, err := listener.Accept()
135+
if err != nil {
136+
t.Fatalf("could not accept connection: %s", err)
137+
}
138+
defer conn.Close()
139+
b, err := ioutil.ReadAll(conn)
140+
if err != nil {
141+
t.Fatalf("could not read all: %s", err)
142+
}
143+
if !messageRegex.MatchString(string(b)) {
144+
t.Fatalf("wrong message: %s", string(b))
145+
} else {
146+
t.Logf("correct message: '%s'", string(b))
147+
}
148+
return
149+
}
150+
}
151+
152+
func init() {
153+
pemCert, _ := pem.Decode([]byte(tlsCRT))
154+
if pemCert == nil {
155+
panic("no test tls certificate")
156+
}
157+
pemKey, _ := pem.Decode([]byte(tlsKEY))
158+
if pemKey == nil {
159+
panic("no test tls key")
160+
}
161+
privateKey, err := x509.ParsePKCS1PrivateKey(pemKey.Bytes)
162+
if err != nil {
163+
panic("invalid test tls key")
164+
}
165+
cert := &tls.Certificate{
166+
Certificate: [][]byte{pemCert.Bytes},
167+
PrivateKey: privateKey,
168+
}
169+
tlsConfig = &tls.Config{
170+
Certificates: []tls.Certificate{*cert},
171+
MinVersion: tls.VersionTLS11,
172+
}
173+
}

0 commit comments

Comments
 (0)