Проблемы с подключением через AWS Nat Gateway

У меня есть сервер приложений Amazon Linux 2 с приложением Spring Boot в частной подсети. Перед этим сервером приложений в общедоступной подсети находится шлюз Nat. Приложение отправляет запрос с заголовком Connection: keep-alive на удаленный хост, а удаленный хост возвращает ответ с тем же заголовком. Итак, я вижу установленное соединение через netstat.

netstat -t | grep <remote server ip>
tcp6       0      0 ip-172-30-4-31.eu:57324 <remote server ip>:http       ESTABLISHED

Из-за отсутствия трафика в течение 350 секунд шлюз Nat закрывает соединение в соответствии с этим документом: https://docs.aws.amazon.com/vpc/latest/userguide/nat-gateway-troubleshooting.html#nat-gateway-troubleshooting-timeout соединение все еще находится в состоянии «Установлено» на сервере приложений, поэтому следующий запрос к удаленному серверу дает мне:

java.net.SocketException: Connection reset

Я попытался внести изменения на сервере приложения в sysctl.conf, чтобы закрыть соединение почти одновременно с Nat Gateway:

net.ipv4.tcp_keepalive_time=351
net.ipv4.tcp_keepalive_intvl=30
net.ipv4.tcp_keepalive_probes=2

Но ничего не происходит, и сброс трафика с сервера приложений на удаленный сервер через tcpdump не дает мне пакетов keep-alive. Итак, что я могу сделать, чтобы избежать этой проблемы, кроме удаления заголовка Connection в моем приложении?


person kolyaiks    schedule 01.09.2020    source источник


Ответы (1)


Проблема была из-за метода, используемого для открытия сокета. Я использовал Apache Fluent API:

Request.Post(mainProperties.getPartnerURL())
                .addHeader("Signature", SecurityHelper.getSignature(requestBody.getBytes("UTF-8"),
                        mainProperties.getPartnerKey()))
                .addHeader("Content-Type", "application/x-www-form-urlencoded")
                .connectTimeout(mainProperties.getRequestTimeoutMillis())
                .bodyByteArray(requestBody.getBytes(UTF_8))
                .execute().returnContent().asString();

Но я установил параметр so_keepalive в сокет. Это можно сделать с помощью HttpClient:

    SocketConfig socketConfig = SocketConfig.custom()
            .setSoKeepAlive(true)
            .build();

    RequestConfig requestConfig = RequestConfig.custom()
            .setConnectTimeout(mainProperties.getRequestTimeoutMillis())
            .build();

    CloseableHttpClient httpClient = HttpClientBuilder.create()
            .setDefaultSocketConfig(socketConfig)
            .setDefaultRequestConfig(requestConfig)
            .build();
            
    HttpPost post = new HttpPost(mainProperties.getPartnerURL());

    post.addHeader("Signature", SecurityHelper.getSignature(requestBody.getBytes("UTF-8"),
                mainProperties.getPartnerKey()));
    post.addHeader("Content-Type", "text/xml");
    post.setEntity(new StringEntity(requestBody, UTF_8));

    CloseableHttpResponse response = httpClient.execute(post);
    return EntityUtils.toString(response.getEntity(), UTF_8);

Затем net.ipv4.tcp_keepalive_time=350, установленный в моем sysctl.conf (sysctl -p, необходимый для применения изменений), применяется к новому соединению, это можно проверить следующим образом. :

netstat -o | grep <remote-host>
tcp6       0      0 ip-172-30-4-233.e:50414 <remote-host>:http ESTABLISHED **keepalive (152.12/0/0)**

Таким образом, пакет TCP-Keep-Alive, отправленный через 350 секунд после последнего пакета без ответа, закрывает УСТАНОВЛЕННОЕ соединение. Все пакеты TCP-Keep-Alive можно увидеть через дамп tcp:

введите здесь описание изображения

person kolyaiks    schedule 02.09.2020