cancel
Showing results for 
Search instead for 
Did you mean: 

Can't connect to on-premise from CPI in Cloud Foundry enviroment (network unreachable)?

Hi there!

I am working on a project where I have to connect to an on-premise system through SFTP with CPI and Cloud Connector (Cloud Foundry enviroment).

I have followed this official guide but it's not working: https://help.sap.com/viewer/cca91383641e40ffbe03bdc78f00f681/Cloud/en-US/cd1583775afa43f0bb9ec69d9db...

Whenever I try to run it, I get a SOCKS5 command failed with status: NETWORK_UNREACHABLE error, but when I check from Connectivity test in CPI, everything's fine. I can get valid JWT and Location ID, so I can't see where the problem is.

Below I will show you my entire code.

import com.sap.gateway.ip.core.customdev.util.Message;
import java.lang.Object;
import java.util.HashMap;
import groovy.json.*;
import org.cloudfoundry.identity.client.UaaContextFactory;
import org.cloudfoundry.identity.client.UaaContext;
import org.cloudfoundry.identity.client.token.TokenRequest;
import org.cloudfoundry.identity.uaa.oauth.token.CompositeAccessToken;
import org.cloudfoundry.identity.client.token.GrantType;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.util.Base64; // or any other library for base64 encoding
import org.json.JSONArray; // or any other library for JSON objects
import org.json.JSONObject; // or any other library for JSON objects
import org.json.JSONException; // or any other library for JSON objects
public class ConnectivitySocks5ProxySocket extends Socket {
    private static final byte SOCKS5_VERSION = 0x05;
    private static final byte SOCKS5_JWT_AUTHENTICATION_METHOD = (byte) 0x80;
    private static final byte SOCKS5_JWT_AUTHENTICATION_METHOD_VERSION = 0x01;
    private static final byte SOCKS5_COMMAND_CONNECT_BYTE = 0x01;
    private static final byte SOCKS5_COMMAND_REQUEST_RESERVED_BYTE = 0x00;
    private static final byte SOCKS5_COMMAND_ADDRESS_TYPE_IPv4_BYTE = 0x01;
    private static final byte SOCKS5_COMMAND_ADDRESS_TYPE_DOMAIN_BYTE = 0x03;
    private static final byte SOCKS5_AUTHENTICATION_METHODS_COUNT = 0x01;
    private static final int SOCKS5_JWT_AUTHENTICATION_METHOD_UNSIGNED_VALUE = 0x80 & 0xFF;
    private static final byte SOCKS5_AUTHENTICATION_SUCCESS_BYTE = 0x00;
    private static final String SOCKS5_PROXY_HOST_PROPERTY = "onpremise_proxy_host";
    private static final String SOCKS5_PROXY_PORT_PROPERTY = "onpremise_socks5_proxy_port";
    private final String jwtToken;
    private final String sccLocationId;
    public ConnectivitySocks5ProxySocket(String jwtToken, String sccLocationId) {
        this.jwtToken = jwtToken;
        this.sccLocationId = sccLocationId != null ? Base64.getEncoder().encodeToString(sccLocationId.getBytes()) : "";
    }
    protected InetSocketAddress getProxyAddress() {
        try {
            JSONObject jsonObj = new JSONObject(System.getenv("VCAP_SERVICES"));
            def jsonSlurper = new JsonSlurper()
            def object = jsonSlurper.parseText(jsonObj.toString())
            String connProxyHost = object.connectivity[0].credentials.onpremise_proxy_host
            int connProxyPort = Integer.parseInt(object.connectivity[0].credentials.onpremise_socks5_proxy_port);
            return new InetSocketAddress(connProxyHost, connProxyPort);
        } catch (JSONException ex) {
            throw new IllegalStateException("Unable to extract the SOCKS5 proxy host and port", ex);
        }
    }
    private JSONObject extractEnvironmentCredentials() throws JSONException {
        JSONObject jsonObj = new JSONObject(System.getenv("VCAP_SERVICES"));
        JSONArray jsonArr = jsonObj.getJSONArray("connectivity");
        return jsonArr.getJSONObject(0).getJSONObject("credentials");
    }
    @Override
    public void connect(SocketAddress endpoint, int timeout) throws IOException {
        super.connect(getProxyAddress(), timeout);
        OutputStream outputStream = getOutputStream();
        executeSOCKS5InitialRequest(outputStream);
        executeSOCKS5AuthenticationRequest(outputStream);
        executeSOCKS5ConnectRequest(outputStream, (InetSocketAddress) endpoint);
    }
    private void executeSOCKS5InitialRequest(OutputStream outputStream) throws IOException {
        byte[] initialRequest = createInitialSOCKS5Request();
        outputStream.write(initialRequest);
        assertServerInitialResponse();
    }
    private byte[] createInitialSOCKS5Request() throws IOException {
        ByteArrayOutputStream byteArraysStream = new ByteArrayOutputStream();
        try {
            byteArraysStream.write(SOCKS5_VERSION);
            byteArraysStream.write(SOCKS5_AUTHENTICATION_METHODS_COUNT);
            byteArraysStream.write(SOCKS5_JWT_AUTHENTICATION_METHOD);
            return byteArraysStream.toByteArray();
        } finally {
            byteArraysStream.close();
        }
    }
    private void assertServerInitialResponse() throws IOException {
        InputStream inputStream = getInputStream();
        int versionByte = inputStream.read();
        if (SOCKS5_VERSION != versionByte) {
            throw new SocketException(String.format("Unsupported SOCKS version - expected %s, but received %s", SOCKS5_VERSION, versionByte));
        }
        int authenticationMethodValue = inputStream.read();
        if (SOCKS5_JWT_AUTHENTICATION_METHOD_UNSIGNED_VALUE != authenticationMethodValue) {
            throw new SocketException(String.format("Unsupported authentication method value - expected %s, but received %s",
                    SOCKS5_JWT_AUTHENTICATION_METHOD_UNSIGNED_VALUE, authenticationMethodValue));
        }
    }
    private void executeSOCKS5AuthenticationRequest(OutputStream outputStream) throws IOException {
        byte[] authenticationRequest = createJWTAuthenticationRequest();
        outputStream.write(authenticationRequest);
        assertAuthenticationResponse();
    }
    private byte[] createJWTAuthenticationRequest() throws IOException {
        ByteArrayOutputStream byteArraysStream = new ByteArrayOutputStream();
        try {
            byteArraysStream.write(SOCKS5_JWT_AUTHENTICATION_METHOD_VERSION);
            byteArraysStream.write(ByteBuffer.allocate(4).putInt(jwtToken.getBytes().length).array());
            byteArraysStream.write(jwtToken.getBytes());
            byteArraysStream.write(ByteBuffer.allocate(1).put((byte) sccLocationId.getBytes().length).array());
            byteArraysStream.write(sccLocationId.getBytes());
            return byteArraysStream.toByteArray();
        } finally {
            byteArraysStream.close();
        }
    }
    private void assertAuthenticationResponse() throws IOException {
        InputStream inputStream = getInputStream();
        int authenticationMethodVersion = inputStream.read();
        if (SOCKS5_JWT_AUTHENTICATION_METHOD_VERSION != authenticationMethodVersion) {
            throw new SocketException(String.format("Unsupported authentication method version - expected %s, but received %s",
                    SOCKS5_JWT_AUTHENTICATION_METHOD_VERSION, authenticationMethodVersion));
        }
        int authenticationStatus = inputStream.read();
        if (SOCKS5_AUTHENTICATION_SUCCESS_BYTE != authenticationStatus) {
            throw new SocketException("Authentication failed!");
        }
    }
    private void executeSOCKS5ConnectRequest(OutputStream outputStream, InetSocketAddress endpoint) throws IOException {
        byte[] commandRequest = createConnectCommandRequest(endpoint);
        outputStream.write(commandRequest);
        assertConnectCommandResponse();
    }
    private byte[] createConnectCommandRequest(InetSocketAddress endpoint) throws IOException {
        String host = endpoint.getHostName();
        int port = endpoint.getPort();
        ByteArrayOutputStream byteArraysStream = new ByteArrayOutputStream();
        try {
            byteArraysStream.write(SOCKS5_VERSION);
            byteArraysStream.write(SOCKS5_COMMAND_CONNECT_BYTE);
            byteArraysStream.write(SOCKS5_COMMAND_REQUEST_RESERVED_BYTE);
            byte[] hostToIPv4 = parseHostToIPv4(host);
            if (hostToIPv4 != null) {
                byteArraysStream.write(SOCKS5_COMMAND_ADDRESS_TYPE_IPv4_BYTE);
                byteArraysStream.write(hostToIPv4);
            } else {
                byteArraysStream.write(SOCKS5_COMMAND_ADDRESS_TYPE_DOMAIN_BYTE);
                byteArraysStream.write(ByteBuffer.allocate(1).put((byte) host.getBytes().length).array());
                byteArraysStream.write(host.getBytes());
            }
            byteArraysStream.write(ByteBuffer.allocate(2).putShort((short) port).array());
            return byteArraysStream.toByteArray();
        } finally {
            byteArraysStream.close();
        }
    }
    private void assertConnectCommandResponse() throws IOException {
        InputStream inputStream = getInputStream();
        int versionByte = inputStream.read();
        if (SOCKS5_VERSION != versionByte) {
            throw new SocketException(String.format("Unsupported SOCKS version - expected %s, but received %s", SOCKS5_VERSION, versionByte));
        }
        int connectStatusByte = inputStream.read();
        assertConnectStatus(connectStatusByte);
        readRemainingCommandResponseBytes(inputStream);
    }
    private void assertConnectStatus(int commandConnectStatus) throws IOException {
        if (commandConnectStatus == 0) {
            return;
        }
        String commandConnectStatusTranslation;
        switch (commandConnectStatus) {
            case 1:
                commandConnectStatusTranslation = "FAILURE";
                break;
            case 2:
                commandConnectStatusTranslation = "FORBIDDEN";
                break;
            case 3:
                commandConnectStatusTranslation = "NETWORK_UNREACHABLE";
                break;
            case 4:
                commandConnectStatusTranslation = "HOST_UNREACHABLE";
                break;
            case 5:
                commandConnectStatusTranslation = "CONNECTION_REFUSED";
                break;
            case 6:
                commandConnectStatusTranslation = "TTL_EXPIRED";
                break;
            case 7:
                commandConnectStatusTranslation = "COMMAND_UNSUPPORTED";
                break;
            case 8:
                commandConnectStatusTranslation = "ADDRESS_UNSUPPORTED";
                break;
            default:
                commandConnectStatusTranslation = "UNKNOWN";
                break;
        }
        throw new SocketException("SOCKS5 command failed with status: " + commandConnectStatusTranslation);
    }
    private byte[] parseHostToIPv4(String hostName) {
        byte[] parsedHostName = null;
        String[] virtualHostOctets = hostName.split("\\.", -1);
        int octetsCount = virtualHostOctets.length;
        if (octetsCount == 4) {
            try {
                byte[] ipOctets = new byte[octetsCount];
                for (int i = 0; i < octetsCount; i++) {
                    int currentOctet = Integer.parseInt(virtualHostOctets[i]);
                    if ((currentOctet < 0) || (currentOctet > 255)) {
                        throw new IllegalArgumentException(String.format("Provided octet %s is not in the range of [0-255]", currentOctet));
                    }
                    ipOctets[i] = (byte) currentOctet;
                }
                parsedHostName = ipOctets;
            } catch (IllegalArgumentException ex) {
                return null;
            }
        }
        return parsedHostName;
    }
    private void readRemainingCommandResponseBytes(InputStream inputStream) throws IOException {
        inputStream.read(); // skipping over SOCKS5 reserved byte
        int addressTypeByte = inputStream.read();
        if (SOCKS5_COMMAND_ADDRESS_TYPE_IPv4_BYTE == addressTypeByte) {
            for (int i = 0; i < 6; i++) {
                inputStream.read();
            }
        } else if (SOCKS5_COMMAND_ADDRESS_TYPE_DOMAIN_BYTE == addressTypeByte) {
            int domainNameLength = inputStream.read();
            int portBytes = 2;
            inputStream.read(new byte[domainNameLength + portBytes], 0, domainNameLength + portBytes);
        }
    }
}

