diff --git a/src/main/java/com/sipgate/udpproxy/Application.java b/src/main/java/com/sipgate/udpproxy/Application.java index 3988f10..73454c0 100644 --- a/src/main/java/com/sipgate/udpproxy/Application.java +++ b/src/main/java/com/sipgate/udpproxy/Application.java @@ -1,11 +1,62 @@ package com.sipgate.udpproxy; -import picocli.CommandLine; +import com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.decoder.Apn; public class Application { public static void main(final String[] args) { - System.exit(new CommandLine(new UdpProxyCommand()).execute(args)); + // 90 + byte[] createBearerResponse = { + (byte) 0x48, (byte) 0x60, (byte) 0x00, (byte) 0x53, (byte) 0x29, (byte) 0x14, (byte) 0x14, (byte) 0x40, (byte) 0x00, (byte) 0x05, (byte) 0xd9, (byte) 0x00, (byte) 0x02, (byte) 0x00, (byte) 0x02, (byte) 0x00, + (byte) 0x10, (byte) 0x00, (byte) 0x5d, (byte) 0x00, (byte) 0x25, (byte) 0x00, (byte) 0x49, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x08, (byte) 0x02, (byte) 0x00, (byte) 0x02, (byte) 0x00, (byte) 0x10, + (byte) 0x00, (byte) 0x57, (byte) 0x00, (byte) 0x09, (byte) 0x00, (byte) 0x80, (byte) 0x23, (byte) 0xe1, (byte) 0x98, (byte) 0x03, (byte) 0x0a, (byte) 0xca, (byte) 0x9b, (byte) 0x9c, (byte) 0x57, (byte) 0x00, + (byte) 0x09, (byte) 0x01, (byte) 0x81, (byte) 0x29, (byte) 0x14, (byte) 0x0b, (byte) 0xfb, (byte) 0x0a, (byte) 0x51, (byte) 0x18, (byte) 0x98, (byte) 0x56, (byte) 0x00, (byte) 0x0d, (byte) 0x00, (byte) 0x18, + (byte) 0x62, (byte) 0xf2, (byte) 0x30, (byte) 0x3a, (byte) 0xad, (byte) 0x62, (byte) 0xf2, (byte) 0x30, (byte) 0x02, (byte) 0x2d, (byte) 0x9d, (byte) 0x25, (byte) 0x72, (byte) 0x00, (byte) 0x02, (byte) 0x00, + (byte) 0x80, (byte) 0x01, (byte) 0x03, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x5c + }; + + // 9 + byte[] echoRequest = {(byte) 0x40, (byte) 0x01, (byte) 0x00, (byte) 0x09, (byte) 0x3c, (byte) 0x98, (byte) 0x84, (byte) 0x00, (byte) 0x03, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x6d + }; + + + // gtp-both.pcap 15 + byte[] createSessionRequest = { + (byte) 0x48,(byte) 0x20,(byte) 0x01,(byte) 0x0f,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0xed,(byte) 0x00,(byte) 0x01,(byte) 0x00,(byte) 0x08,(byte) 0x00, + (byte) 0x62,(byte) 0x02,(byte) 0x43,(byte) 0x68,(byte) 0x10,(byte) 0x72,(byte) 0x65,(byte) 0xf4,(byte) 0x4c,(byte) 0x00,(byte) 0x07,(byte) 0x00,(byte) 0x94,(byte) 0x51,(byte) 0x97,(byte) 0x20, + (byte) 0x73,(byte) 0x65,(byte) 0xf4,(byte) 0x4b,(byte) 0x00,(byte) 0x08,(byte) 0x00,(byte) 0x53,(byte) 0x75,(byte) 0x92,(byte) 0x70,(byte) 0x57,(byte) 0x74,(byte) 0x56,(byte) 0x06,(byte) 0x56, + (byte) 0x00,(byte) 0x0d,(byte) 0x00,(byte) 0x18,(byte) 0x62,(byte) 0xf2,(byte) 0x30,(byte) 0x8c,(byte) 0xb6,(byte) 0x62,(byte) 0xf2,(byte) 0x30,(byte) 0x00,(byte) 0xeb,(byte) 0x81,(byte) 0x01, + (byte) 0x53,(byte) 0x00,(byte) 0x03,(byte) 0x00,(byte) 0x62,(byte) 0xf2,(byte) 0x30,(byte) 0x52,(byte) 0x00,(byte) 0x01,(byte) 0x00,(byte) 0x06,(byte) 0x4d,(byte) 0x00,(byte) 0x02,(byte) 0x00, + (byte) 0x00,(byte) 0x08,(byte) 0x57,(byte) 0x00,(byte) 0x09,(byte) 0x00,(byte) 0x86,(byte) 0x13,(byte) 0x0d,(byte) 0x95,(byte) 0x1e,(byte) 0xd4,(byte) 0x17,(byte) 0x71,(byte) 0x4c,(byte) 0x47, + (byte) 0x00,(byte) 0x24,(byte) 0x00,(byte) 0x10,(byte) 0x73,(byte) 0x69,(byte) 0x70,(byte) 0x67,(byte) 0x61,(byte) 0x74,(byte) 0x65,(byte) 0x2d,(byte) 0x74,(byte) 0x65,(byte) 0x73,(byte) 0x74, + (byte) 0x2d,(byte) 0x64,(byte) 0x65,(byte) 0x76,(byte) 0x06,(byte) 0x6d,(byte) 0x6e,(byte) 0x63,(byte) 0x30,(byte) 0x30,(byte) 0x33,(byte) 0x06,(byte) 0x6d,(byte) 0x63,(byte) 0x63,(byte) 0x32, + (byte) 0x36,(byte) 0x32,(byte) 0x04,(byte) 0x67,(byte) 0x70,(byte) 0x72,(byte) 0x73,(byte) 0x80,(byte) 0x00,(byte) 0x01,(byte) 0x00,(byte) 0x00,(byte) 0x63,(byte) 0x00,(byte) 0x01,(byte) 0x00, + (byte) 0x01,(byte) 0x4f,(byte) 0x00,(byte) 0x05,(byte) 0x00,(byte) 0x01,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x7f,(byte) 0x00,(byte) 0x01,(byte) 0x00,(byte) 0x00,(byte) 0x48, + (byte) 0x00,(byte) 0x08,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x7d,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0xc3,(byte) 0x50,(byte) 0x4e,(byte) 0x00,(byte) 0x23,(byte) 0x00,(byte) 0x80, + (byte) 0x80,(byte) 0x21,(byte) 0x10,(byte) 0x01,(byte) 0x00,(byte) 0x00,(byte) 0x10,(byte) 0x81,(byte) 0x06,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x83,(byte) 0x06,(byte) 0x00, + (byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x0d,(byte) 0x00,(byte) 0x00,(byte) 0x03,(byte) 0x00,(byte) 0x00,(byte) 0x0a,(byte) 0x00,(byte) 0x00,(byte) 0x05,(byte) 0x00,(byte) 0x00, + (byte) 0x10,(byte) 0x00,(byte) 0x5d,(byte) 0x00,(byte) 0x2c,(byte) 0x00,(byte) 0x49,(byte) 0x00,(byte) 0x01,(byte) 0x00,(byte) 0x06,(byte) 0x57,(byte) 0x00,(byte) 0x09,(byte) 0x02,(byte) 0x84, + (byte) 0x13,(byte) 0x0d,(byte) 0x95,(byte) 0x1e,(byte) 0xd4,(byte) 0x17,(byte) 0x71,(byte) 0x4c,(byte) 0x50,(byte) 0x00,(byte) 0x16,(byte) 0x00,(byte) 0x48,(byte) 0x09,(byte) 0x00,(byte) 0x00, + (byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x00, + (byte) 0x00,(byte) 0x00,(byte) 0x03,(byte) 0x00,(byte) 0x01,(byte) 0x00,(byte) 0x6f,(byte) 0x72,(byte) 0x00,(byte) 0x02,(byte) 0x00,(byte) 0x80,(byte) 0x01,(byte) 0x5f,(byte) 0x00,(byte) 0x02, + (byte) 0x00,(byte) 0x08,(byte) 0x00 + }; + + // 262034860127565_EOFINDER_OVERALL_summary-BO-DATA-20230915-142910-276.pcap frame 64 + byte [] createBearerRequest = { + (byte) 0x48,(byte) 0x5f,(byte) 0x00,(byte) 0x82,(byte) 0x0b,(byte) 0x35,(byte) 0x32,(byte) 0xac,(byte) 0x4b,(byte) 0xbe,(byte) 0xfa,(byte) 0x00,(byte) 0x49,(byte) 0x00,(byte) 0x01,(byte) 0x00, + (byte) 0x06,(byte) 0x5d,(byte) 0x00,(byte) 0x71,(byte) 0x00,(byte) 0x49,(byte) 0x00,(byte) 0x01,(byte) 0x00,(byte) 0x00,(byte) 0x50,(byte) 0x00,(byte) 0x16,(byte) 0x00,(byte) 0x7c,(byte) 0x01, + (byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x32,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x32,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x32,(byte) 0x00, + (byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x32,(byte) 0x54,(byte) 0x00,(byte) 0x39,(byte) 0x00,(byte) 0x22,(byte) 0x20,(byte) 0x20,(byte) 0x19,(byte) 0x21,(byte) 0xfd,(byte) 0x00,(byte) 0x02, + (byte) 0x30,(byte) 0xba,(byte) 0xbe,(byte) 0x00,(byte) 0x01,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x01,(byte) 0x80,(byte) 0x30,(byte) 0x11, + (byte) 0x51,(byte) 0x27,(byte) 0x10,(byte) 0x75,(byte) 0x30,(byte) 0x11,(byte) 0x21,(byte) 0x19,(byte) 0x21,(byte) 0xfd,(byte) 0x00,(byte) 0x02,(byte) 0x30,(byte) 0xba,(byte) 0xbe,(byte) 0x00, + (byte) 0x01,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x00,(byte) 0x01,(byte) 0x80,(byte) 0x30,(byte) 0x11,(byte) 0x51,(byte) 0x27,(byte) 0x10,(byte) 0x75, + (byte) 0x30,(byte) 0x57,(byte) 0x00,(byte) 0x09,(byte) 0x01,(byte) 0x85,(byte) 0x39,(byte) 0xaf,(byte) 0x26,(byte) 0x1e,(byte) 0xd4,(byte) 0x17,(byte) 0x65,(byte) 0x75,(byte) 0x5e,(byte) 0x00, + (byte) 0x04,(byte) 0x00,(byte) 0x39,(byte) 0xaf,(byte) 0x26,(byte) 0x1e + }; + + System.out.println(Apn.encode("sipgate.test.dev")); + //System.exit(new CommandLine(new UdpProxyCommand()).execute(args)); } diff --git a/src/main/java/com/sipgate/udpproxy/UdpProxyCommand.java b/src/main/java/com/sipgate/udpproxy/UdpProxyCommand.java index a239131..2ad4f0b 100644 --- a/src/main/java/com/sipgate/udpproxy/UdpProxyCommand.java +++ b/src/main/java/com/sipgate/udpproxy/UdpProxyCommand.java @@ -1,7 +1,7 @@ package com.sipgate.udpproxy; -import com.sipgate.udpproxy.protocol.Protocol; -import com.sipgate.udpproxy.udp.ProxyServer; +import com.sipgate.udpproxy.udp.proxy.ProxyProtocol; +import com.sipgate.udpproxy.udp.proxy.ProxyServer; import com.sipgate.udpproxy.udp.ServerFactory; import picocli.CommandLine.Command; import picocli.CommandLine.Option; @@ -28,11 +28,11 @@ public class UdpProxyCommand implements Callable { private String listenIp; @Option(names = {"-t", "--type"}, description = "The protocol to proxy", defaultValue = "SIP") - private Protocol protocol; + private ProxyProtocol proxyProtocol; @Override public Integer call() { - final Optional maybeServer = ServerFactory.createServer(listenIp, port, iface, protocol); + final Optional maybeServer = ServerFactory.createServer(listenIp, port, iface, proxyProtocol); if (maybeServer.isEmpty()) { return 1; } diff --git a/src/main/java/com/sipgate/udpproxy/protocol/PacketRewriter.java b/src/main/java/com/sipgate/udpproxy/protocol/PacketRewriter.java deleted file mode 100644 index 2f01eab..0000000 --- a/src/main/java/com/sipgate/udpproxy/protocol/PacketRewriter.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.sipgate.udpproxy.protocol; - -import java.net.DatagramPacket; - -public interface PacketRewriter { - - /** - * Rewrites the payload of the given packet. This method is called before the packet is forwarded to the target. - * Use this if the protocol in question must be modified (e.g. remove headers, replace IP addresses, ports, etc.). - * - * @param inboundPacket the packet received from the source - * @return the modified payload - */ - byte[] rewritePayload(final DatagramPacket inboundPacket); -} diff --git a/src/main/java/com/sipgate/udpproxy/protocol/Protocol.java b/src/main/java/com/sipgate/udpproxy/protocol/Protocol.java deleted file mode 100644 index 4858e9c..0000000 --- a/src/main/java/com/sipgate/udpproxy/protocol/Protocol.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.sipgate.udpproxy.protocol; - -public enum Protocol { - SIP -} diff --git a/src/main/java/com/sipgate/udpproxy/protocol/sip/SipRewriter.java b/src/main/java/com/sipgate/udpproxy/protocol/sip/SipRewriter.java deleted file mode 100644 index 02e8a7c..0000000 --- a/src/main/java/com/sipgate/udpproxy/protocol/sip/SipRewriter.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.sipgate.udpproxy.protocol.sip; - -import com.sipgate.udpproxy.protocol.PacketRewriter; -import java.net.DatagramPacket; - -public class SipRewriter implements PacketRewriter { - - /** - * Removes the Route header from the SIP packet. - * sipgate customer loadbalancers will respond with 403 if the Route header is present. - */ - @Override - public byte[] rewritePayload(final DatagramPacket inboundPacket) { - final byte[] payload = new byte[inboundPacket.getLength()]; - System.arraycopy(inboundPacket.getData(), inboundPacket.getOffset(), payload, 0, inboundPacket.getLength()); - final String original = new String(payload); - final StringBuilder sb = new StringBuilder(); - original.lines().forEach(line -> { - if (!line.startsWith("Route:")) { - sb.append(line).append("\r\n"); - } - }); - - return sb.toString().getBytes(); - } -} diff --git a/src/main/java/com/sipgate/udpproxy/udp/ServerFactory.java b/src/main/java/com/sipgate/udpproxy/udp/ServerFactory.java index 2113bdf..29fa4b7 100644 --- a/src/main/java/com/sipgate/udpproxy/udp/ServerFactory.java +++ b/src/main/java/com/sipgate/udpproxy/udp/ServerFactory.java @@ -1,6 +1,7 @@ package com.sipgate.udpproxy.udp; -import com.sipgate.udpproxy.protocol.Protocol; +import com.sipgate.udpproxy.udp.proxy.ProxyProtocol; +import com.sipgate.udpproxy.udp.proxy.ProxyServer; import java.net.*; import java.util.Optional; @@ -10,13 +11,13 @@ public final class ServerFactory { private ServerFactory() { } - public static Optional createServer(final String ip, final int port, final String device, final Protocol protocol) { + public static Optional createServer(final String ip, final int port, final String device, final ProxyProtocol proxyProtocol) { try { return Optional.of(new ProxyServer( new DatagramSocket(getSocketAddress(ip, port, device)), 4096, - protocol + proxyProtocol )); } catch (final SocketException e) { System.err.println("Cannot create server: " + e.getMessage()); diff --git a/src/main/java/com/sipgate/udpproxy/udp/Dialog.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/Dialog.java similarity index 66% rename from src/main/java/com/sipgate/udpproxy/udp/Dialog.java rename to src/main/java/com/sipgate/udpproxy/udp/proxy/Dialog.java index 988f7c3..4b9c6e7 100644 --- a/src/main/java/com/sipgate/udpproxy/udp/Dialog.java +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/Dialog.java @@ -1,8 +1,9 @@ -package com.sipgate.udpproxy.udp; +package com.sipgate.udpproxy.udp.proxy; -import com.sipgate.udpproxy.protocol.PacketRewriter; -import com.sipgate.udpproxy.protocol.Protocol; -import com.sipgate.udpproxy.protocol.sip.SipRewriter; +import com.sipgate.udpproxy.udp.proxy.processors.MessageProcessor; +import com.sipgate.udpproxy.udp.proxy.processors.Message; +import com.sipgate.udpproxy.udp.proxy.processors.sip.SipMessage; +import com.sipgate.udpproxy.udp.proxy.processors.sip.SipMessageProcessor; import java.io.IOException; import java.net.*; @@ -15,21 +16,23 @@ public class Dialog implements Runnable { private static final int RECV_TIMEOUT_MS = 1000 * 60 * 5; // 5 minutes private final DatagramSocket proxySource; private final DatagramSocket proxyTarget; - private final PacketRewriter packetRewriter; + private final MessageProcessor messageProcessor; private final InetAddress clientAddress; + private final ProxyProtocol proxyProtocol; private final int clientPort; private final int bufferSize; private long lastActivity = System.currentTimeMillis(); private boolean running = true; private Thread thread; - Dialog(final DatagramSocket proxySource, final DatagramSocket proxyTarget, final int bufferSize, final PacketRewriter rewriter, final InetAddress clientAddress, final int clientPort) { + Dialog(final DatagramSocket proxySource, final DatagramSocket proxyTarget, final ProxyProtocol proxyProtocol, final int bufferSize, final MessageProcessor rewriter, final InetAddress clientAddress, final int clientPort) { this.proxySource = proxySource; this.proxyTarget = proxyTarget; this.bufferSize = bufferSize; this.clientAddress = clientAddress; this.clientPort = clientPort; - this.packetRewriter = rewriter; + this.messageProcessor = rewriter; + this.proxyProtocol = proxyProtocol; try { proxyTarget.setSoTimeout(RECV_TIMEOUT_MS); @@ -38,17 +41,12 @@ public class Dialog implements Runnable { } } - public static Dialog create(final DatagramSocket proxySource, final int bufferSize, final DatagramPacket clientPacket, final Protocol protocol) { - final Supplier rewriter = () -> { - switch (protocol) { - case SIP: - return new SipRewriter(); - default: - throw new IllegalArgumentException("Unsupported protocol: " + protocol); - } + public static Dialog create(final DatagramSocket proxySource, final int bufferSize, final DatagramPacket clientPacket, final ProxyProtocol proxyProtocol) { + final Supplier rewriter = () -> switch (proxyProtocol) { + case SIP -> new SipMessageProcessor(); }; try { - return new Dialog(proxySource, new DatagramSocket(), bufferSize, rewriter.get(), clientPacket.getAddress(), clientPacket.getPort()); + return new Dialog(proxySource, new DatagramSocket(), proxyProtocol, bufferSize, rewriter.get(), clientPacket.getAddress(), clientPacket.getPort()); } catch (final SocketException e) { System.err.println("Error while creating client socket: " + e.getMessage()); throw new RuntimeException(e); @@ -68,8 +66,11 @@ public void run() { lastActivity = System.currentTimeMillis(); + final var message = createMessage(packetFromTarget); // Forward modified answer to proxy source - final var payload = packetRewriter.rewritePayload(packetFromTarget); + messageProcessor.rewriteMessage(packetFromTarget, message); + message.packBytes(); + final var payload = message.finalizeForNetwork(); final var packetToSource = new DatagramPacket( payload, payload.length, @@ -87,7 +88,10 @@ public void run() { public void sendToTarget(final DatagramPacket unmodifiedPacket, final String targetAddress, final int targetPort) { try { - final var payload = packetRewriter.rewritePayload(unmodifiedPacket); + final var message = createMessage(unmodifiedPacket); + messageProcessor.rewriteMessage(unmodifiedPacket, message); + message.packBytes(); + final var payload = message.finalizeForNetwork(); final var packet = new DatagramPacket(payload, payload.length, new InetSocketAddress(targetAddress, targetPort)); proxyTarget.send(packet); } catch (final IOException e) { @@ -107,4 +111,10 @@ public void stop() { thread.interrupt(); } } + + private Message createMessage(final DatagramPacket packet) { + return switch (proxyProtocol) { + case SIP -> new SipMessage(packet.getData()); + }; + } } diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/ProxyProtocol.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/ProxyProtocol.java new file mode 100644 index 0000000..8e1b4bf --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/ProxyProtocol.java @@ -0,0 +1,5 @@ +package com.sipgate.udpproxy.udp.proxy; + +public enum ProxyProtocol { + SIP +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/ProxyServer.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/ProxyServer.java similarity index 82% rename from src/main/java/com/sipgate/udpproxy/udp/ProxyServer.java rename to src/main/java/com/sipgate/udpproxy/udp/proxy/ProxyServer.java index 907aa14..e8c10ae 100644 --- a/src/main/java/com/sipgate/udpproxy/udp/ProxyServer.java +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/ProxyServer.java @@ -1,8 +1,7 @@ -package com.sipgate.udpproxy.udp; +package com.sipgate.udpproxy.udp.proxy; -import com.sipgate.udpproxy.protocol.Protocol; -import com.sipgate.udpproxy.protocol.ProxyTargetResolver; -import com.sipgate.udpproxy.protocol.sip.SipTargetResolver; +import com.sipgate.udpproxy.udp.proxy.processors.ProxyTargetProcessor; +import com.sipgate.udpproxy.udp.proxy.processors.sip.SipTargetProcessor; import java.net.DatagramPacket; import java.net.DatagramSocket; @@ -19,21 +18,21 @@ public class ProxyServer implements Runnable { private final int bufferSize; private final ExecutorService executorService = Executors.newFixedThreadPool(1000); private final Map dialogs = new HashMap<>(); - private final ProxyTargetResolver targetResolver; - private final Protocol protocol; + private final ProxyTargetProcessor targetResolver; + private final ProxyProtocol proxyProtocol; private boolean running = true; - public ProxyServer(final DatagramSocket serverSocket, final int bufferSize, final Protocol protocol) { + public ProxyServer(final DatagramSocket serverSocket, final int bufferSize, final ProxyProtocol proxyProtocol) { this.serverSocket = serverSocket; this.bufferSize = bufferSize; - this.protocol = protocol; - switch (protocol) { + this.proxyProtocol = proxyProtocol; + switch (proxyProtocol) { case SIP: - this.targetResolver = new SipTargetResolver(); + this.targetResolver = new SipTargetProcessor(); break; default: - throw new IllegalArgumentException("Unsupported protocol: " + protocol); + throw new IllegalArgumentException("Unsupported protocol: " + proxyProtocol); } } @@ -51,7 +50,7 @@ public void run() { // Create dialog if not exists and listen for traffic from proxy target final var dialogKey = getDialogKey(fromClient); final var dialog = dialogs.computeIfAbsent(dialogKey, key -> { - final var newDialog = Dialog.create(serverSocket, bufferSize, fromClient, protocol); + final var newDialog = Dialog.create(serverSocket, bufferSize, fromClient, proxyProtocol); executorService.submit(newDialog); return newDialog; }); diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/Message.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/Message.java new file mode 100644 index 0000000..b10400c --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/Message.java @@ -0,0 +1,44 @@ +package com.sipgate.udpproxy.udp.proxy.processors; + +public abstract class Message { + + private final Payload payload; + + protected Message(final byte[] bytes) { + this.payload = new Payload(bytes, this); + } + + protected Message(final Payload payload, final Message parent, final int windowOffset, final int windowSize) { + this.payload = payload; + this.payload.addMessage(this, parent, windowOffset, windowOffset + (windowSize - 1)); + } + + /** + * Returns the {@link Payload} bytes of this message as a copy + * @return the payload bytes + */ + public byte[] finalizeForNetwork() { + return payload.finalizeForNetwork(); + } + + /** + * Packs the bytes of this message and its children into the {@link Payload}. + */ + public abstract void packBytes(); + + protected byte getPayloadByte(final int index) { + return payload.getPayloadByte(index, this); + } + + protected byte[] getCopyOfBytes(final int offset) { + return payload.finalizeForNetwork(offset, this); + } + + protected void setPayloadBytes(final byte[] bytes) { + payload.setPayloadBytes(bytes, this); + } + + protected int getPayloadLength() { + return payload.getPayloadLength(this); + } +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/MessageProcessor.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/MessageProcessor.java new file mode 100644 index 0000000..6e01dc7 --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/MessageProcessor.java @@ -0,0 +1,15 @@ +package com.sipgate.udpproxy.udp.proxy.processors; + +import java.net.DatagramPacket; + +public interface MessageProcessor { + + /** + * Rewrites the payload of a UDP packet. This method is called before the packet is forwarded to the target. + * Use this if the protocol in question must be modified (e.g. remove headers, replace IP addresses, ports, etc.). + * + * @param packet the packet from the source to be rewritten + * @param message the message to be rewritten + */ + void rewriteMessage(DatagramPacket packet, Message message); +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/Payload.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/Payload.java new file mode 100644 index 0000000..11ad0a5 --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/Payload.java @@ -0,0 +1,274 @@ +package com.sipgate.udpproxy.udp.proxy.processors; + +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Represents a payload of a UDP packet. The payload byte array is the same raw memory for each object instance. + * The windowStartIndex and windowEndIndex are used per object instance to define the window of the payload byte array + * that is visible to the object instance. + *

+ * It is possible for windows to overlap but only if the overlapping object is a child of the overlapped object. + * For instance, the bearer context IE contains multiple other IEs inside it and the window of the bearer context IE + * overlaps with the windows of the IEs inside it. + *

+ * It is possible to insert raw bytes into the payload byte array. This will shift the windowEndIndex of the object + * that put the bytes there as necessary and move the windowStartIndex and windowEndIndex of all other objects that + * come after the object that put the bytes there. + */ +public class Payload { + + private final Lock lock = new ReentrantLock(); + private final Map windowIndex = new IdentityHashMap<>(); + private final Message rootMessage; + private byte[] payloadBytes; + + /** + * Creates a new instance of this payload and initializes the rootMessage. + * + * @param payloadBytes The raw payload bytes + * @param rootMessage The root message of this payload + */ + public Payload(final byte[] payloadBytes, final Message rootMessage) { + this.payloadBytes = payloadBytes; + this.rootMessage = rootMessage; + windowIndex.put(rootMessage, new Window(0, payloadBytes.length)); + } + + /** + * Returns a single byte from the payload byte array through the window of the given message. + * + * @param index The index of the byte to return + * @param message The message that defines the window + * @return The byte at the given index + */ + public byte getPayloadByte(final int index, final Message message) { + lock.lock(); + try { + assertElementIsKnown(message); + final var window = windowIndex.get(message); + assertValidIndex(index, window); + + return payloadBytes[window.start + index]; + } finally { + lock.unlock(); + } + + } + + /** + * Sets a single byte in the payload byte array through the window of the given message. + * + * @param index The index of the byte to set + * @param value The value to set the byte to + * @param message The message that defines the window + */ + public void setPayloadByte(final int index, final byte value, final Message message) { + lock.lock(); + try { + assertElementIsKnown(message); + final var window = windowIndex.get(message); + assertValidIndex(index, window); + + payloadBytes[window.start + index] = value; + } finally { + lock.unlock(); + } + } + + /** + * Replaces the payload byte array with the given bytes and updates the windows of all accessors accordingly. + * + * @param bytes The new payload bytes + * @param message The message that defines the window + */ + public void setPayloadBytes(final byte[] bytes, final Message message) { + lock.lock(); + try { + assertElementIsKnown(message); + final var window = windowIndex.get(message); + + // Simple case: Payload lenght has not changed + if (bytes.length == window.length()) { + System.arraycopy(bytes, 0, payloadBytes, window.start, bytes.length); + lock.unlock(); + return; + } + + // Complex case: Payload length has changed + final var delta = window.length() - bytes.length; + final byte[] newPayload = new byte[payloadBytes.length - delta]; + + System.arraycopy(payloadBytes, 0, newPayload, 0, window.start); + System.arraycopy(bytes, 0, newPayload, window.start, bytes.length); + System.arraycopy(payloadBytes, window.end, newPayload, window.start + bytes.length, payloadBytes.length - window.end); + + // Update window indices + final var windows = windowIndex.entrySet().stream() + .filter(e -> e.getValue().start > window.start) + .toList(); + + for (final var entry : windows) { + final var w = entry.getValue(); + windowIndex.put(entry.getKey(), new Window(w.start - delta, w.end - delta)); + } + + payloadBytes = newPayload; + } finally { + lock.unlock(); + } + } + + /** + * Returns the length of the payload byte array through the window of the given message. + * + * @param message The message that defines the window + * @return The length of the payload byte array + */ + public int getPayloadLength(final Message message) { + lock.lock(); + try { + assertElementIsKnown(message); + return windowIndex.get(message).length(); + } finally { + lock.unlock(); + } + } + + /** + * Returns a copy of the payload bytes of the given message. + * + * @param message The message to get the payload bytes from + * @return A copy of the payload bytes + */ + public byte[] finalizeForNetwork(final int offset, final Message message) { + lock.lock(); + try { + if (offset < 0) { + throw new IllegalArgumentException("Offset must be greater than or equal to 0"); + } + assertElementIsKnown(message); + final var window = windowIndex.get(message); + + if (offset >= window.length()) { + throw new IllegalArgumentException("Offset must be less than the window length"); + } + + final var copyOfBytes = new byte[window.length() - offset]; + System.arraycopy(payloadBytes, window.start + offset, copyOfBytes, 0, window.length() - offset); + return copyOfBytes; + } finally { + lock.unlock(); + } + } + + /** + * Registers a new message with this payload. The window of the message must be within the window of its parent + * message. + * + * @param message The message to register + * @param parent The parent message of the message to register + * @param startIndexFromParent The start index of the window relative to the start of the parent message window + * @param endIndexFromParent The end index of the window relative to the start of the parent message window + */ + public void addMessage(final Message message, final Message parent, final int startIndexFromParent, final int endIndexFromParent) { + lock.lock(); + try { + if (windowIndex.containsKey(message)) { + throw new IllegalArgumentException("message is already registered with this payload"); + } + + if (parent == null) { + throw new IllegalArgumentException("parent is null"); + } + + if (windowIndex.containsKey(parent)) { + throw new IllegalArgumentException("parent is not registered with this payload"); + } + + if (startIndexFromParent < 0 || startIndexFromParent >= payloadBytes.length) { + throw new ArrayIndexOutOfBoundsException("Window start index is out of bounds"); + } + + if (endIndexFromParent < 0 || endIndexFromParent > payloadBytes.length) { + throw new ArrayIndexOutOfBoundsException("Window end index is out of bounds"); + } + + final var parentWindow = windowIndex.get(parent); + final var offset = parentWindow.start; + final var startIndex = offset + startIndexFromParent; + final var endIndex = offset + endIndexFromParent; + + if (startIndex < parentWindow.start || endIndex > parentWindow.end) { + throw new IllegalArgumentException("Window is outside of parent window"); + } + + final var window = new Window(startIndex, endIndex); + windowIndex.put(message, window); + } finally { + lock.unlock(); + } + } + + /** + * Removes an element window from this payload. Nulls all bytes in the window. + * + * @param message The message to remove + */ + public void removeMessage(final Message message) { + lock.lock(); + try { + assertElementIsKnown(message); + if (message == rootMessage) { + throw new IllegalArgumentException("Cannot remove root element"); + } + final var window = windowIndex.get(message); + for (int i = window.start; i < window.end; i++) { + payloadBytes[i] = 0; + } + windowIndex.remove(message); + } finally { + lock.unlock(); + } + } + + /** + * Instructs the payload root message to pack its bytes. This will cause all child messages to pack their + * bytes as well. A copy of the payload byte array is returned that can be sent over the network. + * + * @return A copy of the payload byte array + */ + public byte[] finalizeForNetwork() { + lock.lock(); + try { + rootMessage.packBytes(); + final var copyOfBytes = new byte[payloadBytes.length]; + System.arraycopy(payloadBytes, 0, copyOfBytes, 0, payloadBytes.length); + return copyOfBytes; + } finally { + lock.unlock(); + } + } + + private void assertElementIsKnown(final Message message) { + if (!windowIndex.containsKey(message)) { + throw new IllegalArgumentException("message is not registered with this payload"); + } + } + + private void assertValidIndex(final int index, final Window window) { + if (index < 0 || index >= window.length()) { + throw new ArrayIndexOutOfBoundsException("Window index is out of bounds"); + } + } + + + private record Window(int start, int end) { + public int length() { + return end - start; + } + } + +} diff --git a/src/main/java/com/sipgate/udpproxy/protocol/ProxyTarget.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/ProxyTarget.java similarity index 80% rename from src/main/java/com/sipgate/udpproxy/protocol/ProxyTarget.java rename to src/main/java/com/sipgate/udpproxy/udp/proxy/processors/ProxyTarget.java index 6967c23..cf5689b 100644 --- a/src/main/java/com/sipgate/udpproxy/protocol/ProxyTarget.java +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/ProxyTarget.java @@ -1,4 +1,4 @@ -package com.sipgate.udpproxy.protocol; +package com.sipgate.udpproxy.udp.proxy.processors; /** * Represents a target to which the proxy forwards the packets. diff --git a/src/main/java/com/sipgate/udpproxy/protocol/ProxyTargetResolver.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/ProxyTargetProcessor.java similarity index 78% rename from src/main/java/com/sipgate/udpproxy/protocol/ProxyTargetResolver.java rename to src/main/java/com/sipgate/udpproxy/udp/proxy/processors/ProxyTargetProcessor.java index f8eb9b4..e000c1a 100644 --- a/src/main/java/com/sipgate/udpproxy/protocol/ProxyTargetResolver.java +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/ProxyTargetProcessor.java @@ -1,8 +1,8 @@ -package com.sipgate.udpproxy.protocol; +package com.sipgate.udpproxy.udp.proxy.processors; import java.net.DatagramPacket; -public interface ProxyTargetResolver { +public interface ProxyTargetProcessor { /** * Resolves the target for the given packet by inspecting the contents diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/GTPv2Payload.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/GTPv2Payload.java new file mode 100644 index 0000000..ad356ec --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/GTPv2Payload.java @@ -0,0 +1,125 @@ +package com.sipgate.udpproxy.udp.proxy.processors.gtpv2; + +import com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.InformationElement; +import com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.decoder.BitHelper; + +import java.util.List; + + +/** + * GTPv2 UDP payload. Represents Header and all Information Elements. + *

+ * + * + * For Header, see ETSI TS 129 274 V17.8.0 (2023-04) - Section 5.1 (Page 24) + * + * + * @author Lennart Rosam + */ +public class GTPv2Payload { + + private final byte[] payload; + + // In order of payload bytes + private int version; + private boolean piggybackingFlagSet; + private boolean teidFlagSet; + private boolean messagePriorityFlagSet; + private MessageType messageType; + private int messageLength; + private int tunnelEndpointIdentifier; + private int sequenceNumber; + private byte spare; + private List informationElements; + + + private GTPv2Payload(final byte[] payload) { + this.payload = payload; + + this.version = BitHelper.toInt(payload[0], 6, 8); + this.piggybackingFlagSet = BitHelper.isBitSet(payload[0], 5); + this.teidFlagSet = BitHelper.isBitSet(payload[0], 4); + this.messagePriorityFlagSet = BitHelper.isBitSet(payload[0], 3); + this.messageType = getMessageType(payload[1]); + this.messageLength = BitHelper.int16ToInt32(payload[2], payload[3]); + + final var offsetSequenceNumber = teidFlagSet ? 8 : 4; + final var offsetSpare = teidFlagSet ? 11 : 7; + + if (teidFlagSet) { + this.tunnelEndpointIdentifier = BitHelper.toInt32(payload[4], payload[5], payload[6], payload[7]); + } + this.sequenceNumber = BitHelper.int24ToInt32(payload[offsetSequenceNumber], payload[offsetSequenceNumber + 1], payload[offsetSequenceNumber + 2]); + this.spare = payload[offsetSpare]; + this.informationElements = InformationElement.fromBytes(payload, offsetSpare + 1); + } + + public byte[] getPayload() { + return payload; + } + + public List getInformationElements() { + return informationElements; + } + + public static GTPv2Payload fromBytes(final byte[] bytes) { + return new GTPv2Payload(bytes); + } + + @Override + public String toString() { + return "GTPv2Payload{" + + "version=" + version + + ", piggybackingFlagSet=" + piggybackingFlagSet + + ", teidFlagSet=" + teidFlagSet + + ", messagePriorityFlagSet=" + messagePriorityFlagSet + + ", messageType=" + messageType + + ", messageLength=" + messageLength + + ", tunnelEndpointIdentifier=" + tunnelEndpointIdentifier + + ", sequenceNumber=" + sequenceNumber + + ", spare=" + spare + + ", informationElements=" + informationElements + + '}'; + } + + private MessageType getMessageType(final byte b) { + for (final MessageType type : MessageType.values()) { + if (type.byteValue.equals(b)) { + return type; + } + } + + return MessageType.UNKNOWN; + } + + public enum MessageType { + + // Not all messages are implemented - only the once needed for our setup + ECHO_REQUEST("Echo Request", (byte) 0x01), + ECHO_RESPONSE("Echo Response", (byte) 0x02), + CREATE_SESSION_REQUEST("Create Session Request", (byte) 0x20), + CREATE_SESSION_RESPOSNE("Create Session Response", (byte) 0x21), + MODIFY_BEARER_REQUEST("Modify Bearer Request", (byte) 0x22), + MODIFY_BEARER_RESPONSE("Modify Bearer Response", (byte) 0x23), + DELETE_SESSION_REQUEST("Delete Session Request", (byte) 0x24), + DELETE_SESSION_RESPONSE("Delete Session Response", (byte) 0x25), + CREATE_BEARER_REQUEST("Create Bearer Request", (byte) 0x5f), + CREATE_BEARER_RESPONSE("Create Bearer Response", (byte) 0x60), + UNKNOWN("Not implemented", (byte) 0x00); + + + private final String humanReadable; + private final Byte byteValue; + + MessageType(final String humanReadable, final Byte byteValue) { + this.humanReadable = humanReadable; + this.byteValue = byteValue; + } + + @Override + public String toString() { + return String.format("%s (0x%s)", humanReadable, Integer.toHexString(byteValue & 0xff)); + } + } + +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/AccessPointName.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/AccessPointName.java new file mode 100644 index 0000000..477d002 --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/AccessPointName.java @@ -0,0 +1,42 @@ +package com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie; + +import com.sipgate.udpproxy.udp.proxy.processors.Message; +import com.sipgate.udpproxy.udp.proxy.processors.Payload; +import com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.decoder.Apn; + +public class AccessPointName extends InformationElement { + + private String apn; + + public AccessPointName(final Payload payload, final Message parent, final int windowOffset, final int windowSize) { + super(payload, parent, windowOffset, windowSize); + + + this.apn = Apn.decode(getCopyOfBytes(IE_HEADER_SIZE)); + } + + public String getApn() { + return apn; + } + + public AccessPointName setApn(final String apn) { + this.apn = apn; + return this; + } + + @Override + public void packBytes() { + final byte[] apnBytes = Apn.encode(apn); + setLength((short) apnBytes.length); + final byte[] payload = packHeaderWithEmptyPayload(apnBytes.length); + System.arraycopy(apnBytes, 0, payload, IE_HEADER_SIZE, apnBytes.length - IE_HEADER_SIZE); + setPayloadBytes(payload); + } + + @Override + public String toString() { + return "AccessPointName{" + + "apn='" + getApn() + '\'' + + '}'; + } +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/AggregateMaximumBitRate.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/AggregateMaximumBitRate.java new file mode 100644 index 0000000..ce35a62 --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/AggregateMaximumBitRate.java @@ -0,0 +1,43 @@ +package com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie; + +import com.sipgate.udpproxy.udp.proxy.processors.Message; +import com.sipgate.udpproxy.udp.proxy.processors.Payload; +import com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.decoder.BitHelper; + +public class AggregateMaximumBitRate extends GenericInformationElement { + private final int upLinkBitRate; + private final int downLinkBitRate; + + public AggregateMaximumBitRate(final Payload payload, final Message parent, final int windowOffset, final int windowSize) { + super(payload, parent, windowOffset, windowSize); + this.upLinkBitRate = BitHelper.toInt32( + getPayloadByte(IE_HEADER_SIZE), + getPayloadByte(IE_HEADER_SIZE + 1), + getPayloadByte(IE_HEADER_SIZE + 2), + getPayloadByte(IE_HEADER_SIZE + 3) + ); + + this.downLinkBitRate = BitHelper.toInt32( + getPayloadByte(IE_HEADER_SIZE + 4), + getPayloadByte(IE_HEADER_SIZE + 5), + getPayloadByte(IE_HEADER_SIZE + 6), + getPayloadByte(IE_HEADER_SIZE + 7) + ); + } + + public int getUpLinkBitRate() { + return upLinkBitRate; + } + + public int getDownLinkBitRate() { + return downLinkBitRate; + } + + @Override + public String toString() { + return "AggregateMaximumBitRate{" + + "upLinkBitRate=" + getUpLinkBitRate() + + ", downLinkBitRate=" + getDownLinkBitRate() + + '}'; + } +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/ApnRestriction.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/ApnRestriction.java new file mode 100644 index 0000000..afb36a6 --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/ApnRestriction.java @@ -0,0 +1,52 @@ +package com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie; + +import com.sipgate.udpproxy.udp.proxy.processors.Message; +import com.sipgate.udpproxy.udp.proxy.processors.Payload; + +public class ApnRestriction extends GenericInformationElement { + + private final Restriction restriction; + + public ApnRestriction(final Payload payload, final Message parent, final int windowOffset, final int windowSize) { + super(payload, parent, windowOffset, windowSize); + this.restriction = Restriction.fromValue(getPayloadByte(IE_HEADER_SIZE)); + } + + public Restriction getRestriction() { + return restriction; + } + + @Override + public String toString() { + return "ApnRestriction{" + + "restriction=" + getRestriction() + + '}'; + } + + public enum Restriction { + NO_RESTRICTION(0), + PUBLIC_1(1), + PUBLIC_2(2), + PRIVATE_1(3), + PRIVATE_2(4); + + private final int value; + + Restriction(final int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static Restriction fromValue(final int value) { + for (final var restriction : Restriction.values()) { + if (restriction.getValue() == value) { + return restriction; + } + } + return null; + } + } +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/BearerContext.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/BearerContext.java new file mode 100644 index 0000000..9af41b2 --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/BearerContext.java @@ -0,0 +1,23 @@ +package com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie; + +import com.sipgate.udpproxy.udp.proxy.processors.Message; +import com.sipgate.udpproxy.udp.proxy.processors.Payload; + +public class BearerContext extends GroupedIe { + + public BearerContext(final Payload payload, final Message parent, final int windowOffset, final int windowSize) { + super(payload, parent, windowOffset, windowSize); + } + + @Override + public String toString() { + return "BearerContext{" + + "informationElements=" + getInformationElements() + + '}'; + } + + @Override + public void packBytes() { + informationElements.forEach(InformationElement::packBytes); + } +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/Cause.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/Cause.java new file mode 100644 index 0000000..3fd69cd --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/Cause.java @@ -0,0 +1,161 @@ +package com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie; + +import com.sipgate.udpproxy.udp.proxy.processors.Message; +import com.sipgate.udpproxy.udp.proxy.processors.Payload; +import com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.decoder.BitHelper; + +public class Cause extends GenericInformationElement { + private final CauseType causeType; + private final boolean isCauseSource; + private final boolean isBearerContextIeError; + private final boolean isPdnConnectionIeError; + + public Cause(final Payload payload, final Message parent, final int windowOffset, final int windowSize) { + super(payload, parent, windowOffset, windowSize); + this.causeType = CauseType.fromValue(BitHelper.toInt(getPayloadByte(IE_HEADER_SIZE))); + this.isCauseSource = BitHelper.isBitSet(getPayloadByte(IE_HEADER_SIZE + 1), 1); + this.isBearerContextIeError = BitHelper.isBitSet(getPayloadByte(IE_HEADER_SIZE + 1), 2); + this.isPdnConnectionIeError = BitHelper.isBitSet(getPayloadByte(IE_HEADER_SIZE + 1), 3); + } + + public CauseType getCauseType() { + return causeType; + } + + public boolean isCauseSource() { + return isCauseSource; + } + + public boolean isBearerContextIeError() { + return this.isBearerContextIeError; + } + + public boolean isPdnConnectionIeError() { + return this.isPdnConnectionIeError; + } + + @Override + public String toString() { + return "Cause{" + + "causeType=" + getCauseType() + + ", isCauseSource=" + isCauseSource() + + ", isBearerContextIeError=" + isBearerContextIeError() + + ", isPdnConnectionIeError=" + isPdnConnectionIeError() + + '}'; + } + + public enum CauseType { + RESERVED_0(0), + RESERVED_1(1), + LOCAL_DETACH(2), + COMPLETE_DETACH(3), + RAT_CHANGED_FROM_3GPP_TO_NON_3GPP(4), + ISR_DEACTIVATION(5), + ERROR_INDICATION_RECEIVED_FROM_RNC_ENB_SGSN_OR_MME(6), + IMSI_DETACH_ONLY(7), + REACTIVATION_REQUESTED(8), + PDN_RECONNECTION_TO_THIS_APN_DISALLOWED(9), + ACCESS_CHANGED_FROM_NON_3GPP_TO_3GPP(10), + PDN_CONNECTION_INACTIVITY_TIMER_EXPIRED(11), + PGW_NOT_RESPONDING(12), + NETWORK_FAILURE(13), + QOS_PARAMETERS_REJECTED(14), + EPS_TO_5GS_MOBILITY(15), + REQUEST_ACCEPTED(16), + REQUEST_ACCEPTED_PARTIALLY(17), + NEW_PDN_TYPE_NETWORK_PREFERENCE(18), + NEW_PDN_TYPE_SINGLE_ADDRESS_BEARER_ONLY(19), + // 23 - 63 spare + CONTEXT_NOT_FOUND(64), + INVALID_MESSAGE_FORMAT(65), + VERSION_NOT_SUPPORTED_BY_NEXT_PEER(66), + INVALID_LENGTH(67), + SERVICE_NOT_SUPPORTED(68), + MANDATORY_IE_INCORRECT(69), + MANDATORY_IE_MISSING(70), + // 71: Shall not be used + SYSTEM_FAILURE(72), + NO_RESOURCES_AVAILABLE(73), + SEMANTIC_ERROR_IN_THE_TFT_OPERATION(74), + SYNTACTIC_ERROR_IN_THE_TFT_OPERATION(75), + SEMANTIC_ERRORS_IN_PACKET_FILTER(76), + SYNTACTIC_ERRORS_IN_PACKET_FILTER(77), + MISSING_OR_UNKNOWN_APN(78), + // 79: Shall not be used + GRE_KEY_NOT_FOUND(80), + RELOCATION_FAILURE(81), + DENIED_IN_RAT(82), + PREFERRED_PDN_TYPE_NOT_SUPPORTED(83), + ALL_DYNAMIC_ADDRESSES_ARE_OCCUPIED(84), + UE_CONTEXT_WITHOUT_TFT_ALREADY_ACTIVATED(85), + PROTOCOL_TYPE_NOT_SUPPORTED(86), + UE_NOT_RESPONDING(87), + UE_REFUSES(88), + SERVICE_DENIED(89), + UNABLE_TO_PAGE_UE(90), + NO_MEMORY_AVAILABLE(91), + USER_AUTHENTICATION_FAILED(92), + APN_ACCESS_DENIED_NO_SUBSCRIPTION(93), + REQUEST_REJECTED(94), + P_TMSI_SIGNATURE_MISMATCH(95), + IMSI_IMEI_NOT_KNOWN(96), + SEMANCTIC_ERROR_IN_THE_TAD_OPERATION(97), + SYNTACTIC_ERROR_IN_THE_TAD_OPERATION(98), + // 99: Shall not be used + REMOTE_PEER_NOT_RESPONDING(100), + COLLISION_WITH_NETWORK_INITIATED_REQUEST(101), + UNABLE_TO_PAGE_UE_DUE_TO_SUSPENSION(102), + CONDITIONAL_IE_MISSING(103), + APN_RESTRICTION_TYPE_INCOMPATIBLE_WITH_CURRENTLY_ACTIVE_PDN_CONNECTION(104), + INVALID_OVERALL_LENGTH_OF_THE_TRIGGERED_RESPONSE_MESSAGE(105), + DATA_FORWARDING_NOT_SUPPORTED(106), + INVALID_REPLY_FROM_REMOTE_PEER(107), + FALLBACK_TO_GTPV1(108), + INVALID_PEER(109), + TEMPORARILY_REJECTED_DUE_TO_HO_PROCEDURE(110), + MODIFICATIONS_NOT_LIMITED_TO_S1_U_BEARERS(111), + REQUEST_REJECTED_FOR_PMIPV6_REASON(112), + APN_CONGESTION(113), + BEARER_HANDLING_NOT_SUPPORTED(114), + UE_ALREADY_RE_ATTACHED(115), + MULTIPLE_PDN_CONNECTION_FOR_A_GIVEN_APN_NOT_ALLOWED(116), + TARGET_ACCESS_RESTRICTED_FOR_THE_SUBSCRIBER(117), + // 118: Shall not be used + MME_SGSN_REFUSES_DUE_TO_VPLMN_POLICY(119), + GTP_C_ENTITY_CONGESTION(120), + LATE_OVERLAPPING_REQUEST(121), + TIMED_OUT_REQUEST(122), + UE_IS_TEMPORARILY_NOT_REACHABLE_DUE_TO_POWER_SAVING(123), + RELOCATION_FAILURE_DUE_TO_NAS_MESSAGE_REDIRECTION(124), + UE_NOT_AUTHORIZED_BY_OCS_OR_EXTERNAL_AAA_SERVER(125), + MULTIPLE_ACCESS_TO_A_PDN_CONNECTION_NOT_ALLOWED(126), + REQUEST_REJECTED_DUE_TO_UE_CAPABILITY(127), + S1_U_PATH_FAILURE(128), + NOT_ALLOWED_5GC(129), + PGW_MISMATCH_WITH_NETWORK_SLICE_SUBSCRIBED_BY_THE_UE(130), + REJECTION_DUE_TO_PAGING_RESTRICTION(131), + // 132 - 255: Spare + ; + + + private final int value; + + CauseType(final int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static CauseType fromValue(final int value) { + for (final var causeType : values()) { + if (causeType.getValue() == value) { + return causeType; + } + } + return null; + } + } + +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/EpsBearerId.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/EpsBearerId.java new file mode 100644 index 0000000..8bac959 --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/EpsBearerId.java @@ -0,0 +1,26 @@ +package com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie; + +import com.sipgate.udpproxy.udp.proxy.processors.Message; +import com.sipgate.udpproxy.udp.proxy.processors.Payload; +import com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.decoder.BitHelper; + +public class EpsBearerId extends GenericInformationElement { + + private final int epsBearerId; + + public EpsBearerId(final Payload payload, final Message parent, final int windowOffset, final int windowSize) { + super(payload, parent, windowOffset, windowSize); + epsBearerId = BitHelper.toInt(getPayloadByte(IE_HEADER_SIZE), 1, 4); + } + + public int getEpsBearerId() { + return epsBearerId; + } + + @Override + public String toString() { + return "EpsBearerId{" + + "epsBearerId=" + getEpsBearerId() + + '}'; + } +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/EpsBearerLevelTrafficFlowTemplate.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/EpsBearerLevelTrafficFlowTemplate.java new file mode 100644 index 0000000..f6f6cf5 --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/EpsBearerLevelTrafficFlowTemplate.java @@ -0,0 +1,230 @@ +package com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie; + +import com.sipgate.udpproxy.udp.payload.PackableToBytes; +import com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.decoder.BitHelper; +import com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.tft.GenericPacketFilterComponent; + +import java.util.ArrayList; +import java.util.List; + +public class EpsBearerLevelTrafficFlowTemplate extends InformationElement { + public EpsBearerLevelTrafficFlowTemplate(final byte type, final byte spare, final byte instance, final byte[] payload) { + super(type, spare, instance, payload); + } + + public int getTftOperationCode() { + return BitHelper.toInt(payload[0], 6, 8); + } + + public EpsBearerLevelTrafficFlowTemplate setTftOperationCode(final int tftOperationCode) { + payload[0] = BitHelper.setThreeBitInt(tftOperationCode, 6, 8, payload[0]); + return this; + } + + public TftOperationCode getTftOperationCodeEnum() { + return TftOperationCode.fromCode(getTftOperationCode()); + } + + public int getNumberOfPacketFilters() { + return BitHelper.toInt(payload[0], 1, 4); + } + + public EpsBearerLevelTrafficFlowTemplate setNumberOfPacketFilters(final int numberOfPacketFilters) { + payload[0] = BitHelper.setLowerNibble(numberOfPacketFilters, payload[0]); + return this; + } + + public boolean isExtensionBitSet() { + return BitHelper.isBitSet(payload[0], 5); + } + + public EpsBearerLevelTrafficFlowTemplate setExtensionBit(final boolean extensionBit) { + payload[0] = BitHelper.setBitTo(payload[0], 5, extensionBit); + return this; + } + + public List getPacketFilters() { + final var packetFilters = new ArrayList(); + int offset = 1; + for (int i = 0; i < getNumberOfPacketFilters(); i++) { + final byte[] packetFilterRaw = new byte[payload[offset + 2] + 3]; + System.arraycopy(payload, offset, packetFilterRaw, 0, packetFilterRaw.length); + final var packetFilter = new PacketFilter(packetFilterRaw); + packetFilters.add(packetFilter); + offset += packetFilter.getPacketFilterLength() + 3; + } + return packetFilters; + } + + public EpsBearerLevelTrafficFlowTemplate setPacketFilters(final List packetFilters) { + final var packetFilterBytes = new ArrayList(); + for (final var packetFilter : packetFilters) { + packetFilterBytes.add(packetFilter.packBytes()); + } + final int newLength = packetFilterBytes.stream().mapToInt(b -> b.length).sum(); + final var newPayload = new byte[1 + newLength]; + newPayload[0] = payload[0]; // TFT operation code, E-Bit and number of packet filters + + int offset = 1; + for (final var packetFilter : packetFilterBytes) { + System.arraycopy(packetFilter, 0, newPayload, offset, packetFilter.length); + offset += packetFilter.length; + } + + this.payload = newPayload; + return setNumberOfPacketFilters(packetFilters.size()); // Update num of packet filters + } + + @Override + public String toString() { + return "EpsBearerLevelTrafficFlowTemplate{" + + "tftOperationCode=" + getTftOperationCodeEnum() + + ", numberOfPacketFilters=" + getNumberOfPacketFilters() + + ", packetFilters=" + getPacketFilters() + + '}'; + } + + public enum TftOperationCode { + IGNORE_THIS_IE(0), + CREATE_NEW_TFT(1), + DELETE_EXISTING_TFT(2), + ADD_PACKET_FILTER_TO_EXISTING_TFT(3), + REPLACE_PACKET_FILTERS_IN_EXISTING_TFT(4), + DELETE_PACKET_FILTERS_FROM_EXISTING_TFT(5), + NO_TFT_OPERATION(6), + RESERVED(7); + + private final int code; + + TftOperationCode(final int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public static TftOperationCode fromCode(final int code) { + for (final var tftOperationCode : values()) { + if (tftOperationCode.getCode() == code) { + return tftOperationCode; + } + } + return null; + } + } + + public static class PacketFilter implements PackableToBytes { + public static final int FILTER_DIRECTION_DOWNLINK_ONLY = 1; + public static final int FILTER_DIRECTION_UPLINK_ONLY = 2; + public static final int FILTER_DIRECTION_BIDIRECTIONAL = 3; + + private byte[] payload; + + + public PacketFilter(final byte[] payload) { + this.payload = payload; + } + + public int getPacketFilterIdentifier() { + return BitHelper.toInt(payload[0], 1, 4) + 1; + } + + public PacketFilter setPacketFilterIdentifier(int packetFilterIdentifier) { + packetFilterIdentifier--; + payload[0] = BitHelper.setBitTo(payload[0], 1, BitHelper.isBitSet((byte) packetFilterIdentifier, 1)); + payload[0] = BitHelper.setBitTo(payload[0], 2, BitHelper.isBitSet((byte) packetFilterIdentifier, 2)); + payload[0] = BitHelper.setBitTo(payload[0], 3, BitHelper.isBitSet((byte) packetFilterIdentifier, 3)); + payload[0] = BitHelper.setBitTo(payload[0], 4, BitHelper.isBitSet((byte) packetFilterIdentifier, 4)); + return this; + } + + public int getPacketFilterDirection() { + return BitHelper.toInt(payload[1], 5, 6); + } + + public PacketFilter setPacketFilterDirection(final int packetFilterDirection) { + payload[1] = BitHelper.setBitTo(payload[1], 5, BitHelper.isBitSet((byte) packetFilterDirection, 6)); + payload[1] = BitHelper.setBitTo(payload[1], 6, BitHelper.isBitSet((byte) packetFilterDirection, 5)); + return this; + } + + public int getPacketFilterEvaluationPrecedence() { + return BitHelper.toInt(payload[1]); + } + + public PacketFilter setPacketFilterEvaluationPrecedence(final int packetFilterEvaluationPrecedence) { + payload[1] = (byte) packetFilterEvaluationPrecedence; + return this; + } + + public int getPacketFilterLength() { + return BitHelper.toInt(payload[2]); + } + + public PacketFilter setPacketFilterLength(final int packetFilterLength) { + payload[2] = (byte) packetFilterLength; + return this; + } + + public List getPacketFilterComponents() { + final var components = new ArrayList(); + int offset = 3; + while (offset < payload.length) { + final var componentType = GenericPacketFilterComponent.PacketFilterComponentType.fromCode(payload[offset]); + if (componentType == null) { + throw new IllegalArgumentException("Unknown packet filter component type: " + payload[offset]); + } + final var componentLength = componentType.getValueLength() + 1; + final var componentPayload = new byte[componentLength]; + System.arraycopy(payload, offset, componentPayload, 0, componentLength); + + System.arraycopy(payload, offset, componentPayload, 0, componentLength); + final var component = GenericPacketFilterComponent.fromBytes(componentPayload); + components.add(component); + offset += componentLength; + } + + return components; + } + + public PacketFilter setPacketFilterComponents(List components) { + final var componentBytes = new ArrayList(); + for (final var component : components) { + componentBytes.add(component.packBytes()); + } + final int newLength = componentBytes.stream().mapToInt(b -> b.length).sum(); + final var newPayload = new byte[3 + newLength]; + newPayload[0] = payload[0]; + newPayload[1] = payload[1]; + newPayload[2] = (byte) newLength; + + int offset = 3; + for (final var component : componentBytes) { + System.arraycopy(component, 0, newPayload, offset, component.length); + offset += component.length; + } + + this.payload = newPayload; + return this; + } + + @Override + public byte[] packBytes() { + return this.payload; + } + + @Override + public String toString() { + return "PacketFilter{" + + "packetFilterIdentifier=" + getPacketFilterIdentifier() + + ", packetFilterDirection=" + getPacketFilterDirection() + + ", packetFilterEvaluationPrecedence=" + getPacketFilterEvaluationPrecedence() + + ", packetFilterLength=" + getPacketFilterLength() + + ", packetFilterComponents=" + getPacketFilterComponents() + + '}'; + } + + } + +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/FullyQualifiedTunnelEndpointIdentifier.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/FullyQualifiedTunnelEndpointIdentifier.java new file mode 100644 index 0000000..e6c5777 --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/FullyQualifiedTunnelEndpointIdentifier.java @@ -0,0 +1,147 @@ +package com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie; + +import com.sipgate.udpproxy.udp.proxy.processors.Message; +import com.sipgate.udpproxy.udp.proxy.processors.Payload; +import com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.decoder.BitHelper; +import com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.decoder.IpV4V6; + +import static com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.decoder.BitHelper.isBitSet; + +public class FullyQualifiedTunnelEndpointIdentifier extends GenericInformationElement { + + private final boolean isIPv4Present; + private final boolean isIPv6Present; + private final InterfaceType interfaceType; + private final int teidGreKey; + private String ipV4Address; + private String ipV6Address; + + + public FullyQualifiedTunnelEndpointIdentifier(final Payload payload, final Message parent, final int windowOffset, final int windowSize) { + super(payload, parent, windowOffset, windowSize); + isIPv4Present = getPayloadLength() >= IE_HEADER_SIZE + 1 && isBitSet(getPayloadByte(IE_HEADER_SIZE), 8); + isIPv6Present = getPayloadLength() >= IE_HEADER_SIZE + 1 && isBitSet(getPayloadByte(IE_HEADER_SIZE), 7); + interfaceType = InterfaceType.fromValue(BitHelper.toInt(getPayloadByte(IE_HEADER_SIZE), 1, 6)); + teidGreKey = BitHelper.toInt32( + getPayloadByte(IE_HEADER_SIZE + 1), + getPayloadByte(IE_HEADER_SIZE + 2), + getPayloadByte(IE_HEADER_SIZE + 3), + getPayloadByte(IE_HEADER_SIZE + 4) + ); + + if (isIPv4Present) { + final byte[] ipv4 = new byte[4]; + System.arraycopy(getCopyOfBytes(IE_HEADER_SIZE + 5), 0, ipv4, 0, 4); + ipV4Address = IpV4V6.decodeV4(ipv4); + } + + // TODO: Check offset. Is it really +5? + if (isIPv6Present) { + final byte[] ipv6 = new byte[16]; + System.arraycopy(getCopyOfBytes(IE_HEADER_SIZE + 5), 0, ipv6, 0, 16); + ipV6Address = IpV4V6.decodeV6(ipv6); + } + + } + + public boolean isIpv4Present() { + return isIPv4Present; + } + + public boolean isIpv6Present() { + return isIPv6Present; + } + + public InterfaceType getInterfaceType() { + return interfaceType; + } + + public int getTeidGreKey() { + return teidGreKey; + } + + public String getIpv4Address() { + return ipV4Address; + } + + public String getIpv6Address() { + return ipV6Address; + } + + @Override + public String toString() { + return "FullyQualifiedTunnelEndpointIdentifier{" + + "ipv4Present=" + isIpv4Present() + + ", ipv6Present=" + isIpv6Present() + + ", interfaceType=" + getInterfaceType() + + ", teidGreKey=" + getTeidGreKey() + + ", ipv4Address='" + getIpv4Address() + '\'' + + ", ipv6Address='" + getIpv6Address() + '\'' + + '}'; + } + + public enum InterfaceType { + S1_U_ENODEB_GTP_U(0), + S1_U_SGW_GTP_U(1), + S12_RNC_GTP_U(2), + S12_SGW_GTP_U(3), + S5_S8_SGW_GTP_U(4), + S5_S8_PGW_GTP_U(5), + S5_S8_SGW_GTP_C(6), + S5_S8_PGW_GTP_C(7), + S5_S8_SGW_PMIPV6(8), + S5_S8_PGW_PMIPV6_ALT(9), + S11_MME_GTP_C(10), + S11_S4_SGW_GTP_C(11), + S10_N26_MME_GTP_C(12), + S3_MME_GTP_C(13), + S3_SGSN_GTP_C(14), + S4_SGSN_GTP_U(15), + S4_SGW_GTP_U(16), + S4_SGSN_GTP_C(17), + S16_SGSN_GTP_C(18), + ENODEB_GNODEB_GTP_U_FOR_DL_FORWARDING(19), + ENODEB_GNODEB_GTP_U_FOR_UL_FORWARDING(20), + RNC_GTP_U_FOR_DATA_FORWARDING(21), + SGSN_GTP_U_FOR_DATA_FORWARDING(22), + SGW_UPF_GTP_U_FOR_DL_FORWARDING(23), + SM_MBMS_GW_GTP_C(24), + SN_MBMS_GW_GTP_C(25), + SM_MME_GTP_C(26), + SN_SGSN_GTP_C(27), + SGW_GTPU_FOR_UL_FORWARDING(28), + SN_SGSN_GTP_U(29), + S2B_EPDG_GTP_C(30), + S2B_U_EPDG_GTP_U(31), + S2B_PGW_GTP_C(32), + S2B_U_PGW_GTP_U(33), + S2A_TWAN_GTP_U(34), + S2A_TWAN_GTP_C(35), + S2A_PGW_GTP_C(36), + S2A_PGW_GTP_U(37), + S11_MME_GTP_U(38), + S11_SGW_GTP_U(39), + N26_AMF_GTP_C(40), + N19MB_UPF_GTP_U(41), + ; + + private final int value; + + InterfaceType(final int value) { + this.value = value; + } + + public static InterfaceType fromValue(final int value) { + for (final InterfaceType interfaceType : InterfaceType.values()) { + if (interfaceType.value == value) { + return interfaceType; + } + } + return null; + } + + public int getValue() { + return value; + } + } +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/GenericInformationElement.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/GenericInformationElement.java new file mode 100644 index 0000000..7784247 --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/GenericInformationElement.java @@ -0,0 +1,28 @@ +package com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie; + +import com.sipgate.udpproxy.udp.proxy.processors.Message; +import com.sipgate.udpproxy.udp.proxy.processors.Payload; + +public class GenericInformationElement extends InformationElement { + public GenericInformationElement(final Payload payload, final Message parent, final int windowOffset, final int windowSize) { + super(payload, parent, windowOffset, windowSize); + } + + @Override + public void packBytes() { + // Stub! Generic implementation has no properties to modify. + } + + @Override + public String toString() { + final var payloadSb = new StringBuilder(); + for (int i = IE_HEADER_SIZE; i < getPayloadLength(); i++) { + payloadSb.append(String.format("0x%s ", Integer.toHexString(getPayloadByte(i) & 0xff))); + } + return "InformationElement{" + + "type=" + getType() + + ", length=" + getLength() + + ", payload=" + payloadSb.toString().trim() + + '}'; + } +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/GroupedIe.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/GroupedIe.java new file mode 100644 index 0000000..e645055 --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/GroupedIe.java @@ -0,0 +1,31 @@ +package com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie; + +import com.sipgate.udpproxy.udp.proxy.processors.Message; +import com.sipgate.udpproxy.udp.proxy.processors.Payload; +import com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.decoder.BitHelper; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public abstract class GroupedIe extends InformationElement { + + protected final List informationElements = new ArrayList<>(); + + protected GroupedIe(final Payload payload, final Message parent, final int windowOffset, final int windowSize) { + super(payload, parent, windowOffset, windowSize); + + var currentOffset = 0; + while (currentOffset < getPayloadLength()) { + final int type = getPayloadByte(0); + final short length = (short) BitHelper.int16ToInt32(getPayloadByte(1), getPayloadByte(2)); + currentOffset += length + IE_HEADER_SIZE; + InformationElement.fromPayload(type, payload, this, currentOffset, length + IE_HEADER_SIZE); + } + + } + + public List getInformationElements() { + return Collections.unmodifiableList(informationElements); + } +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/Imsi.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/Imsi.java new file mode 100644 index 0000000..949bece --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/Imsi.java @@ -0,0 +1,26 @@ +package com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie; + +import com.sipgate.udpproxy.udp.proxy.processors.Message; +import com.sipgate.udpproxy.udp.proxy.processors.Payload; +import com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.decoder.Tbcd; + +public class Imsi extends GenericInformationElement { + + private final String imsi; + + public Imsi(final Payload payload, final Message parent, final int windowOffset, final int windowSize) { + super(payload, parent, windowOffset, windowSize); + this.imsi = Tbcd.decode(getCopyOfBytes(IE_HEADER_SIZE)); + } + + @Override + public String toString() { + return "Imsi{" + + "imsi=" + getImsi() + + '}'; + } + + public String getImsi() { + return imsi; + } +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/Indication.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/Indication.java new file mode 100644 index 0000000..a2e723e --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/Indication.java @@ -0,0 +1,326 @@ +package com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie; + +import com.sipgate.udpproxy.udp.proxy.processors.Message; +import com.sipgate.udpproxy.udp.proxy.processors.Payload; + +import static com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.decoder.BitHelper.isBitSet; + +public class Indication extends GenericInformationElement { + + public Indication(final Payload payload, final Message parent, final int windowOffset, final int windowSize) { + super(payload, parent, windowOffset, windowSize); + } + + public boolean isDaf() { + return getPayloadLength() >= IE_HEADER_SIZE + 1 && isBitSet(getPayloadByte(IE_HEADER_SIZE), 8); + } + + public boolean isDtf() { + return getPayloadLength() >= IE_HEADER_SIZE + 1 && isBitSet(getPayloadByte(IE_HEADER_SIZE), 7); + } + + public boolean isHi() { + return getPayloadLength() >= IE_HEADER_SIZE + 1 && isBitSet(getPayloadByte(IE_HEADER_SIZE), 6); + } + + public boolean isDfi() { + return getPayloadLength() >= IE_HEADER_SIZE + 1 && isBitSet(getPayloadByte(IE_HEADER_SIZE), 5); + } + + public boolean isOi() { + return getPayloadLength() >= IE_HEADER_SIZE + 1 && isBitSet(getPayloadByte(IE_HEADER_SIZE), 4); + } + + public boolean isIsrsi() { + return getPayloadLength() >= IE_HEADER_SIZE + 1 && isBitSet(getPayloadByte(IE_HEADER_SIZE), 3); + } + + public boolean isIrai() { + return getPayloadLength() >= IE_HEADER_SIZE + 1 && isBitSet(getPayloadByte(IE_HEADER_SIZE), 2); + } + + public boolean isSgwci() { + return getPayloadLength() >= IE_HEADER_SIZE + 1 && isBitSet(getPayloadByte(IE_HEADER_SIZE), 1); + } + + public boolean isSqci() { + return getPayloadLength() >= 2 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 1), 8); + } + + public boolean isUimsi() { + return getPayloadLength() >= 2 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 1), 7); + } + + public boolean isCfsi() { + return getPayloadLength() >= 2 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 1), 6); + } + + public boolean isCrsi() { + return getPayloadLength() >= 2 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 1), 5); + } + + public boolean isP() { + return getPayloadLength() >= 2 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 1), 4); + } + + public boolean isPt() { + return getPayloadLength() >= 2 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 1), 3); + } + + public boolean isSi() { + return getPayloadLength() >= 2 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 1), 2); + } + + public boolean isMsv() { + return getPayloadLength() >= 2 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 1), 1); + } + + public boolean isRetLoc() { + return getPayloadLength() >= 3 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 2), 8); + } + + public boolean isPbic() { + return getPayloadLength() >= 3 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 2), 7); + } + + public boolean isSrni() { + return getPayloadLength() >= 3 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 2), 6); + } + + public boolean isS6af() { + return getPayloadLength() >= 3 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 2), 5); + } + + public boolean isS4af() { + return getPayloadLength() >= 3 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 2), 4); + } + + public boolean isMbmdt() { + return getPayloadLength() >= 3 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 2), 3); + } + + public boolean isIsrau() { + return getPayloadLength() >= 3 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 2), 2); + } + + public boolean isCcrsi() { + return getPayloadLength() >= 3 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 2), 1); + } + + public boolean isCprai() { + return getPayloadLength() >= 4 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 3), 8); + } + + public boolean isArrl() { + return getPayloadLength() >= 4 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 3), 7); + } + + public boolean isPpof() { + return getPayloadLength() >= 4 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 3), 6); + } + + public boolean isPponPpei() { + return getPayloadLength() >= 4 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 3), 5); + } + + public boolean isPpsi() { + return getPayloadLength() >= 4 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 3), 4); + } + + public boolean isCsfbi() { + return getPayloadLength() >= 4 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 3), 3); + } + + public boolean isClii() { + return getPayloadLength() >= 4 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 3), 2); + } + + public boolean isCpsr() { + return getPayloadLength() >= 4 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 3), 1); + } + + public boolean isNsi() { + return getPayloadLength() >= 5 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 4), 8); + } + + public boolean isUasi() { + return getPayloadLength() >= 5 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 4), 7); + } + + public boolean isDtci() { + return getPayloadLength() >= 5 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 4), 6); + } + + public boolean isBdwi() { + return getPayloadLength() >= 5 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 4), 5); + } + + public boolean isPsci() { + return getPayloadLength() >= 5 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 4), 4); + } + + public boolean isPcri() { + return getPayloadLength() >= 5 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 4), 3); + } + + public boolean isAosi() { + return getPayloadLength() >= 5 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 4), 2); + } + + public boolean isAopi() { + return getPayloadLength() >= 5 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 4), 1); + } + + public boolean isRoaai() { + return getPayloadLength() >= 6 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 5), 8); + } + + public boolean isEpcosi() { + return getPayloadLength() >= 6 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 5), 7); + } + + public boolean isCpopci() { + return getPayloadLength() >= 6 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 5), 6); + } + + public boolean isPmtsmi() { + return getPayloadLength() >= 6 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 5), 5); + } + + public boolean isS11tf() { + return getPayloadLength() >= 6 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 5), 4); + } + + public boolean isPnsi() { + return getPayloadLength() >= 6 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 5), 3); + } + + public boolean isUnaccsi() { + return getPayloadLength() >= 6 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 5), 2); + } + + public boolean isWpmsi() { + return getPayloadLength() >= 6 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 5), 1); + } + + public boolean is5gsnn26() { + return getPayloadLength() >= 7 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 6), 8); + } + + public boolean isReprefi() { + return getPayloadLength() >= 7 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 6), 7); + } + + public boolean is5gsiwk() { + return getPayloadLength() >= 7 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 6), 6); + } + + public boolean isEevrsi() { + return getPayloadLength() >= 7 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 6), 5); + } + + public boolean isLtemui() { + return getPayloadLength() >= 7 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 6), 4); + } + + public boolean isLtempi() { + return getPayloadLength() >= 7 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 6), 3); + } + + public boolean isEnbcrsi() { + return getPayloadLength() >= 7 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 6), 2); + } + + public boolean isTspcmi() { + return getPayloadLength() >= 7 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 6), 1); + } + + public boolean isCsrmfi() { + return getPayloadLength() >= 8 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 7), 8); + } + + public boolean isMtedtn() { + return getPayloadLength() >= 8 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 7), 7); + } + + public boolean isMtedta() { + return getPayloadLength() >= 8 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 7), 6); + } + + public boolean isN5gnmi() { + return getPayloadLength() >= 8 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 7), 5); + } + + public boolean is5gcnrs() { + return getPayloadLength() >= 8 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 7), 4); + } + + public boolean is5gcnri() { + return getPayloadLength() >= 8 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 7), 3); + } + + public boolean is5srhoi() { + return getPayloadLength() >= 8 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 7), 2); + } + + public boolean isEthpdn() { + return getPayloadLength() >= 8 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 7), 1); + } + + public boolean isNspusi() { + return getPayloadLength() >= 9 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 8), 8); + } + + public boolean isPgwrnsi() { + return getPayloadLength() >= 9 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 8), 7); + } + + public boolean isRppcsi() { + return getPayloadLength() >= 9 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 8), 6); + } + + public boolean isPgwchi() { + return getPayloadLength() >= 9 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 8), 5); + } + + public boolean isSissme() { + return getPayloadLength() >= 9 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 8), 4); + } + + public boolean isNsenbi() { + return getPayloadLength() >= 9 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 8), 3); + } + + public boolean isIdfupf() { + return getPayloadLength() >= 9 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 8), 2); + } + + public boolean isEmci() { + return getPayloadLength() >= 9 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 8), 1); + } + + public boolean isLtemsai() { + return getPayloadLength() >= IE_HEADER_SIZE + 10 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 9), 3); + } + + public boolean isSrtpi() { + return getPayloadLength() >= IE_HEADER_SIZE + 10 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 9), 2); + } + + public boolean isUpipsi() { + return getPayloadLength() >= IE_HEADER_SIZE + 10 && isBitSet(getPayloadByte(IE_HEADER_SIZE + 9), 1); + } + + @Override + public String toString() { + final var payloadSb = new StringBuilder(); + for (final var b : getCopyOfBytes(IE_HEADER_SIZE)) { + payloadSb.append(String.format("0x%s ", Integer.toHexString(b & 0xff))); + } + return "Indication{" + + "type=" + this.getType() + + ", length=" + getLength() + + ", payload=" + payloadSb.toString().trim() + + '}'; + } +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/InformationElement.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/InformationElement.java new file mode 100644 index 0000000..d59f175 --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/InformationElement.java @@ -0,0 +1,70 @@ +package com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie; + +import com.sipgate.udpproxy.udp.proxy.processors.Payload; +import com.sipgate.udpproxy.udp.proxy.processors.Message; +import com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.decoder.BitHelper; + +public abstract class InformationElement extends Message { + + protected static final int IE_HEADER_SIZE = 4; + + private final int type; + private short length; + + protected InformationElement(final Payload payload, final Message parent, final int windowOffset, final int windowSize) { + super(payload, parent, windowOffset, windowSize); + type = getPayloadByte(0); + length = (short) BitHelper.int16ToInt32(getPayloadByte(1), getPayloadByte(2)); + if (getPayloadLength() != length + IE_HEADER_SIZE) { + throw new IllegalArgumentException("Length field of IE is invalid! Indicated length: " + length + ", actual length: " + (getPayloadLength() - IE_HEADER_SIZE)); + } + } + + public int getType() { + return type; + } + + public short getLength() { + return length; + } + + public void setLength(final short length) { + this.length = length; + } + + protected byte[] packHeaderWithEmptyPayload(final int payloadLength) { + final byte[] bytes = new byte[IE_HEADER_SIZE + payloadLength]; + bytes[0] = (byte) type; + bytes[1] = (byte) ((payloadLength >> 8) & 0xff); + bytes[2] = (byte) (payloadLength & 0xff); + bytes[3] = getPayloadByte(3); + + return bytes; + } + + public static InformationElement fromPayload(final int type, final Payload payload, final Message parent, final int windowOffset, final int windowSize) { + return switch (type) { + case 0x01 -> new Imsi(payload, parent, windowOffset, windowSize); + case 0x02 -> new Cause(payload, parent, windowOffset, windowSize); + case 0x03 -> new RestartCounter(payload, parent, windowOffset, windowSize); + case 0x47 -> new AccessPointName(payload, parent, windowOffset, windowSize); + case 0x48 -> new AggregateMaximumBitRate(payload, parent, windowOffset, windowSize); + case 0x49 -> new EpsBearerId(payload, parent, windowOffset, windowSize); + case 0x4b -> new MobileEquipmentIdentity(payload, parent, windowOffset, windowSize); + case 0x4c -> new Msisdn(payload, parent, windowOffset, windowSize); + case 0x4d -> new Indication(payload, parent, windowOffset, windowSize); + case 0x4f -> new PdnAddressAllocation(payload, parent, windowOffset, windowSize); + case 0x52 -> new RatType(payload, parent, windowOffset, windowSize); + case 0x53 -> new ServingNetwork(payload, parent, windowOffset, windowSize); + case 0x54 -> new EpsBearerLevelTrafficFlowTemplate(payload, parent, windowOffset, windowSize); + case 0x56 -> new UserLocationInformation(payload, parent, windowOffset, windowSize); + case 0x57 -> new FullyQualifiedTunnelEndpointIdentifier(payload, parent, windowOffset, windowSize); + case 0x5d -> new BearerContext(payload, parent, windowOffset, windowSize); + case 0x63 -> new PdnType(payload, parent, windowOffset, windowSize); + case 0x7f -> new ApnRestriction(payload, parent, windowOffset, windowSize); + case (byte) 0x80 -> new SelectionMode(payload, parent, windowOffset, windowSize); + default -> new GenericInformationElement(payload, parent, windowOffset, windowSize); + }; + } + +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/MobileEquipmentIdentity.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/MobileEquipmentIdentity.java new file mode 100644 index 0000000..ec4f2b2 --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/MobileEquipmentIdentity.java @@ -0,0 +1,27 @@ +package com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie; + +import com.sipgate.udpproxy.udp.proxy.processors.Message; +import com.sipgate.udpproxy.udp.proxy.processors.Payload; +import com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.decoder.Tbcd; + +public class MobileEquipmentIdentity extends GenericInformationElement { + + private final String mei; + + public MobileEquipmentIdentity(final Payload payload, final Message parent, final int windowOffset, final int windowSize) { + super(payload, parent, windowOffset, windowSize); + this.mei = Tbcd.decode(getCopyOfBytes(IE_HEADER_SIZE)); + } + + @Override + public String toString() { + return "MobileEquipmentIdentity{" + + "mei=" + getMei() + + '}'; + } + + public String getMei() { + return mei; + } + +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/Msisdn.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/Msisdn.java new file mode 100644 index 0000000..1761e01 --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/Msisdn.java @@ -0,0 +1,25 @@ +package com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie; + +import com.sipgate.udpproxy.udp.proxy.processors.Message; +import com.sipgate.udpproxy.udp.proxy.processors.Payload; +import com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.decoder.Tbcd; + +public class Msisdn extends GenericInformationElement { + private final String msidn; + + public Msisdn(final Payload payload, final Message parent, final int windowOffset, final int windowSize) { + super(payload, parent, windowOffset, windowSize); + this.msidn = Tbcd.decode(getCopyOfBytes(IE_HEADER_SIZE)); + } + + @Override + public String toString() { + return "Msidn{" + + "msidn=" + getMsidn() + + '}'; + } + + public String getMsidn() { + return msidn; + } +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/PdnAddressAllocation.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/PdnAddressAllocation.java new file mode 100644 index 0000000..de2cd59 --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/PdnAddressAllocation.java @@ -0,0 +1,49 @@ +package com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie; + +import com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.decoder.IpV4V6; + +public class PdnAddressAllocation extends PdnType { + public PdnAddressAllocation(final byte type, final byte spare, final byte instance, final byte[] payload) { + super(type, spare, instance, payload); + } + + + public String getIpv4Address() { + final Type pdnType = getPdnTypeValue(); + if (!Type.IP_V4.equals(pdnType) && !Type.IP_V4_V6.equals(pdnType)) { + return null; + } + + final byte[] ipv4 = new byte[4]; + System.arraycopy(this.payload, pdnType.equals(Type.IP_V4_V6) ? 17 : 1, ipv4, 0, 4); + return IpV4V6.decodeV4(ipv4); + } + + public String getIpv6PrefixLength() { + if (!Type.IP_V6.equals(getPdnTypeValue()) && !Type.IP_V4_V6.equals(getPdnTypeValue())) { + return null; + } + + return String.format("%d", payload[1]); + } + + public String getIpv6Address() { + if (!Type.IP_V6.equals(getPdnTypeValue()) && !Type.IP_V4_V6.equals(getPdnTypeValue())) { + return null; + } + + final byte[] ipv6 = new byte[16]; + System.arraycopy(this.payload, 2, ipv6, 0, 16); + return IpV4V6.decodeV6(ipv6); + } + + @Override + public String toString() { + return "PdnAddressAllocation{" + + "pdnType=" + getPdnTypeValue() + + ", ipv4Address=" + getIpv4Address() + + ", ipv6PrefixLength=" + getIpv6PrefixLength() + + ", ipv6Address=" + getIpv6Address() + + '}'; + } +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/PdnType.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/PdnType.java new file mode 100644 index 0000000..365247d --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/PdnType.java @@ -0,0 +1,51 @@ +package com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie; + +import com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.decoder.BitHelper; + +public class PdnType extends InformationElement { + public PdnType(final byte type, final byte spare, final byte instance, final byte[] payload) { + super(type, spare, instance, payload); + } + + public int getPdnType() { + return BitHelper.toInt(payload[0], 1, 3); + } + + public Type getPdnTypeValue() { + return Type.fromValue(BitHelper.toInt(payload[0], 1, 3)); + } + + @Override + public String toString() { + return "PdnType{" + + "pdnType=" + getPdnTypeValue() + + '}'; + } + + public enum Type { + IP_V4(1), + IP_V6(2), + IP_V4_V6(3), + NON_IP(4), + ETHERNET(5); + + private final int value; + + Type(final int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static Type fromValue(final int value) { + for (final var type : Type.values()) { + if (type.getValue() == value) { + return type; + } + } + return null; + } + } +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/RatType.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/RatType.java new file mode 100644 index 0000000..07e056f --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/RatType.java @@ -0,0 +1,71 @@ +package com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie; + +import com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.decoder.BitHelper; + +public class RatType extends InformationElement { + + public RatType(final byte type, final byte spare, final byte instance, final byte[] payload) { + super(type, spare, instance, payload); + } + + @Override + public String toString() { + return "RatType{" + + "ratType=" + getRatTypeValue() + + '}'; + } + + public int getRatType() { + return BitHelper.toInt(payload[0]); + } + + public RatTypeValue getRatTypeValue() { + return RatTypeValue.fromValue(getRatType()); + } + + public enum RatTypeValue { + UTRAN(1), + GERAN(2), + WLAN(3), + GAN(4), + HSPA_EVOLUTION(5), + EUTRAN(6), + VIRTUAL(7), + EUTRAN_NB_IOT(8), + LTE_M(9), + NR(10), + WB_E_UTRAN_LEO(11), + WB_E_UTRAN_MEO(12), + WB_E_UTRAN_GEO(13), + WB_E_UTRAN_OTHERSAT(14), + EUTRAN_NB_IOT_LEO(15), + EUTRAN_NB_IOT_MEO(16), + EUTRAN_NB_IOT_GEO(17), + EUTRAN_NB_IOT_OTHERSAT(18), + LTE_M_LEO(19), + LTE_M_MEO(20), + LTE_M_GEO(21), + LTE_M_OTHERSAT(22), + // 23 - 255: Spare + ; + + private final int value; + + RatTypeValue(final int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static RatTypeValue fromValue(final int value) { + for (final RatTypeValue ratTypeValue : RatTypeValue.values()) { + if (ratTypeValue.value == value) { + return ratTypeValue; + } + } + return null; + } + } +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/RestartCounter.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/RestartCounter.java new file mode 100644 index 0000000..804cb1a --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/RestartCounter.java @@ -0,0 +1,26 @@ +package com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie; + +import com.sipgate.udpproxy.udp.proxy.processors.Message; +import com.sipgate.udpproxy.udp.proxy.processors.Payload; +import com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.decoder.BitHelper; + +public class RestartCounter extends GenericInformationElement { + + private final int restartCounter; + + public RestartCounter(final Payload payload, final Message parent, final int windowOffset, final int windowSize) { + super(payload, parent, windowOffset, windowSize); + this.restartCounter = BitHelper.toInt(getPayloadByte(0)); + } + + @Override + public String toString() { + return "RestartCounter{" + + "counter=" + getRestartCounter() + + '}'; + } + + public int getRestartCounter() { + return restartCounter; + } +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/SelectionMode.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/SelectionMode.java new file mode 100644 index 0000000..af77b36 --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/SelectionMode.java @@ -0,0 +1,51 @@ +package com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie; + +import com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.decoder.BitHelper; + +public class SelectionMode extends InformationElement { + public SelectionMode(final byte type, final byte spare, final byte instance, final byte[] payload) { + super(type, spare, instance, payload); + } + + public int getMode() { + return BitHelper.toInt(payload[0], 1, 2); + } + + public Mode getModeValue() { + return Mode.fromValue(BitHelper.toInt(payload[0], 1, 2)); + } + + @Override + public String toString() { + return "SelectionMode{" + + "mode=" + getModeValue() + + '}'; + } + + public enum Mode { + MS_OR_NETWORK_PROVIDED_APN_SUBSCRIPTION_VERIFIED(0), + MS_PROVIDED_APN_SUBSCRIPTION_NOT_VERIFIED(1), + NETWORK_PROVIDED_APN_SUBSCRIPTION_NOT_VERIFIED(2), + RESERVED(3), + ; + + private final int value; + + Mode(final int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static Mode fromValue(final int value) { + for (final var mode : Mode.values()) { + if (mode.getValue() == value) { + return mode; + } + } + return null; + } + } +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/ServingNetwork.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/ServingNetwork.java new file mode 100644 index 0000000..3af1d66 --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/ServingNetwork.java @@ -0,0 +1,33 @@ +package com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie; + +import com.sipgate.udpproxy.udp.proxy.processors.Message; +import com.sipgate.udpproxy.udp.proxy.processors.Payload; +import com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.decoder.MncMcc; + +public class ServingNetwork extends GenericInformationElement { + + private final int mcc; + private final int mnc; + + public ServingNetwork(final Payload payload, final Message parent, final int windowOffset, final int windowSize) { + super(payload, parent, windowOffset, windowSize); + this.mcc = MncMcc.decodeMcc(getPayloadByte(IE_HEADER_SIZE), getPayloadByte(IE_HEADER_SIZE + 1)); + this.mnc = MncMcc.decodeMnc(getPayloadByte(IE_HEADER_SIZE + 1), getPayloadByte(IE_HEADER_SIZE + 2)); + } + + private int getMcc() { + return mcc; + } + + private int getMnc() { + return mnc; + } + + @Override + public String toString() { + return "ServingNetwork{" + + "mcc=" + getMcc() + + ", mnc=" + getMnc() + + '}'; + } +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/UserLocationInformation.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/UserLocationInformation.java new file mode 100644 index 0000000..dc45717 --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/UserLocationInformation.java @@ -0,0 +1,554 @@ +package com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie; + +import com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.decoder.BitHelper; +import com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.decoder.MncMcc; + +import java.util.Optional; + +/** + * User Location Information + *

+ * + * + * See ETSI TS 129 274 V17.8.0 (2023-04) - Section 8.21 (Page 299) + * + * + * @author Lennart Rosam + */ +public class UserLocationInformation extends InformationElement { + + private final UliFlags flags; + + // Ordered the way it should be parsed according to the flags if more than one identity is present at the same time + private Cgi cgi; + private Sai sai; + private Rai rai; + private Tai tai; + private Ecgi ecgi; + private Lai lai; + private MacroEnbId macroEnbId; + private ExtendedMacroEnbId extendedMacroEnbId; + private byte[] payload; + + UserLocationInformation(final byte type, final byte spare, final byte instance, final byte[] payload) { + super(type, spare, instance, payload); + flags = new UliFlags(payload[0]); + this.payload = payload; + + if (flags.isExtendedMacroEnbIdPresent() && flags.isMacroEnbIdPresent()) { + throw new IllegalArgumentException("MacroEnbId and ExtendedMacroEnbId are mutually exclusive!"); + } + + int currentOffset = 1; + final UliFlags flagsToParse = new UliFlags(payload[0]); + do { + if (flagsToParse.isCgi()) { + final byte[] cgiBytes = new byte[7]; + System.arraycopy(payload, currentOffset, cgiBytes, 0, cgiBytes.length); + cgi = new Cgi(cgiBytes); + currentOffset += cgiBytes.length; + flagsToParse.setCgi(false); + continue; + } + + if (flagsToParse.isSai()) { + final byte[] saiBytes = new byte[7]; + System.arraycopy(payload, currentOffset, saiBytes, 0, saiBytes.length); + sai = new Sai(saiBytes); + currentOffset += saiBytes.length; + flagsToParse.setSai(false); + continue; + } + + if (flagsToParse.isRai()) { + final byte[] raiBytes = new byte[7]; + System.arraycopy(payload, currentOffset, raiBytes, 0, raiBytes.length); + rai = new Rai(raiBytes); + currentOffset += raiBytes.length; + flagsToParse.setRai(false); + continue; + } + + if (flagsToParse.isTai()) { + final byte[] taiBytes = new byte[5]; + System.arraycopy(payload, currentOffset, taiBytes, 0, taiBytes.length); + tai = new Tai(taiBytes); + currentOffset += taiBytes.length; + flagsToParse.setTai(false); + continue; + } + + if (flagsToParse.isEcgi()) { + final byte[] ecgiBytes = new byte[7]; + System.arraycopy(payload, currentOffset, ecgiBytes, 0, ecgiBytes.length); + ecgi = new Ecgi(ecgiBytes); + currentOffset += ecgiBytes.length; + flagsToParse.setEcgi(false); + continue; + } + + if (flagsToParse.isLai()) { + final byte[] laiBytes = new byte[5]; + System.arraycopy(payload, currentOffset, laiBytes, 0, laiBytes.length); + lai = new Lai(laiBytes); + currentOffset += laiBytes.length; + flagsToParse.setLai(false); + continue; + } + + if (flagsToParse.isMacroEnbIdPresent()) { + final byte[] macroEnbIdBytes = new byte[6]; + System.arraycopy(payload, currentOffset, macroEnbIdBytes, 0, macroEnbIdBytes.length); + macroEnbId = new MacroEnbId(macroEnbIdBytes); + currentOffset += macroEnbIdBytes.length; + flagsToParse.setMacroEnbIdPresent(false); + continue; + } + + if (flagsToParse.isExtendedMacroEnbIdPresent()) { + final byte[] extendedMacroEnbIdBytes = new byte[6]; + System.arraycopy(payload, currentOffset, extendedMacroEnbIdBytes, 0, extendedMacroEnbIdBytes.length); + extendedMacroEnbId = new ExtendedMacroEnbId(extendedMacroEnbIdBytes); + currentOffset += extendedMacroEnbIdBytes.length; + flagsToParse.setExtendedMacroEnbId(false); + } + + } while (currentOffset < payload.length); + + } + + public UliFlags getFlags() { + return flags; + } + + public Optional getCgi() { + return Optional.ofNullable(cgi); + } + + public Optional getSai() { + return Optional.ofNullable(sai); + } + + public Optional getRai() { + return Optional.ofNullable(rai); + } + + public Optional getTai() { + return Optional.ofNullable(tai); + } + + public Optional getEcgi() { + return Optional.ofNullable(ecgi); + } + + public Optional getLai() { + return Optional.ofNullable(lai); + } + + public Optional getMacroEnbId() { + return Optional.ofNullable(macroEnbId); + } + + public Optional getExtendedMacroEnbId() { + return Optional.ofNullable(extendedMacroEnbId); + } + + public byte[] getPayload() { + return payload; + } + + @Override + public String toString() { + return "UserLocationInformation{" + + "flags=" + flags + + ", cgi=" + cgi + + ", sai=" + sai + + ", rai=" + rai + + ", tai=" + tai + + ", ecgi=" + ecgi + + ", lai=" + lai + + ", macroEnbId=" + macroEnbId + + ", extendedMacroEnbId=" + extendedMacroEnbId + + '}'; + } + + /** + * User Location Information Flags + */ + public static class UliFlags { + + private byte flags; + + UliFlags(final byte flags) { + this.flags = flags; + } + + + public boolean isExtendedMacroEnbIdPresent() { + return BitHelper.isBitSet(flags, 8); + } + + public UliFlags setExtendedMacroEnbId(final boolean extendedMacroEnbId) { + flags = BitHelper.setBitTo(flags, 8, extendedMacroEnbId); + return this; + } + + public boolean isMacroEnbIdPresent() { + return BitHelper.isBitSet(flags, 7); + } + + public UliFlags setMacroEnbIdPresent(final boolean macroEnbId) { + flags = BitHelper.setBitTo(flags, 7, macroEnbId); + return this; + } + + public boolean isLai() { + return BitHelper.isBitSet(flags, 6); + } + + public UliFlags setLai(final boolean lai) { + flags = BitHelper.setBitTo(flags, 6, lai); + return this; + } + + public boolean isEcgi() { + return BitHelper.isBitSet(flags, 5); + } + + public UliFlags setEcgi(final boolean ecgi) { + flags = BitHelper.setBitTo(flags, 5, ecgi); + return this; + } + + public boolean isTai() { + return BitHelper.isBitSet(flags, 4); + } + + public UliFlags setTai(final boolean tai) { + flags = BitHelper.setBitTo(flags, 4, tai); + return this; + } + + public boolean isRai() { + return BitHelper.isBitSet(flags, 3); + } + + public UliFlags setRai(final boolean rai) { + flags = BitHelper.setBitTo(flags, 3, rai); + return this; + } + + public boolean isSai() { + return BitHelper.isBitSet(flags, 2); + } + + public UliFlags setSai(final boolean sai) { + flags = BitHelper.setBitTo(flags, 2, sai); + return this; + } + + public boolean isCgi() { + return BitHelper.isBitSet(flags, 1); + } + + public UliFlags setCgi(final boolean cgi) { + flags = BitHelper.setBitTo(flags, 1, cgi); + return this; + } + + public byte getFlags() { + return flags; + } + + public UliFlags setFlags(final byte flags) { + this.flags = flags; + return this; + } + + public String toString() { + return "UliFlags{" + + "isExtendedMacroEnbId=" + isExtendedMacroEnbIdPresent() + + ", isMacroEnbIdPresent=" + isMacroEnbIdPresent() + + ", isLai=" + isLai() + + ", isEcgi=" + isEcgi() + + ", isTai=" + isTai() + + ", isRai=" + isRai() + + ", isSai=" + isSai() + + ", isCgi=" + isCgi() + + '}'; + } + } + + /** + * Base class for all location information. Those all contain MNC and MCC. + */ + public abstract static class Location { + private byte[] bytes; + + Location(final byte[] bytes) { + this.bytes = bytes; + } + + /** + * Returns the raw bytes of the location information. + * + * @return The raw bytes + */ + public byte[] getBytes() { + return bytes; + } + + /** + * Sets the raw bytes of the location information. + * + * @param bytes The raw bytes + * @return The location information itself + */ + public Location setBytes(final byte[] bytes) { + this.bytes = bytes; + return this; + } + + /** + * Returns the Mobile Country Code (MCC) of the location information. + * + * @return The MCC + */ + public int getMcc() { + return MncMcc.decodeMcc(bytes[0], bytes[1]); + } + + /** + * Returns the Mobile Network Code (MNC) of the location information. + * + * @return The MNC + */ + public int getMnc() { + return MncMcc.decodeMnc(bytes[1], bytes[2]); + } + } + + /** + * Cell Global Identifier (CGI) location information. Only zero or one time present. + */ + public static class Cgi extends Lai { + Cgi(final byte[] bytes) { + super(bytes); + } + + /** + * Returns the Cell Global Identifier (CGI) of the location information. + * + * @return The CGI + */ + public int getCgi() { + return BitHelper.int16ToInt32(getBytes()[5], getBytes()[6]); + } + + public String toString() { + return "Cgi{" + + "mcc=" + getMcc() + + ", mnc=" + getMnc() + + ", lac=" + getLac() + + ", cgi=" + getCgi() + + '}'; + } + } + + + /** + * Service Area Identifier (SAI) location information. Only zero or one time present. + */ + public static class Sai extends Lai { + Sai(final byte[] bytes) { + super(bytes); + } + + /** + * Returns the Service Area Identifier (SAI) of the location information. + * + * @return The SAI + */ + public int getSai() { + return BitHelper.int16ToInt32(getBytes()[5], getBytes()[6]); + } + + public String toString() { + return "Sai{" + + "mcc=" + getMcc() + + ", mnc=" + getMnc() + + ", lac=" + getLac() + + ", sai=" + getSai() + + '}'; + } + } + + /** + * Routing Area Identifier (RAI) location information. Only zero or one time present. + */ + public static class Rai extends Lai { + + Rai(final byte[] bytes) { + super(bytes); + } + + /** + * Returns the Routing Area Identifier (RAI) of the location information. + * + * @return The RAI + */ + public int getRai() { + return BitHelper.int16ToInt32(getBytes()[5], getBytes()[6]); + } + + public String toString() { + return "Rai{" + + "mcc=" + getMcc() + + ", mnc=" + getMnc() + + ", lac=" + getLac() + + ", rai=" + getRai() + + '}'; + } + + } + + /** + * Tracking Area Identifier (TAI) location information. Only zero or one time present. + */ + public static class Tai extends Location { + Tai(final byte[] bytes) { + super(bytes); + } + + /** + * Returns the Tracking Area Identifier (TAI) of the location information. + * + * @return The TAI + */ + public int getTai() { + return BitHelper.int16ToInt32(getBytes()[3], getBytes()[4]); + } + + public String toString() { + return "Tai{" + + "mcc=" + getMcc() + + ", mnc=" + getMnc() + + ", tai=" + getTai() + + '}'; + } + } + + /** + * E-UTRAN Cell Global Identifier (ECGI) location information. Only zero or one time present. + */ + public static class Ecgi extends Location { + Ecgi(final byte[] bytes) { + super(bytes); + } + + /** + * Returns the E-UTRAN Cell Identifier (ECI) of the location information. + * + * @return The ECI + */ + public int getEci() { + final byte lowerNibble = BitHelper.lowerNibble(getBytes()[3]); + return BitHelper.toInt32(lowerNibble, getBytes()[4], getBytes()[5], getBytes()[6]); + } + + public String toString() { + return "Ecgi{" + + "mcc=" + getMcc() + + ", mnc=" + getMnc() + + ", eci=" + getEci() + + '}'; + } + } + + /** + * Location Area Identifier (LAI) location information. Only zero or one time present. + */ + public static class Lai extends Location { + Lai(final byte[] bytes) { + super(bytes); + } + + /** + * Returns the Location Area Cdentifier (LAC) of the location information. + * + * @return The LAC + */ + public int getLac() { + return BitHelper.int16ToInt32(getBytes()[3], getBytes()[4]); + } + + } + + /** + * Macro eNodeB ID + */ + public static class MacroEnbId extends Location { + + MacroEnbId(final byte[] bytes) { + super(bytes); + } + + /** + * Returns the Macro eNodeB ID of the macro eNodeB ID. + * + * @return The Macro eNodeB ID + */ + public int getMacroEnbId() { + // Upper nibble is marked as spare and currently unused + final byte lowerNibble = BitHelper.lowerNibble(getBytes()[3]); + return BitHelper.int24ToInt32(lowerNibble, getBytes()[4], getBytes()[5]); + } + + public String toString() { + return "MacroEnbId{" + + "mcc=" + getMcc() + + ", mnc=" + getMnc() + + ", macroEnbId=" + getMacroEnbId() + + '}'; + } + } + + /** + * Extended Macro eNodeB ID + */ + public static class ExtendedMacroEnbId extends Location { + + ExtendedMacroEnbId(final byte[] bytes) { + super(bytes); + } + + + public boolean isSMeNBFlagSet() { + return BitHelper.isBitSet(getBytes()[3], 8); + } + + public ExtendedMacroEnbId setSMeNBFlag(final boolean sMeNBFlag) { + getBytes()[2] = BitHelper.setBitTo(getBytes()[3], 8, sMeNBFlag); + return this; + } + + /** + * Returns the Extended Macro eNodeB ID of the extended macro eNodeB ID. + * + * @return The Extended Macro eNodeB ID + */ + public int getMacroEnbId() { + // if the sMeNB flag is set, the ID is 21 bits long, starting at bit 5 of the 4th byte and extends + // over the 5th and 6th byte + if (isSMeNBFlagSet()) { + final byte partialByteThree = (byte) BitHelper.toInt(getBytes()[3], 1, 5); + return BitHelper.int24ToInt32(partialByteThree, getBytes()[4], getBytes()[5]); + } + + // if the sNeNB flag is not set, the ID is 18 bits long, starting at bit 2 of the 4th byte and extend + // over the 5th and 6th byte + final byte partialByteThree = (byte) BitHelper.toInt(getBytes()[3], 1, 2); + return BitHelper.int24ToInt32(partialByteThree, getBytes()[4], getBytes()[5]); + } + } +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/decoder/Apn.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/decoder/Apn.java new file mode 100644 index 0000000..cbf96a3 --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/decoder/Apn.java @@ -0,0 +1,41 @@ +package com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.decoder; + +import java.nio.charset.StandardCharsets; + +public class Apn { + + public static String decode(final byte[] payload) { + // Encoding: TS 123.003, Clause 9.1: Each label is encoded as a length octet followed by that number of octets + final var apn = new StringBuilder(); + var nextLengthIndex = 0; + for (int i = 0; i < payload.length; i++) { + if (i != nextLengthIndex) { + apn.append((char) payload[i]); + continue; + } + + if (i != 0) { + apn.append("."); + } + nextLengthIndex = i + (payload[i] & 0xff) + 1; + } + + + return apn.toString(); + } + + public static byte[] encode(final String apn) { + final var labels = apn.split("\\."); + final var payload = new byte[apn.getBytes(StandardCharsets.US_ASCII).length]; + + int payloadIndex = 0; + for (final var label : labels) { + payload[payloadIndex] = (byte) label.length(); + final var labelBytes = label.getBytes(StandardCharsets.US_ASCII); + System.arraycopy(labelBytes, 0, payload, payloadIndex + 1, labelBytes.length); + payloadIndex += labelBytes.length + 1; + } + + return payload; + } +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/decoder/BitHelper.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/decoder/BitHelper.java new file mode 100644 index 0000000..ea53418 --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/decoder/BitHelper.java @@ -0,0 +1,180 @@ +package com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.decoder; + +import java.nio.ByteBuffer; + +/** + * Helper class for bit operations. My brain hurts when I try to do this in my head, so I wrote this class + * to make my code more expressive. + * + * @author Lennart Rosam + */ +public class BitHelper { + + private BitHelper() { + } + + /** + * Checks if a given bit is set in a byte. + * + * @param b The byte to check + * @param bitPosition The position of the bit to check (1-8) + * @return true if the bit is set, false otherwise + */ + public static boolean isBitSet(final byte b, final int bitPosition) { + final int mask = 1 << (bitPosition - 1); + return (b & mask) != 0; + } + + /** + * Sets a bit in a byte. + * + * @param b The byte to set the bit in + * @param bitPosition The position of the bit to set (1-8) + * @return The modified byte with the bit set + */ + public static byte setBit(final byte b, final int bitPosition) { + final int mask = 1 << (bitPosition - 1); + return (byte) (b | mask); + } + + /** + * Sets a bit in a byte to either 1 or 0 indicated by the boolean. + * + * @param b The byte to set the bit in + * @param bitPosition The position of the bit to set (1-8) + * @param value The value to set the bit to. True for 1, false for 0 + * @return The modified byte with the bit set + */ + public static byte setBitTo(final byte b, final int bitPosition, final boolean value) { + if (value) { + return setBit(b, bitPosition); + } + return clearBit(b, bitPosition); + } + + /*** + * Clears a bit in a byte. + * + * @param b The byte to clear the bit in + * @param bitPosition The position of the bit to clear (1-8) + * @return The modified byte with the bit cleared + */ + public static byte clearBit(final byte b, final int bitPosition) { + final int mask = ~(1 << (bitPosition - 1)); + return (byte) (b & mask); + } + + /** + * Returns just the lower nibble of a byte. + * @param b The byte to get the lower nibble from + * @return The lower nibble + */ + public static byte lowerNibble(final byte b) { + return (byte) ((b & 0b00001111) << 4); + } + + /** + * Sets the lower nibble of a byte to a given 4 bit integer. + * + * @param fourBitInt The 4 bit integer to set the lower nibble to (0-15) + * @param b The byte to set the lower nibble in + * @return The modified byte with the lower nibble set + */ + public static byte setLowerNibble(final int fourBitInt, final byte b) { + if (fourBitInt > 0b1111) { + throw new IllegalArgumentException("fourBitInt must be between 0 and 15"); + } + + return (byte) ((b & 0b11110000) | (fourBitInt & 0b00001111)); + } + + /** + * Sets the upper nibble of a byte to a given 4 bit integer. + * + * @param fourBitInt The 4 bit integer to set the upper nibble to (0-15) + * @param b The byte to set the upper nibble in + * @return The modified byte with the upper nibble set + */ + public static byte setUpperNibble(final int fourBitInt, final byte b) { + if (fourBitInt > 0b1111) { + throw new IllegalArgumentException("fourBitInt must be between 0 and 15"); + } + + return (byte) ((b & 0b00001111) | (fourBitInt << 4)); + } + + public static byte setThreeBitInt(final int threeBitInt, final int lowestOrderBit, final int highestOrderBit, final byte b) { + if (threeBitInt > 0b111) { + throw new IllegalArgumentException("threeBitInt must be between 0 and 7"); + } + + byte mask = 0b00000000; + for (int i = highestOrderBit; i >= lowestOrderBit; i--) { + mask = setBit(mask, i); + } + return (byte) ((b & ~mask) | (threeBitInt << lowestOrderBit - 1)); + } + + /** + * Returns just the upper nibble of a byte. + * @param b The byte to get the upper nibble from + * @return The upper nibble + */ + public static byte upperNibble(final byte b) { + return (byte) ((b & 0b11110000) >>> 4); + } + + /** + * Converts a byte to an integer that is between lowestBit and highestBit. + * @param b the byte to convert + * @param lowestBit the lowest bit to convert (1-8 inclusive) + * @param highestBit the highest bit to convert (1-8 inclusive) + * @return The integer + */ + public static int toInt(final byte b, final int lowestBit, final int highestBit) { + byte mask = 0b00000000; + for (int i = highestBit; i >= lowestBit; i--) { + mask = setBit(mask, i); + } + return (b & mask) >>> lowestBit - 1; + } + + public static int toInt(final byte b) { + return toInt(b, 1, 8); + } + + /** + * Converts a 16 bit integer to an 32 bit integer. + * + * @param fourthOctet The fourth octet of the final integer + * @param fifthOctet The fifth octet of the final integer + * @return The integer + */ + public static int int16ToInt32(final byte fourthOctet, final byte fifthOctet) { + return ByteBuffer.wrap(new byte[]{0x0, 0x0, fourthOctet, fifthOctet}).getInt(); + } + + /** + * Converts a 24 bit integer to an 32 bit integer. Wierd telco datatype that is used in GTPv2. + * @param thirdOctet The third octet of the final integer + * @param fourthOctet The fourth octet of the final integer + * @param fifthOctet The fifth octet of the final integer + * @return The integer + */ + public static int int24ToInt32(final byte thirdOctet, final byte fourthOctet, final byte fifthOctet) { + return ByteBuffer.wrap(new byte[]{0x0, thirdOctet, fourthOctet, fifthOctet}).getInt(); + } + + /** + * Converts 4 bytes to an 32 bit integer. This is just a convenience method to make the code more readable. + * + * @param firstOctet The first octet of the final integer + * @param secondOctet The second octet of the final integer + * @param thirdOctet The third octet of the final integer + * @param fourthOctet The fourth octet of the final integer + * @return The integer + */ + public static int toInt32(final byte firstOctet, final byte secondOctet, final byte thirdOctet, final byte fourthOctet) { + return ByteBuffer.wrap(new byte[]{firstOctet, secondOctet, thirdOctet, fourthOctet}).getInt(); + } +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/decoder/IpV4V6.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/decoder/IpV4V6.java new file mode 100644 index 0000000..0de5671 --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/decoder/IpV4V6.java @@ -0,0 +1,43 @@ +package com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.decoder; + +public class IpV4V6 { + + public static String decodeV4(final byte[] payload) { + return String.format("%d.%d.%d.%d", payload[0] & 0xff, payload[1] & 0xff, payload[2] & 0xff, payload[3] & 0xff); + } + + public static byte[] encodeV4(final String ipv4) { + final var octets = ipv4.split("\\."); + final var payload = new byte[4]; + for (int i = 0; i < 4; i++) { + payload[i] = (byte) Integer.parseInt(octets[i]); + } + return payload; + } + + public static String decodeV6(final byte[] payload) { + final var ipv6Address = new StringBuilder(); + for (int i = 0; i < 16; i += 2) { + ipv6Address.append(String.format("%02x%02x", payload[i] & 0xff, payload[i + 1] & 0xff)); + if (i < 14) { + ipv6Address.append(":"); + } + } + return ipv6Address.toString(); + } + + public static byte[] encodeV6(final String ipv6) { + if (ipv6.contains("::")) { + throw new IllegalArgumentException("IPv6 shorthand form is not supported! (Must not contain ::"); + } + + final var octets = ipv6.split(":"); + final var payload = new byte[16]; + for (int i = 0; i < 8; i++) { + final var octet = octets[i]; + payload[i * 2] = (byte) Integer.parseInt(octet.substring(0, 2), 16); + payload[i * 2 + 1] = (byte) Integer.parseInt(octet.substring(2, 4), 16); + } + return payload; + } +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/decoder/MncMcc.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/decoder/MncMcc.java new file mode 100644 index 0000000..78e15b6 --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/decoder/MncMcc.java @@ -0,0 +1,40 @@ +package com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.decoder; + +/** + * Decodes MncMcc from a byte array. + *

+ * Source: https://github.com/Takuto88/cell-id-decoder/blob/main/src/main/java/de/takuto/cellid/Application.java + * @author Lennart Rosam + */ +public class MncMcc { + private MncMcc(){} + + public static int decodeMcc(final byte firstOctet, final byte secondOctet) { + + final int mccFirstDigit = firstOctet & 0x0F; + final int mccSecondDigit = (firstOctet & 0xF0) >> 4; + final int mccThirdDigit = secondOctet & 0x0F; + + return (mccFirstDigit * 100) + (mccSecondDigit * 10) + mccThirdDigit ; + } + + public static int decodeMnc(final byte secondOctet, final byte thirdOctet) { + final int mncFirstDigit = (thirdOctet & 0xF0) >> 4; + final int mncSecondDigit = thirdOctet & 0x0F; + final int mncThirdDigit = (secondOctet & 0xF0) >> 4; + final boolean hasFiller = mncThirdDigit == 0xF; + final boolean isSingleDigitMnc = (mncSecondDigit == 0x00 && hasFiller); + final boolean isTwoDigitMnc = hasFiller && !isSingleDigitMnc; + + if (isSingleDigitMnc) { + return mncFirstDigit; + } + + if (isTwoDigitMnc) { + return (mncFirstDigit * 10) + mncSecondDigit; + } + + return (mncFirstDigit * 100) + (mncSecondDigit * 10) + mncThirdDigit; + } + +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/decoder/Tbcd.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/decoder/Tbcd.java new file mode 100644 index 0000000..ef1e6f8 --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/decoder/Tbcd.java @@ -0,0 +1,35 @@ +package com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.decoder; + +/** + * Decodes TBCD encoded byte arrays. In those arrays, numbers are encoded in 4 bit chunks, with the most significant + * nibble being the SECOND digit and the least significant nibble being the FIRST digit. + *

+ * Allows for efficient encoding of long numbers in just a few bytes. The last byte may be terminated with a 0xF. + * @author Lennart Rosam + */ +public class Tbcd { + + private Tbcd() {} + + /** + * Decodes a TBCD encoded byte array into a string. + * @param payload The byte array to decode + * @return The decoded, human-readable string + */ + public static String decode(final byte[] payload) { + + final byte lastByte = payload[payload.length - 1]; + final boolean lastDigitTerminated = ((byte) (lastByte & 0b11110000) >> 4) < 0x0; + final char[] numbers = new char[payload.length * 2 - (lastDigitTerminated ? 1 : 0)]; + for (int i = 0; i < payload.length; i++) { + numbers[i * 2] = (char) ((payload[i] & 0b00001111) + 0x30); + // Last digit might be termination digit + if (i * 2 + 1 >= numbers.length) { + break; + } + numbers[i * 2 + 1] = (char) (((payload[i] & 0b11110000) >> 4) + 0x30); + } + + return new String(numbers); + } +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/tft/GenericPacketFilterComponent.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/tft/GenericPacketFilterComponent.java new file mode 100644 index 0000000..df3e516 --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/tft/GenericPacketFilterComponent.java @@ -0,0 +1,107 @@ +package com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.tft; + +import com.sipgate.udpproxy.udp.proxy.processors.Message; + +public class GenericPacketFilterComponent extends Message { + + public GenericPacketFilterComponent(final byte[] payload) { + this.payload = payload; + } + + public static GenericPacketFilterComponent fromBytes(final byte[] rawBytes) { + final var packetFilterComponentType = PacketFilterComponentType.fromCode(rawBytes[0]); + if (packetFilterComponentType == null) { + throw new IllegalArgumentException("Unknown packet filter component type: " + rawBytes[0]); + } + + switch (packetFilterComponentType) { + case IPV4_REMOTE_ADDRESS, IPV4_LOCAL_ADDRESS: + return new IPv4AddressComponent(rawBytes); + case IPV6_REMOTE_ADDRESS: + return new IPv6AddressComponent(rawBytes); + case IPV6_REMOTE_ADDRESS_PREFIX_LENGTH, IPV6_LOCAL_ADDRESS_PREFIX_LENGTH: + return new IPv6AddressPrefixComponent(rawBytes); + default: + return new GenericPacketFilterComponent(rawBytes); + } + } + + public PacketFilterComponentType getPacketFilterComponentType() { + return PacketFilterComponentType.fromCode(payload[0]); + } + + public GenericPacketFilterComponent setPacketFilterComponentType(final PacketFilterComponentType type) { + this.payload = new byte[type.length + 1]; + this.payload[0] = (byte) type.code; + return this; + } + + public GenericPacketFilterComponent setPacketFilterBytes(final byte[] bytes) { + if (bytes.length != getPacketFilterComponentType().length) { + throw new IllegalArgumentException("Invalid length for packet filter component type " + getPacketFilterComponentType() + ": " + bytes.length); + } + System.arraycopy(bytes, 0, payload, 1, bytes.length); + return this; + } + + @Override + public byte[] packBytes() { + return payload; + } + + @Override + public String toString() { + return "GenericPacketFilterComponent{" + + "packetFilterComponentType=" + getPacketFilterComponentType() + + '}'; + } + + public enum PacketFilterComponentType { + IPV4_REMOTE_ADDRESS(16, 8), + IPV4_LOCAL_ADDRESS(17, 8), + IPV6_REMOTE_ADDRESS(32, 32), + IPV6_REMOTE_ADDRESS_PREFIX_LENGTH(33, 17), + IPV6_LOCAL_ADDRESS_PREFIX_LENGTH(35, 17), + PROTOCOL_IDENTIFIER_NEXT_HEADER(48, 1), + SINGLE_LOCAL_PORT(64, 2), + LOCAL_PORT_RANGE(65, 4), + SINGLE_REMOTE_PORT(80, 2), + REMOTE_PORT_RANGE(81, 4), + SECURITY_PARAMETER_INDEX(96, 4), + TYPE_OF_SERVICE_TRAFFIC_CLASS(112, 2), + FLOW_LABEL(128, 3), + DESTINATION_MAC_ADDRESS(129, 6), + SOURCE_MAC_ADDRESS(130, 6), + IEE_802_1Q_C_TAG_VID(131, 2), + IEE_802_1Q_S_TAG_VID(132, 2), + IEE_802_1Q_C_TAG_PCP(133, 1), + IEE_802_1Q_S_TAG_PCP(134, 1), + ETHERTYPE(135, 2), + ; + + private final int code; + private final int length; + + PacketFilterComponentType(final int code, final int length) { + this.code = code; + this.length = length; + } + + public int getCode() { + return code; + } + + public int getValueLength() { + return length; + } + + public static PacketFilterComponentType fromCode(final int code) { + for (final var packetFilterComponentType : values()) { + if (packetFilterComponentType.getCode() == code) { + return packetFilterComponentType; + } + } + return null; + } + } +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/tft/IPv4AddressComponent.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/tft/IPv4AddressComponent.java new file mode 100644 index 0000000..0b92451 --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/tft/IPv4AddressComponent.java @@ -0,0 +1,63 @@ +package com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.tft; + +import com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.decoder.IpV4V6; + +public class IPv4AddressComponent extends GenericPacketFilterComponent { + public IPv4AddressComponent(final byte[] payload) { + super(payload); + } + + public IPv4AddressComponent() { + super(new byte[8 + 8 + 1]); // Address and mask + type + setPacketFilterComponentType(PacketFilterComponentType.IPV4_REMOTE_ADDRESS); + } + + public IPv4AddressComponent setPacketFilterComponentType(final PacketFilterComponentType packetFilterComponentType) { + if (PacketFilterComponentType.IPV4_REMOTE_ADDRESS != packetFilterComponentType + && PacketFilterComponentType.IPV4_LOCAL_ADDRESS != packetFilterComponentType) { + throw new IllegalArgumentException( + "Packet filter component type must be IPV4_REMOTE_ADDRESS OR IPV4_LOCAL_ADDRESS" + ); + } + payload[0] = (byte) packetFilterComponentType.getCode(); + return this; + } + + public String getIpv4Address() { + final byte[] ipv4Address = new byte[4]; + System.arraycopy(payload, 1, ipv4Address, 0, 4); + + return IpV4V6.decodeV4(ipv4Address); + } + + public IPv4AddressComponent setIpv4RemoteAddress(final String ipv4RemoteAddress) { + final byte[] ipv4Address = IpV4V6.encodeV4(ipv4RemoteAddress); + System.arraycopy(ipv4Address, 0, payload, 1, 4); + + return this; + } + + public String getIpv4Mask() { + final byte[] ipv4Address = new byte[4]; + System.arraycopy(payload, 9, ipv4Address, 0, 4); + + return IpV4V6.decodeV4(ipv4Address); + } + + public IPv4AddressComponent setIpv4Mask(final String ipv4RemoteAddress) { + final byte[] ipv4Address = IpV4V6.encodeV4(ipv4RemoteAddress); + System.arraycopy(ipv4Address, 0, payload, 9, 4); + + return this; + } + + @Override + public String toString() { + return "IPv4AddressComponent{" + + "packetFilterComponentType=" + getPacketFilterComponentType() + + ", ipv4Address=" + getIpv4Address() + + ", ipv4Mask=" + getIpv4Mask() + + '}'; + } + +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/tft/IPv6AddressComponent.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/tft/IPv6AddressComponent.java new file mode 100644 index 0000000..4729fe1 --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/tft/IPv6AddressComponent.java @@ -0,0 +1,51 @@ +package com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.tft; + +import com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.decoder.IpV4V6; + +public class IPv6AddressComponent extends GenericPacketFilterComponent { + public IPv6AddressComponent(final byte[] payload) { + super(payload); + } + + public IPv6AddressComponent() { + super(new byte[16 + 16 + 1]); // Address and mask + type + setPacketFilterComponentType(PacketFilterComponentType.IPV6_REMOTE_ADDRESS); + } + + public String getIpV6Address() { + final byte[] ipv6Address = new byte[16]; + System.arraycopy(payload, 1, ipv6Address, 0, 16); + + return IpV4V6.decodeV6(ipv6Address); + } + + public IPv6AddressComponent setIpV6Address(final String ipV6Address) { + final byte[] ipv6Address = IpV4V6.encodeV6(ipV6Address); + System.arraycopy(ipv6Address, 0, payload, 1, 16); + + return this; + } + + public String getIpV6Mask() { + final byte[] ipv6Address = new byte[16]; + System.arraycopy(payload, 17, ipv6Address, 0, 16); + + return IpV4V6.decodeV6(ipv6Address); + } + + public IPv6AddressComponent setIpV6Mask(final String ipV6Mask) { + final byte[] ipv6Address = IpV4V6.encodeV6(ipV6Mask); + System.arraycopy(ipv6Address, 0, payload, 17, 16); + + return this; + } + + @Override + public String toString() { + return "IPv6AddressComponent{" + + "packetFilterComponentType=" + getPacketFilterComponentType() + + ", ipV6Address=" + getIpV6Address() + + ", ipV6Mask=" + getIpV6Mask() + + '}'; + } +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/tft/IPv6AddressPrefixComponent.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/tft/IPv6AddressPrefixComponent.java new file mode 100644 index 0000000..c0d3dab --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/gtpv2/ie/tft/IPv6AddressPrefixComponent.java @@ -0,0 +1,46 @@ +package com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.tft; + +import com.sipgate.udpproxy.udp.proxy.processors.gtpv2.ie.decoder.IpV4V6; + +public class IPv6AddressPrefixComponent extends GenericPacketFilterComponent { + public IPv6AddressPrefixComponent(final byte[] payload) { + super(payload); + } + + public IPv6AddressPrefixComponent() { + super(new byte[16 + 1 + 1]); // Address and prefix length + type + setPacketFilterComponentType(PacketFilterComponentType.IPV6_REMOTE_ADDRESS_PREFIX_LENGTH); + } + + public String getIpV6Address() { + final byte[] ipv6Address = new byte[16]; + System.arraycopy(payload, 1, ipv6Address, 0, 16); + + return IpV4V6.decodeV6(ipv6Address); + } + + public IPv6AddressPrefixComponent setIpV6Address(final String ipV6Address) { + final byte[] ipv6Address = IpV4V6.encodeV6(ipV6Address); + System.arraycopy(ipv6Address, 0, payload, 1, 16); + + return this; + } + + public int getIpV6PrefixLength() { + return payload[17]; + } + + public IPv6AddressPrefixComponent setIpV6PrefixLength(final int ipV6PrefixLength) { + payload[17] = (byte) ipV6PrefixLength; + return this; + } + + @Override + public String toString() { + return "IPv6AddressPrefixComponent{" + + "packetFilterComponentType=" + getPacketFilterComponentType() + + ", ipV6Address=" + getIpV6Address() + + ", ipV6PrefixLength=" + getIpV6PrefixLength() + + '}'; + } +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/sip/SipMessage.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/sip/SipMessage.java new file mode 100644 index 0000000..6bafdb5 --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/sip/SipMessage.java @@ -0,0 +1,27 @@ +package com.sipgate.udpproxy.udp.proxy.processors.sip; + +import com.sipgate.udpproxy.udp.proxy.processors.Message; + +public class SipMessage extends Message { + private String message; + + public SipMessage(final byte[] bytes) { + super(bytes); + this.message = new String(bytes); + } + + public String getMessage() { + return message; + } + + public void setMessage(final String message) { + this.message = message; + } + + @Override + public void packBytes() { + setPayloadBytes(message.getBytes()); + } + + +} diff --git a/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/sip/SipMessageProcessor.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/sip/SipMessageProcessor.java new file mode 100644 index 0000000..fc2a351 --- /dev/null +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/sip/SipMessageProcessor.java @@ -0,0 +1,29 @@ +package com.sipgate.udpproxy.udp.proxy.processors.sip; + +import com.sipgate.udpproxy.udp.proxy.processors.Message; +import com.sipgate.udpproxy.udp.proxy.processors.MessageProcessor; + +import java.net.DatagramPacket; + +public class SipMessageProcessor implements MessageProcessor { + + /** + * Removes the Route header from the SIP packet. + * sipgate customer loadbalancers will respond with 403 if the Route header is present. + * + * @param packet the packet to rewrite + * @param message the message to rewrite + */ + @Override + public void rewriteMessage(final DatagramPacket packet, final Message message) { + final SipMessage sipMessage = (SipMessage) message; + final String original = sipMessage.getMessage(); + final StringBuilder sb = new StringBuilder(); + original.lines().forEach(line -> { + if (!line.startsWith("Route:")) { + sb.append(line).append("\r\n"); + } + }); + sipMessage.setMessage(sb.toString()); + } +} diff --git a/src/main/java/com/sipgate/udpproxy/protocol/sip/SipTargetResolver.java b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/sip/SipTargetProcessor.java similarity index 50% rename from src/main/java/com/sipgate/udpproxy/protocol/sip/SipTargetResolver.java rename to src/main/java/com/sipgate/udpproxy/udp/proxy/processors/sip/SipTargetProcessor.java index 1aa82a8..0e10477 100644 --- a/src/main/java/com/sipgate/udpproxy/protocol/sip/SipTargetResolver.java +++ b/src/main/java/com/sipgate/udpproxy/udp/proxy/processors/sip/SipTargetProcessor.java @@ -1,11 +1,11 @@ -package com.sipgate.udpproxy.protocol.sip; +package com.sipgate.udpproxy.udp.proxy.processors.sip; -import com.sipgate.udpproxy.protocol.ProxyTarget; -import com.sipgate.udpproxy.protocol.ProxyTargetResolver; +import com.sipgate.udpproxy.udp.proxy.processors.ProxyTarget; +import com.sipgate.udpproxy.udp.proxy.processors.ProxyTargetProcessor; import java.net.DatagramPacket; -public class SipTargetResolver implements ProxyTargetResolver { +public class SipTargetProcessor implements ProxyTargetProcessor { @Override public ProxyTarget resolveTarget(final DatagramPacket inboundPacket) { // FIXME: sipgate customer loadbalancer hardcoded. Inspect packet for real target