All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.snice.networking.examples.gtp.Pgw Maven / Gradle / Ivy

There is a newer version: 0.0.13
Show newest version
package io.snice.networking.examples.gtp;


import io.snice.buffer.Buffer;
import io.snice.codecs.codec.gtp.Teid;
import io.snice.codecs.codec.gtp.gtpc.v1.Gtp1Message;
import io.snice.codecs.codec.gtp.gtpc.v2.messages.tunnel.CreateSessionRequest;
import io.snice.codecs.codec.gtp.gtpc.v2.messages.tunnel.DeleteSessionRequest;
import io.snice.networking.common.ConnectionId;
import io.snice.networking.gtp.GtpApplication;
import io.snice.networking.gtp.GtpBootstrap;
import io.snice.networking.gtp.GtpEnvironment;
import io.snice.networking.gtp.GtpTunnel;
import io.snice.networking.gtp.PdnSessionContext;
import io.snice.networking.gtp.event.GtpEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * Sample app that pretends to be a PGW (pretends because it is far from a complete PGW!)
 */
public class Pgw extends GtpApplication {

    private static final Logger logger = LoggerFactory.getLogger(GtpApplication.class);

    private GtpEnvironment environment;

    private final ConcurrentMap pdnSessions = new ConcurrentHashMap<>();
    private final ConcurrentMap tunnels = new ConcurrentHashMap<>();

    private final Sgi sgi;

    public Pgw(final Sgi sgi) {
        this.sgi = sgi;
    }

    /**
     * dns query for google.com. Grabbed from wireshark
     */
    final static Buffer dnsQuery = Buffer.of(
            (byte) 0x5c, (byte) 0x79, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00,
            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x03, (byte) 0x77, (byte) 0x77, (byte) 0x77,
            (byte) 0x06, (byte) 0x67, (byte) 0x6f, (byte) 0x6f, (byte) 0x67, (byte) 0x6c, (byte) 0x65, (byte) 0x03,
            (byte) 0x63, (byte) 0x6f, (byte) 0x6d, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x01);

    @Override
    public void initialize(final GtpBootstrap bootstrap) {
        bootstrap.onConnection(c -> true).save(tunnel -> {
            tunnels.put(tunnel.id(), tunnel);
        }).accept(b -> {
            b.match(GtpEvent::isPdu).map(GtpEvent::toGtp1Message).consume(this::processPdu);
            b.match(GtpEvent::isCreateSessionRequest).map(GtpEvent::toCreateSessionRequest).consume(this::processCreateSessionRequest);
            b.match(GtpEvent::isDeleteSessionRequest).map(GtpEvent::toDeleteSessionRequest).consume(this::processDeleteSessionRequest);
        });
    }

    public void processPdu(final GtpTunnel tunnel, final Gtp1Message pdu) {
        final var localTeid = pdu.getHeader().toGtp1Header().getTeid();
        final var pdnSession = pdnSessions.get(localTeid);
        if (pdnSession == null) {
            logger.warn("Unable to locate PDN Session for TEID {}. Dropping PDU", localTeid);
        } else {

            // NOTE: the reason for not using the GtpTunnel that was passed in to us is that
            // Snice-Networking says you shouldn't use that tunnel after the invocation. As such,
            // we will look-up the real tunnel that we saved away earlier since that one we
            // can use outside of method invocation from the underlying snice-networking layers.
            final var t = tunnels.get(tunnel.id());
            sgi.processPdu(t, pdnSession, pdu);
        }
    }

    private void processCreateSessionRequest(final GtpTunnel tunnel, final CreateSessionRequest request) {
        // Note: will blow up if the Optional is empty. But, this is a sample app so...
        final var remoteFTeid = request.getFTeid().get().getValue();
        final var remoteTeid = remoteFTeid.getTeid();

        final var localTeid = Teid.random();
        final var localBearerTeid = Teid.random();
        logger.info("Creating new Session with Local Sender TEID {} and Local Bearer TEID {}", localTeid, localBearerTeid);

        final var response = request.createResponse()
                .withTeid(remoteTeid) // the TEID of the remote end has to go in the header.
                .withIPv4PdnAddressAllocation("100.64.0.10")
                .withNewSenderControlPlaneFTeid()
                .withTeid(localTeid) // Our local TEID, which, as you can see, goes in our GTP-C FTeid.
                .withIPv4Address("127.0.0.1")
                .doneFTeid()
                .withNewBearerContext()
                .withEpsBearerId(5)
                .withNewSgwFTeid()
                .withTeid(localBearerTeid) // and this is our
                .withIPv4Address("127.0.0.1")
                .doneFTeid()
                .withNewBearerQualityOfService(9)
                .withPriorityLevel(10)
                .withPci()
                .doneBearerQoS()
                .doneBearerContext()
                .build();

        // TODO: the remote/local TEID etc is really from the perspective of the requester
        // v.s. the receiver. Perhaps this should be renamed. A bit confusing because the remote TEID
        // is actually the one I generated above and as such, it is my local one.
        final var pdnSession = PdnSessionContext.of(request, response);
       if (pdnSessions.putIfAbsent(localTeid, pdnSession) != null) {
           System.err.println("WTF - There already exists a PDN Session under Local TEID: " + localTeid);
       }

        if (pdnSessions.putIfAbsent(localBearerTeid, pdnSession) != null) {
            System.err.println("WTF - There already exists a PDN Session unde4r Local Bearer TEID: " + localBearerTeid);
        }

        tunnel.send(response);
    }

    private void processDeleteSessionRequest(final GtpTunnel tunnel, final DeleteSessionRequest request) {
        // TODO: should be defensive here. Or at least if this was a real PGW implementation and not an example!
        final var teid = request.getHeader().getTeid().get();
        final var session = pdnSessions.remove(teid);
        sgi.deleteSession(session);
        if (session == null) {
            logger.info("Session unknown " + teid);
            // TODO: return error response.
            return;
        }

        final var sessionAgain = pdnSessions.remove(session.getRemoteBearerTeid());

        // yes, actually want to compare references. It is supposed to be the exact
        // same instance...
        if (session != sessionAgain) {
            logger.warn("Odd, the PDN Context under the different TEIDs are different");
        }

        final var response = request.createResponse();
        if (session == null) {
            // if there were no session we should say so in the Cause.
            // for now, let's ignore it.
            logger.info("Unable to find the PDN Session with TEID {} ", teid);
        }
        tunnel.send(response.build());
    }

    @Override
    public void run(final GtpConfig configuration, final GtpEnvironment environment) {
        this.environment = environment;
    }

    public static void main(final String... args) throws Exception {
        final var sgi = new Sgi();
        final var pgw = new Pgw(sgi);
        sgi.run("SgiServerConfig.yml");
        pgw.run("pgw.yml");
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy