Compare commits

..

11 Commits

Author SHA1 Message Date
smittythekid 556d263e37 port geändert 2026-04-13 17:19:40 +02:00
smittythekid 66f06b1711 vorgegebene Klassen verwendet für Nachrichten-Formatierung 2026-04-08 22:13:18 +02:00
smittythekid 4534883782 Discovery port 8888 integriert; doppelte Datei aus tests entfernt 2026-04-08 21:29:45 +02:00
smittythekid 4c5842aa8f Längenprüfung für Nachricht integriert 2026-04-08 20:41:21 +02:00
smittythekid 5099a7b4d6 CLient IP + Port is now read and shown 2026-04-08 18:49:56 +02:00
smittythekid 5e81f4f979 messages are now read 2026-04-08 18:42:47 +02:00
smittythekid cb15d6ac69 txt für doku der konsolenbefehle ergänzt 2026-04-08 18:29:25 +02:00
smittythekid c82a06e58d hello world entfernt; erst Konstrukt für Server in SyslogServer erstellt + gestest auf Port 5514 2026-04-08 18:24:57 +02:00
smittythekid 42cf4baba4 Server Syslog introduced 2026-04-07 21:20:26 +02:00
smittythekid adb2972d81 Ordnerstruktur + .gitignore angepasst 2026-04-07 21:17:47 +02:00
smittythekid 0a031cd74b branch published 2026-04-07 19:45:15 +02:00
9 changed files with 765 additions and 33 deletions

3
.gitignore vendored
View File

@ -24,3 +24,6 @@
hs_err_pid* hs_err_pid*
replay_pid* replay_pid*
# IDE files
justfile
pom.xml

View File

@ -1,33 +0,0 @@
In dieser Aufgabe soll ein Logging-System entwickelt werden, das das Syslog-Protokoll (RFC 5424) unterstützt.
Dabei sollen abweichend von der Spezifikation in RFC 5424 die folgenden Vereinfachungen gelten:
- Als Transportprotokoll soll ausschließlich UDP verwendet werden.
- TLS (Transport Layer Security) braucht nicht betrachtet werden.
- Der Syslog-Server braucht die empfangenen Daten nur nachrichtenweise auf der Konsole (Standard-Ausgabe) ausgeben.
Dabei sollen Informationen über den sendenden Client (insb. IP-Adresse) mitausgegeben werden.
- Strukturierte Daten brauchen nicht berücksichtigt werden.
Berücksichtigen Sie unbedingt die maximale Nachrichtenlänge und prüfen Sie, dass Nachrichten, die von Clients empfangen werden auch kurz genug sind.
Abweichend vom Syslog-Protokoll soll der Server folgende zusätzliche Eigenschaften haben:
- Die IP-Adresse des Logging-Dienstes muss vom Client durch eine automatische Service Discovery ermittelt werden.
Clients sollen die IP-Adresse des Servers also durch Broadcast-Nachrichten selbstständig ermitteln können.
Es ist möglich, diese Anforderung unberücksichtigt zu lassen und die IP-Adresse des Servers fest zu konfigurieren. Es gibt dann aber bis zu 8 Punkte Abzug.
- Der Server soll zwei UDP-Sockets verwenden: einen auf dem SYSLOG-Standard-Port und den anderen auf dem Port 8888 für die Auto-Discovery.
- (Broadcast-) Nachrichten an 8888 sollen ähnlich wie bei DHCP beantwortet werden.
Da der Absender aber bereits eine IP-Adresse hat, kann hier direkt eine UDP-Nachricht zurückgeschickt werden.
Die zurückgeschickte Nachricht kann leer sein, der Client kann daraus trotzdem die Adresse des SYSLOG-Servers (seine IP-Adresse) auslesen
und dann Log-Nachrichten an den SYSLOG-Port auf dem Host mit der so gefundenen IP-Adresse senden.
Entwickeln Sie den Server in Java. Ein Client (in einer beliebigen Programmiersprache) ermöglicht Ihnen das Testen, ist aber nicht abzugeben.
Geben Sie den Quellcode des Servers in Form eines Maven- oder Eclipse-Projekts als ZIP/TAR/TGZ-Archiv verpackt ab.
Client und Server sollen sich in demselben Sub-Netz befinden und über IP miteinander kommunizieren.
Zur Kommunikation dürfen nur Datagramme (UDP) verwendet werden.
Auf dem Ziel-System wird es nur genau ein Netzwerk geben (nicht Ethernet und Wifi mit getrennten Sub-Netzen).
Sie können daher Broadcast-Nachrichten an 255.255.255.255 schicken.
vs.pu01.tgz