// THE CODE WHERE I CALL THIS CLASS:

    JSONObject jsonObj = new JSONObject(System.getenv("VCAP_SERVICES"));
    def jsonSlurper = new JsonSlurper()
    def object = jsonSlurper.parseText(jsonObj.toString())
    String connProxyHost = object.connectivity[0].credentials.onpremise_proxy_host
    int connProxyPort = Integer.parseInt(object.connectivity[0].credentials.onpremise_proxy_http_port);

    // get value of "clientid" and "clientsecret" from the environment variables
    String clientid = object.connectivity[0].credentials.clientid;
    String clientsecret = object.connectivity[0].credentials.clientsecret;
     
    // get the URL to xsuaa from the environment variables
    URI xsuaaUrl = new URI(object.connectivity[0].credentials.token_service_url);
     
    // make request to UAA to retrieve access token
    UaaContextFactory factory = UaaContextFactory.factory(xsuaaUrl).authorizePath("/oauth/authorize").tokenPath("/oauth/token");
    TokenRequest tokenRequest = factory.tokenRequest();
    tokenRequest.setGrantType(GrantType.CLIENT_CREDENTIALS);
    tokenRequest.setClientId(clientid);
    tokenRequest.setClientSecret(clientsecret);
    UaaContext xsuaaContext = factory.authenticate(tokenRequest);
    def accessToken = xsuaaContext.getToken();

    ConnectivitySocks5ProxySocket ourConnection = new ConnectivitySocks5ProxySocket(accessToken.toString(), "<Location_Id>");
    SocketAddress socketAddressTry = new InetSocketAddress("<on_premise_host_url>", <on_premise_port>);
    ourConnection.connect(socketAddressTry, 30000);

