This is an open source implementation of the ETSI X1 / X2 and X3 interfaces for lawful interception of telecommunications. At sipgate, we have to provide these interfaces to the authorities, and we decided to open source our implementation so others can learn and inspect how this actually works in code.
Maven:
<depedency>
<groupId>com.sipgate</groupId>
<artifactId>li-lib-x1x2x3</artifactId>
<version>1.0.0</version>
</dependency>
Gradle (Kotlin DSL):
implementation("com.sipgate:li-lib-x1x2x3:1.0.0")
Gradle (Groovy DSL):
implementation 'com.sipgate:li-lib-x1x2x3:1.0.0'
---
title: Simplified overview of X1/X2/X3
---
flowchart LR
LE["Law Enforcement"]
ADMF <--"e.g. HI-1 (out of scope)"--> LE
NE <--X1--> ADMF
NE <--X2/X3--> MDF
MDF <--"e.g. HI-2/HI-3 (out of scope)"--> LE
In this simplified version the NE is also the Point of Interception (POI) for example an asterisk PBX.
- ADMF = ADMinistration Function
- HI = Handover Interface 1
- MDF = Mediation and Delivery Function
- NE = Network Element
You can use this library to implement:
- ADMF (HI out of scope)
- NE completeley
- MDF (HI out of scope)
This library offers client and server implementations for the X1/X2/X3 protocols:
- X1Client
- X1Server
- This class supports the server-side of the Network Element. You have to implement the server-side of other roles yourself.
- X2X3Client
- X2X3Server
This library does not implement your persistence layer. Use the repository interfaces to implement those.
This library does not implement the business logic on your side (e.g. activation of interception in your SIP stack). Use the listener interfaces to implement those.
When you want to intercept and forward SIP or RTP, then use the X2X3Client
in your business logic.
When you want to receive the intercepted SIP or RTP over X2/X3, then use the X2X3Server
and add Consumer
implementations.
Below you find code snippets for several use cases. If you want to see a complete ADMF implmentation with a REST interface, see our LI simulator.
// set to null if you want to disable mutual TLS (not recommended)
final var sslContext = SSLContextBuilder.newBuilder()
.withKeyStore(Path.of("keystore.jks"), "password")
.withTrustStore(Path.of("truststore.jks"), "password")
.build();
// use your implementation instead (e.g. store to pgsql)
final var destinationRepository = new YourDestinationRepositoryImpl();
final var taskRepository = new YourTaskRepositoryImpl();
// use your implementation instead (e.g. control your SIP/RTP stack)
final var taskListener = new NoopTaskListener();
final var destinationListener = new NoopDestinationListener();
// use your implementation instead (e.g. wrap Micrometer)
final var metricsService = new NoopMetricsService();
final var x1Server = X1Server.createNetworkElement(
sslContext,
destinationRepository,
taskRepository,
"network-element-id"
)
.setTaskListener(taskListener)
.setDestinationListener(destinationListener)
.setMetricsService(metricsService);
// listen on TCP port 8443 for any IPv4 address
// this call blocks, so start it in a separate thread if needed
x1Server.start(new InetSocketAddress("0.0.0.0", 8443));
// set to null if you want to disable mutual TLS (not recommended)
final var sslContext = SSLContextBuilder.newBuilder()
.withKeyStore(Path.of("keystore.jks"), "password")
.withTrustStore(Path.of("truststore.jks"), "password")
.build();
final var socketFactory = sslContext.getSocketFactory();
final List<TLV> conditionalAttributes = new ArrayList<>(List.of(
/* add your TLVs according to your business logic */
new TimestampTLV(Instant.now())
));
// forward RTP, change this according to your business logic
final var pduObject = new PduObjectBuilder()
.rtp()
.payloadDirection(PayloadDirection.SENT_TO_TARGET)
.correlationID(new byte[]{/* fill according to X2/X3 standard */})
.xid(UUID.randomUUID() /* use the XID from the task activation via X1 */)
.conditionalAttributeFields(conditionalAttributes)
.payload(new byte[]{/* raw RTP payload */})
.build();
final var timeout = 30_000; // milliseconds
final var client = new X2X3Client(socketFactory, "1.2.3.4", 12345, timeout);
client.send(pduObject);
// set to null if you want to disable mutual TLS (not recommended)
final var sslContext = SSLContextBuilder.newBuilder()
.withKeyStore(Path.of("keystore.jks"), "password")
.withTrustStore(Path.of("truststore.jks"), "password")
.build();
final var requestFactory = new X1RequestFactory(DatatypeFactory.newInstance(), "ne-id", "admf-id");
final var request = requestFactory.builder(ListAllDetailsRequest.builder()).build();
final var client = X1ClientBuilder.newBuilder().withTarget("https://ne.example.org:8443/X1/NE").withContext(sslContext).build();
// when you send other types of request, change the response class
// when a request fails, an X1ClientException is thrown
final var response = client.request(request, ListAllDetailsResponse.class);
System.out.println(resp.getListOfXIDs());
// set to null if you want to disable mutual TLS (not recommended)
final var sslContext = SSLContextBuilder.newBuilder()
.withKeyStore(Path.of("keystore.jks"), "password")
.withTrustStore(Path.of("truststore.jks"), "password")
.build();
final var maxHeaderLength = 320;
final var maxPayloadLength = 8192;
// implement your business logic for receiving SIP/RTP via X2/X3 here
final Consumer<PduObject> pduConsumer = (pduObject) -> {};
final var server = new X2X3Server(
sslContext,
new X2X3Decoder(maxHeaderLength, maxPayloadLength)
).addConsumer(pduConsumer);
// listen on TCP port 12345 for any IPv4 address
// this call blocks, so start it in a separate thread if needed
server.start(new InetSocketAddress("0.0.0.0", 12345));
In order to collect Metrics, implement the interface MetricsService
and set it on the X1Server
.
Metric name | Type | Tags | Description |
---|---|---|---|
li_x1_errorResponse |
Counter | request , code |
Number of returned X1 error responses |
li_x1_exception |
Counter | type |
Number of requests that failed because of a Java exception (type is the simple class name) |
li_x1_handleRequestMessage |
Timer | type |
Execution time of handleRequestMessage , type = SimpleClassName |
li_x1_requestContainer |
Counter | size |
Number of X1 request container and their size |
li_x1_requestError |
Counter | type |
Number of requests that caused a TopLevelError (currently: type =unknown_message_type ) |
li_x1_requestMessage |
Counter | type , version |
Number of received X1 request messages, SimpleClassName and version in RequestMessage xml |
li_x1_responseMessage |
Counter | type |
Number of returned X1 response messages, type = SimpleClassName |
git clone
the repository- Run
npm install
for git hooks and prettier (code formatting)
We're using the Maven release plugin.
When ready, run mvn release:prepare
and follow the instructions. This will create, tag and push a new release.
- Get the main XSDs from
- Get XSD dependencies
- Extract all XSD files into generate/src/main/resources
- Check the catalog file generate/src/main/resources/ts_103.cat for the correct schema location patches
- look for the
<xs:import>
tags and check the filenames against the catalog
- look for the
- Now, you can run
make
ingenerate
or:- Generate the Java source files from the XSDs with
mvn clean verify
- Copy the generated Java files from
generate/target/generated-sources/xjc
to thesrc/main/java
folder- look for the
org.etsi
package for the main classes - as well as the
com.kscs.util
package for the utilities from the rich-contract plugin
- look for the
- Generate the Java source files from the XSDs with
cd generate
make
- TS 103 200 with multiple versions (PDF, XSDs, some versions have examples): https://www.etsi.org/deliver/etsi_ts/103200_103299/10322101/