[네트워크] 두 IP 가 같은 네트워크에 속해있는지 판별해보자

Hocaron·2023년 12월 16일
1

네트워크

목록 보기
4/5

IP 가 특정 네트워크에 속해있는지 확인이 필요한 기능이 있다. 예를 들다면, 특정 IP 가 amazone ip ranges 에 속해있는지 확인이 필요한 경우다.

IP 와 네트워크 계산 방식에 대해 간단히 알아보고, 특정 IP 가 IP RANGES 에 속하는지 판단하는 코드를 구현해보자.

IP 주소 형식으로는 어떤 것이 있을까?

IP 주소는 두 부분으로 구성된다.

  • 네트워크 주소는 네트워크의 고유 식별자를 가리키는 일련의 숫자이다.
  • 호스트 주소는 네트워크상의 호스트 또는 개별 디바이스 식별자를 나타내는 일련의 숫자이다.

클래스 A

클래스 A IPv4 주소는 네트워크 접두사 비트가 8개이다. 예를 들어 44.0.0.1에서 44는 네트워크 주소이고 0.0.1은 호스트 주소이다.

클래스 B

클래스 B IPv4 주소는 네트워크 접두사 비트가 16개이다. 예를 들어 128.16.0.2에서 128.16은 네트워크 주소이고 0.2는 호스트 주소이다.

클래스 C

클래스 C IPv4 주소는 네트워크 접두사 비트가 24개이다. 예를 들어 192.168.1.100에서 192.168.1은 네트워크 주소이고 100은 호스트 주소이다.

클래스 없는 주소

클래스 없는 주소 또는 Classless Inter-Domain Routing(CIDR) 주소는 가변 길이 서브넷 마스킹(VLSM)을 사용하여 IP 주소의 네트워크와 호스트 주소 비트 간의 비율을 변경한다. 서브넷 마스크는 호스트 주소를 0으로 변환하여 IP 주소의 네트워크 주소 값을 반환하는 식별자 집합이다.

CIDR

CIDR 에 대해서 알아보자

CIDR 블록은 A.B.C.D/N과 같은 형태를 띠고 있다. '/' 뒤에 N은 prefix 길이이며 주소의 왼쪽으로부터 비트의 수를 가리키고 있다.

IPv4는 주소의 길이가 32비트입니다. 따라서 N비트의 CIDR prefix는 32-N 비트의 나머지를 남기며, 남은 비트로로 만들 수 있는 경우의 수는 2^(32-N)라고 할 수 있다. 짧은 CIDR prefix는 더 많은 IP 주소를 가지게 되며, 긴 CIDR prefix는 더 적은 IP 주소를 가지게 된다.

또한 CIDR는 IPv6 주소에서도 사용될 수 있는데, prefix의 길이는 0에서 128까지의 범위를 가지고 있다.(0 < N < 128) IPv6도 IPv4처럼 동일한 방식이 적용된다.

CIDR 계산해보자

192.168.10.0/24라는 CIDR 블록이 있다. prefix길이가 24이므로 192.168.10 부분은 고정이다.

. 을 기준으로 구간을 나눈다면, 한 구간에는 8비트를 차지하기 때문이다.

따라서 Host로 사용할 수 있는 IP 주소는 192.168.10.0 ~ 192.168.10.255이다.

하지만 여기서 네트워크와 브로트캐스트 주소를 뺀 192.168.1 ~ 192.168.254 구간을 실질적으로 사용할 수 있다.

CIDR 계산을 통해 네트워크 주소를 계산해보자

예를 들어 192.168.10.10/24의 네트워크 주소를 알고 싶을 때 다음과 같이 구할 수 있다.

  1. 192.168.10.10을 2진수로 전환한다. 그리고 CIDR prefix 수(여기서는 24)만큼 왼쪽에서부터 1을 24개 채운다.
  2. AND 연산을 하여 192.168.10.0이라는 네트워크 주소를 구할 수 있다.