Accepted Solutions (0)

Answers (2)

Answers (2)

0 Kudos

Did you set a Location ID in both the Cloud Connector and the Coding (when creating the instance for the ConnectivitySocks5ProxySocket)?

I ask because I try to do the same at the moment (CPI-CF -> SOCKS5/CC -> On-Premise System). When I tried to set Location ID (even when I set it such that the value in CC and the Java-Coding was matching) I got the same result. Working in Connectivity Test but not working from Groovy Script (NETWORK UNREACHABLE). When I did not set the Location ID (both in CC and in the Java Coding) I get the response (FORBIDDEN). So that's why I guess somehow with the above Java Coding I am not hitting the "right" Cloud Connector, at least from within the CPI instance.

But I have no solution at the moment to get a connection from within the CPI instance

jalf1991
Explorer
0 Kudos

Hi @jozsef_huszko

Do you get a solution with the connection SOCK5 JAVA?. I can help you. I have done this but i have another problem. If you have a solution please apreciate your help. Share me your experience .

Thanks

0 Kudos

Hi Alfredo,

can you share how you got the SOCKS5 Java-Coding provided by SAP to work from within the CPI/Cloud Foundry? I tried to do the same as Jozsef (Link) and I seem to get one stup further since I have at least reached the Cloud Connector but cannot get through the cloud connector.

Best Regards and thanks in advance
Patrick