개요
안녕하세요 빈코입니다! 오늘은 Java에서 IP 유효성 검사 및 다양한 Util 함수들에 대해서 포스팅하려 합니다. IP 유효성 검사는 웹 애플리케이션에서 사용자로부터 IP 주소를 입력받을 때나 외부 API에서 IP 주소를 사용할 때 등 생각보다 많은 곳에서 사용됩니다😁
유효성 검사 로직만 살펴보기에는 포스팅 내용이 짧을 것 같아서 제가 주로 사용해왔던 IP 관련 함수들도 소개하려고 합니다. 그럼 하단에서 조금 더 자세히 살펴볼까요?
IPv4 VS IPv6📙
첫 번째로 알아야 할 점은 IP는 IPv4와 IPv6로 나뉩니다. IPv4는 32비트 주소 체계를 사용하며, 4개의 옥텟(ex: 127.0.0.1)으로 표현되고, IPv6는 128비트 주소 체계를 사용하여 8개의 16진수 블록으로 표현됩니다(ex: xxxx.xxx.xxxx.xxxx.xxxx.xxxx.xxxx.xxxx)
저희가 주로 사용하는 버전은 IPv4 방식이지만, 사실 IPv6는 4버전보다 보안 기능인 IPsec이 기본적으로 통합되어 있어, 데이터 전송 중 보안이 강화되는 장점이 있고, IPv4보다 헤더가 간소화되어 있어 패킷 처리 속도가 비교적 높습니다. 그렇다고 해서 IPv6를 많이 사용하느냐? 또 그런 건 아닙니다.
IPv6는 IPv4 보다 더 나은 기능과 주소 공간을 제공하지만, 전환 과정에서 발생하는 비용, 호환성 문제, 기술적 어려움 등으로 여전히 많은 기업이 IPv4를 사용합니다. 대게 IPv6를 사용하는 기업도 있으니 두 개의 유효성 검사 모두 알아놔야겠죠?
IP 유효성 검사📘
사실 IP 유효성 검사는 따로 공부하지 않고 코드만 복사해서 가져다 사용해도 됩니다😊
첫 번째는 String 방식으로 유효성 검사 하는 방법과, 두 번째는 Pattern 클래스를 이용해서 유효성 검사 하는 방법을 소개할게요!
1) IPv4 유효성 검사 - String 방식
public static boolean ipV4Chk(String ip) {
String ipRegex = "^(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}$";
return ip.matches(ipRegex);
}
2) IPv6 유효성 검사 - String 방식
public static boolean ipV6Chk(String ip) {
String ipRegex = "^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$";
return ip.matches(ipRegex);
}
3) IPv4 유효성 검사 - Pattern 방식
private static final String IPv4_PATTERN =
"^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\." +
"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\." +
"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\." +
"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$";
public static boolean isValidIPv4(String ip) {
return Pattern.matches(IPv4_PATTERN, ip);
}
4) IPv6 유효성 검사 - Pattern 방식
private static final String IPv6_PATTERN =
"^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|"+
"([0-9a-fA-F]{1,4}:){1,7}:|"+
"([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|"+
"([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|"+
"([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|"+
"([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|"+
"([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|"+
"[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|"+
":((:[0-9a-fA-F]{1,4}){1,7}|:)|"+
"fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|"+
"::(ffff(:0{1,4}){0,1}:){0,1}"+
"((25[0-5]|(2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\\.){3}"+
"(25[0-5]|(2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))|"+
"([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\\.){3}"+
"(25[0-5]|(2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))))$";
public static boolean isValidIPv6(String ip) {
return Pattern.matches(IPv6_PATTERN, ip);
}
이 밖의 IP 자주 사용하는 함수📒
사실 이 포스팅의 메인은 유효성 검사보다 제가 자주 사용하는 함수를 소개하는 파트인 것 같아요. 유효성 검사는 사실 현재 웹상에서 너무 많은 정보가 있어서 메리트가 없다고 생각이 들었습니다😥
첫 번째로 제가 자주 사용하는 IP 관련 함수는 IPv4를 Long 형태로 변환하는 것입니다. 주로 IP는 유효성 검사나 크기 비교를 많이 하게 되는데, IP 끼리 크기 비교를 하기 위해서는 Long 타입으로 변환하는 과정이 필요합니다.
1) IPv4 -> Long 타입 변환
public static long ipV4ToLong(String ip) {
String[] ipArr;
long ipToLong = 0;
if (!ipV4Chk(ip)) {
return ipToLong;
}
try {
ipArr = ip.split("\\.");
int power = 3;
for (String ipOctet : ipArr) {
ipToLong += ( Integer.parseInt(ipOctet) % 256 * Math.pow(256, power));
power --;
}
} catch (Exception e) {
e.printStackTrace();
}
return ipToLong;
}
ipV4ToLing 함수는 매개변수로 String ip를 넘겨받습니다. 이후에 ipArr이라는 String 배열을 선언하고, 해당 배열은 추후 IP 주소를 각 옥텟별로 나누어 저장할 예정입니다. ipToLong은 변환된 long 값의 ip를 저장할 변수로 초기값은 0으로 설정했습니다.
그다음으로는 넘겨받은 ip가 유효한지에 대한 조건을 수행하고 만약 유효하지 않다면 초기값인 0으로 리턴합니다.
유효하다면 ip를 "." 기준으로 나누어 ipArr 배열에 각 옥텟을 저장합니다.
IPv4 주소의 각 옥텟은 8비트이기 때문에, 이를 long 값으로 변환하기 위해서는 옥텟마다 256의 제곱수를 곱해야 합니다. 여기서 power는 이를 위해 사용하는 변수로, 첫 번째 옥텟부터 256^3, 256^2, 256^1, 256^0 순으로 곱해지기 위해 사용됩니다.
반복문을 통해 ip 배열을 돌면서 Integer.parseInt(ipOctet) % 256을 통해 각 옥텟의 값이 0에서 255 사이에 있는지 확인하고, 이전에 설명한 제곱을 진행합니다. 그리고 결과값을 ipToLong 값에 더해줍니다.
이런 일련의 과정을 통해 매개변수로 받은 String ip는 각 옥텟별로 모두 더해져 Long 형태로 반환됩니다.
2) IPv4 크기 비교
public static boolean compareIPv4(String startIP, String endIP) {
long startIPLong = ipV4ToLong(startIP);
long endIPLong = ipV4ToLong(endIP);
if (startIPLong == 0 || endIPLong == 0) {
return false;
}
if (startIPLong <= endIPLong) {
return true;
} else {
return false;
}
}
compareIPv4는 매개변수로 넘겨받은 startIP와 endIP의 크기를 비교합니다.
이전에 만든 ipV4ToLong 함수를 이용하여 각각의 ip를 Long 형태로 변환 후 비교를 시작합니다. 먼저, 각각의 변환 값이 0이면 ip가 유효하지 않기 때문에 false를 리턴하고, 그렇지 않다면 startIP가 endIP보다 작은지 비교합니다.
IP 심화📚
마지막으로 제가 실무에서 겪었던 IP 관련 문제를 해결한 과정을 포스팅하려 합니다. 이럴 경우는 많이 없겠지만, 로직으로는 어느 배열에서나 써먹을 수 있을 것 같아서 포스팅합니다 :)
시나리오
1) 여러 그룹이 있는데, 각 그룹마다 start_ip와 end_ip가 있음. 즉, 그룹별 IP 범위가 있는 상황
2) 그룹이 8000개가 넘는 상황
3) 엑셀에 각 그룹의 정보가 칼럼별로 다 담겨 있는데, 해당 엑셀을 이용하여 데이터를 넣을 때 각 그룹마다 IP 범위가 겹치면 안 되는 조건
그룹 이름 | 시작 IP | 끝 IP | |
1 | A | 10.54.1.10 | 10.54.1.19 |
2 | B | 10.54.2.10 | 10.54.2.19 |
... | ... 8000개 | ... | ... |
어떻게 해야 할까요? 처음에는 그룹 데이터를 1개씩 DB에 넣고, 이미 존재하는 IP 대역인지 확인하고 그 다음 그룹을 넣는 방식으로 진행을 하려 했는데 그런 식으로 진행하면 결국엔 service 단에서 쿼리를 그룹의 개수만큼 호출해야 해서 비효율적이라고 생각을 했습니다.
그러다 문득 Java 배열을 이용해보자! 라는 생각이 들었는데요. 만약 100개의 그룹이 있다고 가정하면, 각 그룹을 반복문을 돌고 다시 하위에서 2중 반복문을 돌면서 다른 그룹의 IP 대역과 비교한다면? 그러면 100 X 100 = 10000번의 반복문이 돌아가네요. 이건 안돼..
그러면 각 시작 IP를 순서대로 정렬후에 그다음 요소만 비교하면 되지 않을까? 즉 A그룹은 B그룹이랑만 비교, B그룹은 C그룹이랑만 비교... 이런식으로요!
엑셀을 읽어들일 때 각 그룹의 시작 IP와 끝 IP는 이전에 만든 IP 비교 함수를 이용하여 비교하여 유효성 체크를 하니깐, 각 그룹의 끝 IP는 시작 IP보다 큰 건 이미 확인이 되었으니, 각 그룹의 시작 IP만 오름차순으로 정렬해서 순차적으로 비교한다면 이중 반복문을 사용할 때보다 훨씬 더 효율적으로 비교가 가능할 것 같았습니다.
// start_ip 기준으로 오름차순 정렬
List<Map<String,String>> groupList = new ArrayList<>(); // 데이터 8000개 들어가있다는 가정
groupList.sort(Comparator.comparing(g -> ipV4ToLong(g.get("start_ip")))); // Java Comparator를 통해 오름차순 정렬
// 현재 그룹의 start_ip와 end_ip가 다음 그룹의 IP 범위 내에 있는지 확인
for (int i = 0; i < groupList.size() - 1; i++) {
Map<String, String> currentGroup = groupList.get(i);
Map<String, String> nextGroup = groupList.get(i + 1);
long currentStartIp = ipV4ToLong(currentGroup.get("start_ip"));
long currentEndIp = ipV4ToLong(currentGroup.get("end_ip"));
long nextStartIp = ipV4ToLong(nextGroup.get("start_ip"));
long nextEndIp = ipV4ToLong(nextGroup.get("end_ip"));
// 현재 그룹의 start_ip가 다음 그룹의 범위 내에 있는지 체크
if (currentStartIp >= nextStartIp && currentStartIp <= nextEndIp) {
return false;
}
// 현재 그룹의 end_ip 다음 그룹의 범위 내에 있는지 체크
if(currentEndIp >= nextStartIp && currentEndIp <= nextEndIp){
return false;
}
이러면 그룹이 8000개가 있다고 해도, 반복문은 그룹의 개수 - 1 만큼만 돌면 되기 때문에 더 효과적으로 코드를 구성할 수 있었습니다. 배열의 위대함을 다시 한번 느끼게 된 계기였네요😁
마치며
지금까지 Java에서 사용하는 IP 유효성 검사와 여러 함수들을 알아보았습니다. 실무에서 많이 사용하기 때문에 꼭 숙지하시길 바라요🙇♂️
👨👩👦👦 오픈채팅방 운영
취업을 준비하는 예비 개발자분들을 위한 질문&답변할 수 있는 공간을 만들었습니다. 취업과 이직을 하기 위해서 어떤 걸 중점적으로 준비해야 하는지부터 포트폴리오&이력서 작성법 등 다양한 질문들을 받고 답변을 드립니다. 참여하셔서 다양한 정보 얻고 가시면 좋을 것 같네요😁
참여코드 : 456456
'TIL' 카테고리의 다른 글
[JS] find,some,filter,map 등의 고차 함수 활용하기 (0) | 2024.10.11 |
---|---|
js Shallow Copy(얕은 복사)와 Deep Copy(깊은 복사)의 차이 (0) | 2024.10.07 |
Java Spring Scheduler 정의 및 사용법 예시 (1) | 2024.09.13 |
Java HTTP Server 쉽게 이해하기 및 예제 (1) | 2024.09.09 |
SMTP 정의 및 Java에서 메일 발송 기능 활용해보기(간단 예시) (2) | 2024.09.04 |