13
consoleInput.txt 100644
View File

@ -0,0 +1,13 @@
Dokumentation Befehle für Konsole
mvn exec:java -Dexec.mainClass="vs.SyslogServer"
> startet den SyslogServer
> Strg + C um Server im Terminal aus Endlosschleife zu beenden
echo "test" | nc -u -w1 127.0.0.1 5514
> Test ob Server auf dem Port 5514 aktiv zuhört und Nachricht korrekt ankommt
> w1 damit nach einer Sekunde beendet wird, netcat (nc) wartet bei UDP manchmal auf eine Antwort
python3 -c "print('A'*600)" | nc -u -w1 127.0.0.1 5514
> Test Maximallänge überschritten

View File

@ -0,0 +1,87 @@
package vs;
/**
* helper class for RFC 5424 (https://datatracker.ietf.org/doc/html/rfc5424)
* compliant log messages as immutable Java objects - representation of a subset
* of printable strings of specific length
*
* @author Sandro Leuchter
*
*/
public abstract class AsciiChars {
private final String value;
public String value() {
return this.value;
}
protected AsciiChars(int length, String value) {
if (value != null) {
if (value.length() > length) {
throw new IllegalArgumentException(
"Stringlänge = " + value.length() + " > " + length
);
}
for (int c : value.getBytes()) {
if (c < 33 || c > 126) {
throw new IllegalArgumentException(
"Stringinhalt nicht printable US-ASCII ohne Space"
);
}
}
}
this.value = value;
}
@Override
public String toString() {
if (value() == null || value().length() == 0) {
return "-";
} else {
return value();
}
}
public static final class L004 extends AsciiChars {
public L004(String value) {
super(4, value);
}
}
public static final class L012 extends AsciiChars {
public L012(String value) {
super(12, value);
}
}
public static final class L032 extends AsciiChars {
public L032(String value) {
super(32, value);
}
}
public static final class L048 extends AsciiChars {
public L048(String value) {
super(48, value);
}
}
public static final class L128 extends AsciiChars {
public L128(String value) {
super(128, value);
}
}
public static final class L255 extends AsciiChars {
public L255(String value) {
super(255, value);
}
}
}

View File