특정 IP 가 IP RANGES 에 속하는 코드를 구현해보자

public class TrustedProxiesChecker {

    private static final Set<TrustIP> IP_RANGES = Set.of(
            new TrustIP("3.2.34.0/26"),
            new TrustIP("3.5.140.0/2"),
            ...
    );

    @Getter
    private static class TrustIP {
        private static final Pattern CIDR_PATTERN = Pattern.compile("^\\d+\\.\\d+\\.\\d+\\.\\d+/\\d+$");

        private final String ip;
        private final long baseIpToLong;
        private final int prefixLength;

        private TrustIP(String ip) {
            String[] cidrParts = ip.split("/");

            if (!CIDR_PATTERN.matcher(ip).matches()) {
                throw new IllegalArgumentException("유효하지 않은 CIDR 형식입니다.");
            }

            this.ip = ip;
            this.baseIpToLong = ipToLong(cidrParts[0]);
            this.prefixLength = Integer.parseInt(cidrParts[1]);
        }

        private boolean isInRange(long ip) {
            // 서브넷 마스크 계산
            long mask = (0xFFFFFFFFL << (32 - prefixLength));
            // 입력 IP 주소에 서브넷 마스크를 적용한 결과
            long maskedInputIP = ip & mask;

            // 두 IP 주소가 동일한 네트워크인지 판별
            return maskedInputIP == baseIpToLong;
        }

        /**
         * IP 문자열을 long 값으로 변환
         *
         * @param ip 변환할 IP
         * @return long 으로 변환된 IP
         */
        private static long ipToLong(String ip) {
            try {
                InetAddress inetAddress = InetAddress.getByName(ip);
                byte[] addressBytes = inetAddress.getAddress();
                long result = 0;
                for (byte b : addressBytes) {
                    result = result << 8 | (b & 0xFF);
                }
                return result;
            } catch (UnknownHostException e) {
                log.error("[ipToLong] ip : {}", ip);
                // IP 의 long 표현 또는 유효하지 않은 경우 0을 반환
                return 0;
            }
        }
    }

    /**
     * IP  지정된 프록시 범위 중 하나에 있는지 확인
     *
     * @param ip 확인할 IP 를 확인할 대상
     * @return 주어진 IP 가 지정된 범위 중 하나에 있으면 true, 그렇지 않으면 false 를 반환
     */
    public static boolean isInRange(@NonNull String ip) {
        long ipToLong = TrustIP.ipToLong(ip);

        for (var trustIP : IP_RANGES) {
            if (trustIP.isInRange(ipToLong)) {
                return true;
            }
        }

        return false;
    }
}
  1. 서브넷 마스크 계산
  2. 입력 IP 주소에 서브넷 마스크를 적용한 결과(입력 IP 이 기본 네트워크 IP 주소)
  3. 입력 IP 주소에 서브넷 마스크를 적용한 결과와 기본 네트워크 IP 주소가 동일한지 판별

현재 IP 가 IP_RANGES 에 속하는지 판단하는 메서드는 다음과 같이 구현하면 된다.

    @ParameterizedTest
    @ValueSource(strings = {"127.0.0.0", "x.x.x.x""})
    @DisplayName("IP 주소 범위에 해당되는 경우, true 를 리턴하라.")
    void isInRangeTest(String ip) {

        Assertions.assertThat(TrustedProxiesChecker.isInRange(ip)).isTrue();
    }

정리

  • CIDR 계산 방식으로 통해 두 IP 가 동일한 네트워크에 속하는지 판별이 가능하다.
  • 간단하게 계산만 필요하다면 아래 사이트를 이용해보자.
    IPv4 / IPv6 CIDR 계산기

References

profile
기록을 통한 성장을

2개의 댓글

comment-user-thumbnail
2023년 12월 18일

쉽고 깔끔하게 잘 설명해주셔서 오늘 하루 또 배워 갑니다. 🙇‍♂️
덤으로 코드까지...👏

1개의 답글