@ -0,0 +1,180 @@
package vs;
import java.util.ArrayList;
import java.util.List;
/**
* helper class for RFC 5424 (https://datatracker.ietf.org/doc/html/rfc5424)
* compliant log messages as immutable Java objects - structured data (set of
* key/value-pairs) with some predefined sets according to the standard
*
* @author Sandro Leuchter
*
*/
public class StructuredData {
public static class Element {
private final String name;
private List<Param> parameters;
public static Element newTimeQuality(
boolean tzKnown,
boolean isSynced
) {
return newTimeQuality(tzKnown, isSynced, null);
}
public static Element newTimeQuality(
boolean tzKnown,
boolean isSynced,
Integer syncAccuracy
) {
var e = new Element("timeQuality");
e.add(new Param("tzKnown", (tzKnown) ? "1" : "0"));
e.add(new Param("isSynced", (isSynced) ? "1" : "0"));
if (syncAccuracy != null && !isSynced) {
e.add(new Param("syncAccuracy", String.valueOf(syncAccuracy)));
}
return e;
}
public static Element newOrigin(
String enterpriseId,
String software,
String swVersion
) {
return newOrigin(
new String[] {},
enterpriseId,
software,
swVersion
);
}
public static Element newOrigin(
String ip,
String enterpriseId,
String software,
String swVersion
) {
return newOrigin(
new String[] { ip },
enterpriseId,
software,
swVersion
);
}
public static Element newOrigin(
String[] ip,
String enterpriseId,
String software,
String swVersion
) {
var e = new Element("origin");
for (var p : ip) {
e = e.add(new Param("ip", p));
}
if (enterpriseId != null && !enterpriseId.equals("")) {
e = e.add(new Param("enterpriseId", enterpriseId));
}
if (software != null && !software.equals("")) {
e = e.add(new Param("software", software));
}
if (swVersion != null && !swVersion.equals("")) {
e = e.add(new Param("swVersion", swVersion));
}
return e;
}
public static Element newMeta(
Integer sequenceId,
Integer sysUpTime,
String language
) {
var e = new Element("meta");
if (sequenceId != null && sequenceId > 0) {
e = e.add(
new Param(
"sequenceId",
String.valueOf(sequenceId % 2147483647)
)
);
}
if (sysUpTime != null && sysUpTime >= 0) {
e = e.add(new Param("sysUpTime", String.valueOf(sysUpTime)));
}
if (language != null && !language.equals("")) {
e = e.add(new Param("language", language));
}
return e;
}
public Element(String name) {
this.name = name;
this.parameters = new ArrayList<>();
}
public Element add(Param parameter) {
var e = new Element(this.name);
e.parameters = this.parameters;
e.parameters.add(parameter);
return e;
}
@Override
public String toString() {
var str = "[" + this.name;
for (var p : this.parameters) {
str = str + " " + p.toString();
}
return str + "]";
}
}
public static class Param {
private final String name;
// name: printable US-ASCII string ^[@=\]\"\s]+
// "@" + private enterpise number "@\d+(\.\d+)*"
private final String value;
public Param(String name, String value) {
this.name = name; // 7-Bit ASCII
this.value = value; // UTF-8
}
@Override
public String toString() {
return this.name + "=\"" + this.value + "\"";
}
}
private List<Element> params;
public StructuredData() {
this.params = new ArrayList<>();
}
public StructuredData(List<Element> params) {
this.params = params;
}
public String toString() {
if (this.params.size() == 0) {
return "-";
}
var str = "";
for (var p : this.params) {
str = str + p.toString();
}
return str;
}
public StructuredData add(Element e) {
var p = this.params;
p.add(e);
return new StructuredData(p);
}
}

View File

@ -0,0 +1,213 @@
package vs;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* RFC 5424 (https://datatracker.ietf.org/doc/html/rfc5424) compliant log
* messages as immutable Java objects
*
* @author Sandro Leuchter
*
*/
public class SyslogMessage implements Serializable {
private static final long serialVersionUID = -5895573029109990861L;
private final Facility fac;
private final Severity sev;
private final AsciiChars.L255 host;
private final AsciiChars.L048 appName;
private final AsciiChars.L128 procId;
private final AsciiChars.L032 msgId;
private final StructuredData data;
private final Message message;
public SyslogMessage(
Facility fac,
Severity sev,
AsciiChars.L255 host,
AsciiChars.L048 appName,
AsciiChars.L128 procId,
AsciiChars.L032 msgId,
StructuredData data,
Message message
) {
this.fac = fac;
this.sev = sev;
this.host = host;
this.appName = appName;
this.procId = procId;
this.msgId = msgId;
this.data = data;
this.message = message;
}
public Facility fac() {
return this.fac;
}
public Severity sev() {
return sev;
}
public AsciiChars.L255 host() {
return host;
}
public AsciiChars.L048 appName() {
return appName;
}
public AsciiChars.L128 procId() {
return procId;
}
public AsciiChars.L032 msgId() {
return msgId;
}
public StructuredData data() {
return data;
}
public Message message() {
return message;
}
public static int version() {
return VERSION;
}
public static enum Facility {
KERNEL,
USER,
MAIL_SYSTEM,
SYS_DAEMON,
SECURITY1,
INTERNAL,
PRINTER,
NEWS,
UUCP,
CLOCK1,
SECURITY2,
FTP,
NTP,
AUDIT,
ALERT,
CLOCK2,
LOCAL0,
LOCAL1,
LOCAL2,
LOCAL3,
LOCAL4,
LOCAL5,
LOCAL6,
LOCAL7,
}
public static enum Severity {
EMERGENCY,
ALERT,
CRITICAL,
ERROR,
WARNING,
NOTICE,
INFORMATIONAL,
DEBUG,
}
public static interface Message {
public Object message();
public int length();
}
public static class BinaryMessage implements Message {
private Byte[] message;
public BinaryMessage(Byte[] message) {
this.message = message;
}
@Override
public String toString() {
return message.toString();
}
@Override
public Object message() {
return this.message;
}
@Override
public int length() {
return this.message.length;
}
}
public static class TextMessage implements Message {
private String message; // UTF8
public TextMessage(String message) {
this.message = message;
}
@Override
public String toString() {
return "\u00EF\u00BB\u00BF" + message.toString();
}
@Override
public Object message() {
return this.message;
}
@Override
public int length() {
return this.message.length();
}
}
static final int VERSION = 1; // RFC 5424, Mar 2009
@Override
public String toString() {
var prival = String.valueOf(fac().ordinal() * 8 + sev().ordinal());
var d = "";
if (data() != null) {
d = " " + data();
}
var m = "";
if (
message() != null &&
message().message() != null &&
message().length() > 0
) {
m = " " + message();
}
var timestamp = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX").format(
new Date()
);
return (
"<" +
prival +
">" +
VERSION +
" " +
timestamp +
" " +
host().toString() +
" " +
appName().toString() +
" " +
procId().toString() +
" " +
msgId().toString() +
d +
m
);
}
}

View File

@ -0,0 +1,120 @@
package vs;
import java.net.DatagramSocket;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
import vs.SyslogMessage;
import vs.AsciiChars;
public class SyslogServer {
public static void main(String[] args) {
int port = 514; // Default syslog port: 514, but using 5514 to avoid permission issues
SyslogServer server = new SyslogServer();
server.start(port);
}
private void runDiscoveryListener() {
try {
DatagramSocket discoverySocket = new DatagramSocket(8888); // Port for discovery
byte[] bufferDiscovery = new byte[256];
System.out.println("Discovery Listener started on port 8888");
while (true) {
DatagramPacket discoveryRequest = new DatagramPacket(bufferDiscovery, bufferDiscovery.length);
discoverySocket.receive(discoveryRequest);
InetAddress clientAddress = discoveryRequest.getAddress();
int clientPort = discoveryRequest.getPort();
System.out.println(
"Discovery request received from: " + clientAddress.getHostAddress() + ":" + clientPort);
// Send a response back to the client
byte[] responseData = "SYSLOG_SERVER".getBytes(StandardCharsets.UTF_8);
DatagramPacket discoveryResponse = new DatagramPacket(
responseData,
responseData.length,
clientAddress,
clientPort);
discoverySocket.send(discoveryResponse);
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static final int MAX_MESSAGE_SIZE = 480;
public void start(int port) {
System.out.println("Syslog Server started on port " + port);
// Discovery Listener in a separate thread
new Thread(() -> runDiscoveryListener()).start();
try {
// Create a DatagramSocket to listen for incoming messages
DatagramSocket socket = new DatagramSocket(port);
// Buffer to hold incoming messages
byte[] buffer = new byte[1024];
while (true) {
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
// Wait for a message to be received (blocking call)
socket.receive(packet);
// Extract the message from the packet + data; how many bytes were actually
// received
int length = packet.getLength();
if (length > MAX_MESSAGE_SIZE) {
System.err.println("Received message exceeds maximum allowed size of " + MAX_MESSAGE_SIZE
+ " bytes. Message will be ignored.");
continue; // Skip processing this message
}
String message = new String(
packet.getData(), // complete byte array be aware: packet.getData() returns the entire buffer,
// not just the received data
packet.getOffset(), // get the offset where the data starts
length,
StandardCharsets.UTF_8); // Convert the byte array to a string using UTF-8 encoding
// Show Client IP and Port
InetAddress clientAddress = packet.getAddress();
int clientPort = packet.getPort();
System.out.println("From: " + clientAddress.getHostAddress() + ":" + clientPort);
System.out.println("Message received: " + message);
SyslogMessage syslogMessage = new SyslogMessage(
SyslogMessage.Facility.LOCAL0,
SyslogMessage.Severity.INFORMATIONAL,
new AsciiChars.L255("server"),
new AsciiChars.L048("syslogServer"),
new AsciiChars.L128("-"),
new AsciiChars.L032("MSG"),
null,
new SyslogMessage.TextMessage(message)
);
System.out.println("Formatted Syslog Message: " + syslogMessage.toString());
}
}
catch (IOException e) {
System.err.println("Could not start server: " + e.getMessage());
return;
}
}
}

View File

@ -0,0 +1,56 @@
package vs;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
class AsciiCharsTest {
@Test
void nullValue() {
var ac = new AsciiChars.L004(null);
assertEquals(ac.toString(), "-");
}
@Test
void emptyValue() {
var ac = new AsciiChars.L004("");
assertEquals(ac.toString(), "-");
}
@Test
void longValue() {
var ac = new AsciiChars.L004("1234");
assertEquals(ac.toString(), "1234");
}
@Test
void longerValue() {
var thrown = assertThrows(IllegalArgumentException.class, () -> {
new AsciiChars.L004("12345");
});
assertEquals("Stringlänge = 5 > 4", thrown.getMessage());
}
@Test
void space() {
var thrown = assertThrows(IllegalArgumentException.class, () -> {
new AsciiChars.L004("1 1");
});
assertEquals(
"Stringinhalt nicht printable US-ASCII ohne Space",
thrown.getMessage()
);
}
@Test
void special() {
var thrown = assertThrows(IllegalArgumentException.class, () -> {
new AsciiChars.L004("ä");
});
assertEquals(
"Stringinhalt nicht printable US-ASCII ohne Space",
thrown.getMessage()
);
}
}

View File

@ -0,0 +1,93 @@
package vs;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
import vs.StructuredData.*;
import vs.SyslogMessage.*;
class SyslogMessageTest {
@Test
void testToString() {
var m1 = new SyslogMessage(
//
Facility.SECURITY1, //
Severity.CRITICAL, //
new AsciiChars.L255("mymachine.example.com"), //
new AsciiChars.L048("su"), //
new AsciiChars.L128(""), //
new AsciiChars.L032("ID47"), //
new StructuredData() //
.add(Element.newTimeQuality(true, true))
.add(
new Element("exampleSDID@32473")
.add(new Param("iut", "3"))
.add(new Param("eventSource", "Application"))
.add(new Param("eventID", "1011"))
)
.add(
new Element("examplePriority@32473").add(
new Param("class", "high")
)
),
new TextMessage("'su root' failed for lonvick on /dev/pts/8")
);
var s = m1.toString();
assertEquals(s.substring(0, 6), "<34>1 ");
assertEquals(
s.substring(s.length() - 221, s.length()),
" mymachine.example.com su - ID47 [timeQuality tzKnown=\"1\" isSynced=\"1\"][exampleSDID@32473 iut=\"3\" eventSource=\"Application\" eventID=\"1011\"][examplePriority@32473 class=\"high\"] 'su root' failed for lonvick on /dev/pts/8"
);
}
@Test
void test2() {
var m1 = new SyslogMessage(
//
Facility.SECURITY1, //
Severity.CRITICAL, //
new AsciiChars.L255("mymachine.example.com"), //
new AsciiChars.L048("su"), //
new AsciiChars.L128(""), //
new AsciiChars.L032("ID47"), //
null, //
new TextMessage("'su root' failed for lonvick on /dev/pts/8")
);
var s = m1.toString();
assertEquals(s.substring(0, 6), "<34>1 ");
assertEquals(
s.substring(s.length() - 78, s.length()),
" mymachine.example.com su - ID47 'su root' failed for lonvick on /dev/pts/8"
);
}
@Test
void test3() {
var m1 = new SyslogMessage(
//
Facility.SECURITY1, //
Severity.CRITICAL, //
new AsciiChars.L255("mymachine.example.com"), //
new AsciiChars.L048("su"), //
new AsciiChars.L128(""), //
new AsciiChars.L032("ID47"), //
new StructuredData() //
.add(Element.newTimeQuality(true, true))
.add(
Element.newOrigin(
new String[] { "0.0.8.8", "8.8.8.8" },
null,
null,
null
)
)
.add(Element.newMeta(null, 32, "de")),
new BinaryMessage(null)
);
assertEquals(
m1.data().toString(),
"[timeQuality tzKnown=\"1\" isSynced=\"1\"][origin ip=\"0.0.8.8\" ip=\"8.8.8.8\"][meta sysUpTime=\"32\" language=\"de\"]"
);
}
}