내부 로직을 다 이해해도 조금만 시간 지나면 까먹어서 정리했다. 코드는 IPv4 만 구현되어 있다.
서브넷 마스크, 넷마스크 용어를 엄격하게 구분져서 쓰지 않았다. 엄격히 구분할 필요가 없다고 판단했다.
먼저 ip 가 서로 같은지 다른지 비교를 해보자. 192.168.254.252와 192.168.254.253 은 당연히 다르다. string equal 을 이용해 바로 확인이 가능하다.
그런데 192.168.254.252/13 와 같이 subnetmask 가 같이 있는 경우, 192.175.254.253 이 포함되는지 여부는 바로 알기 어렵다(답은 포함됨).
코드를 보면서 하나씩 살펴보자. 먼저 IpAddressMatcher 생성자로 ip 와 subnetmask 문자열을 / 구분자로 함께 전달한다.
IpAddressMatcher ipAddressMatcher = new IpAddressMatcher("192.168.254.252/13");
다음 코드는 생성자 내부 로직이다. / 구분자를 기준으로 ipAddressString 은 192.168.254.252 가 되고 netMaskString 는 13 가 된다. / 가 없으면 netmask 는 -1이 된다(이 경우 단순 ip 문자 비교가 이뤄진다).
public IpAddressMatcher(String ipAddress) throws NumberFormatException {
if (ipAddress.indexOf('/') > 0) {
String[] addressAndMask = StringUtils.split(ipAddress, "/");
if (addressAndMask.length != 2) {
throw new NumberFormatException("Invalid CIDR format");
}
String ipAddressString = addressAndMask[0];
String netMaskString = addressAndMask[1];
makeIpAddress(ipAddressString);
makeNetMask(netMaskString);
} else {
makeIpAddress(ipAddress);
this.netmask = -1;
}
}
다음으로 makeIpAddress 메서드를 통해서 int 타입의 ipAddress를 얻는다.
private void makeIpAddress(String ipAddressString) {
String[] octets = ipAddressString.split("\\.");
if (octets.length != 4) {
throw new NumberFormatException("Invalid IP address: " + ipAddressString);
}
int i = 24;
this.ipAddress = 0;
for (int n = 0; n < octets.length; n++) {
int value = Integer.parseInt(octets[n]);
if (value != (value & 0xff)) { // 0xff는 255
throw new NumberFormatException("Invalid IP address: " + ipAddressString);
}
this.ipAddress += value << i;
i -= 8;
}
}
192.168.254.252 문자열을 . 구분자로 분리해서 octets 배열에 담고 for문을 돌리면서 각각의 int 값을 얻어 ipAddress에 더한다.
아래에 과정별 로그가 있는데 먼저 192는 Integer.parseInt를 통해 11000000 가 된다. 그리고 i는 24이므로 24만큼 왼쪽으로 shift 되서 11000000000000000000000000000000 가 된다(빈자리는 0으로 채워짐)
value : 11000000
i : 24
ipAddress : 11000000000000000000000000000000
value : 10101000
i : 16
ipAddress : 11000000101010000000000000000000
value : 11111110
i : 8
ipAddress : 11000000101010001111111000000000
value : 11111100
i : 0
ipAddress : 11000000101010001111111011111100
다음번은 168에 해당하는 10101000 가 16만큼 shift 된다. 그리고 마지막 for문까지 마치면 ipAddress 는 이진수로 표현된 값을 얻는다(11000000101010001111111011111100).
다음은 subnet mask 를 int 타입으로 얻는 과정이다. netMaskString 13이라는 숫자는 1이 13개 있다는 뜻이다(왼쪽부터).
private void makeNetMask(String netMaskString) {
int netMaskInteger = Integer.parseInt(netMaskString);
if (netMaskInteger < 1 || netMaskInteger > 32) {
throw new NumberFormatException("netMaskInteger value is wrong");
}
// 최소단위 A 클래스 255.0.0.0
if (netMaskInteger < 8) {
throw new NumberFormatException("Netmask CIDR can not be less than 8");
}
this.netmask = 0xffffffff; // 11111111.11111111.11111111.11111111
this.netmask = this.netmask << (32 - netMaskInteger);
}
netmask 변수는 먼저 1로 다 채우고 32 - 13 인 19개를 shift 함으로써 0으로 채우고 13개가 1이 되게 한다.
즉 11111111111110000000000000000000 이 된다.
이제 192.168.254.252/13 범위를 구해보자.
먼저 netmask 가 -1이면(subnetmask가 없으면) 범위가 없으니 그냥 ip를 리턴한다.
그 후 numberOfBits 변수를 얻는데 netmask 가 11111111111110000000000000000000 다. 0이 나올때까지 for문 돌려서 값을 증가시키므로 13이 된다. 특별한건 없고 아까 subnetmask 문자열을 다시 얻고 싶어서 있는 로직이다.
public String getHostAddressRange() {
if (this.netmask == -1) {
return convertIpToString(this.ipAddress);
}
int numberOfBits;
for (numberOfBits = 0; numberOfBits < 32; numberOfBits++) {
if ((this.netmask << numberOfBits) == 0) {
break;
}
}
int numberOfIps = 0;
for (int n = 0; n < (32 - numberOfBits); n++) {
numberOfIps = numberOfIps << 1;
numberOfIps = numberOfIps | 0x01;
}
// 192.168.254.252/13
// 11000000.10101000.11111110.11111100 (ipAddress)
// 11111111.11111000.00000000.00000000 (netmask)
// 11000000.10101000.00000000.00000000 (&연산. networkIp)
int networkIp = this.ipAddress & this.netmask;
// 11000000.10101000.00000000.00000000 (networkIp)
// 00000000.00000111.11111111.11111111 (numberOfIps) netmask를 8보다 제한하므로 더해서 단위 초과는 발생안함
// 11000000.10101111.11111111.11111111 (networkIp + numberOfIps)
String firstIP = convertIpToString(networkIp);
String lastIP = convertIpToString(networkIp + numberOfIps);
return firstIP + " - " + lastIP;
}
private String convertIpToString(int ip) {
StringBuffer sb = new StringBuffer(15);
for (int shift = 24; shift > 0; shift -= 8) {
// process 3 bytes, from high order byte down.
sb.append(((ip >>> shift) & 0xff)); // >>> 연산자는 오른쪽으로 이동시킬 때 부호에 상관없이 항상 0으로 채움
sb.append('.');
}
sb.append((ip & 0xff));
return sb.toString();
}
다음 numberOfIps 변수 얻는 과정을 보자. 32 - 13인 19번 for문을 돌면서 numberOfIps 왼쪽 shift 하고 1을 추가한다(or 연산자로).
그 과정이 아래 로그로 표현하면 이해가 빠르다.
numberOfIps : 0
numberOfIps : 1
numberOfIps : 10
numberOfIps : 11
numberOfIps : 110
numberOfIps : 111
numberOfIps : 1110
numberOfIps : 1111
numberOfIps : 11110
numberOfIps : 11111
numberOfIps : 111110
numberOfIps : 111111
numberOfIps : 1111110
numberOfIps : 1111111
numberOfIps : 11111110
numberOfIps : 11111111
numberOfIps : 111111110
numberOfIps : 111111111
numberOfIps : 1111111110
numberOfIps : 1111111111
numberOfIps : 11111111110
numberOfIps : 11111111111
numberOfIps : 111111111110
numberOfIps : 111111111111
numberOfIps : 1111111111110
numberOfIps : 1111111111111
numberOfIps : 11111111111110
numberOfIps : 11111111111111
numberOfIps : 111111111111110
numberOfIps : 111111111111111
numberOfIps : 1111111111111110
numberOfIps : 1111111111111111
numberOfIps : 11111111111111110
numberOfIps : 11111111111111111
numberOfIps : 111111111111111110
numberOfIps : 111111111111111111
numberOfIps : 1111111111111111110
numberOfIps : 1111111111111111111
즉 numberOfIps 는 1이 19개인 값이다.
이제 범위의 출발점인 networkIp 를 먼저 구한다.
// 192.168.254.252/13
// 11000000.10101000.11111110.11111100 (ipAddress)
// 11111111.11111000.00000000.00000000 (netmask)
// 11000000.10101000.00000000.00000000 (networkIp)
int networkIp = this.ipAddress & this.netmask;
아까 구한 ipAddress 와 netmask 를 & 연산를 하면 왼쪽부터 13개 까지만 ipAddress와 일치하게 된다. 나머지는 0이다.
범위의 마지막인 lastIp를 구한다. 위에서 구한 networkIp (11000000.10101000.00000000.00000000) 와 1이 19개인 numberOfIps 를 더한다.
// 11000000.10101000.00000000.00000000 (networkIp)
// 00000000.00000111.11111111.11111111 (numberOfIps) netmask를 8보다 제한하므로 더해서 단위 초과는 발생안함
// 11000000.10101111.11111111.11111111 (networkIp + numberOfIps)
String lastIP = convertIpToString(networkIp + numberOfIps);
String lastIP2 = convertIpToString(networkIp | numberOfIps); // OR 연산해도 결과는 같음
결국 범위는 192.168.0.0 - 192.175.255.255 이고 192.175.254.253 은 포함된다.
IpAddressMatcher ipAddressMatcher = new IpAddressMatcher("192.168.254.252/13");
assertThat(ipAddressMatcher.contains("192.175.254.253"), is(true));
그런데 networkIp와 numberOfIps 를 더하는 것보다는 OR 연산자를 쓰는게 더 좋아보인다. networkIp는 ipAddress 와 subnet mask 로 AND 연산자가 수행되서 앞의 13개만 빼고 나머지는 다 0이 된다. numberOfIps 는 뒤 19개가 1이니 OR 연산자를 통해서도 쉽게 얻을 수 있다.
마지막으로 위 contains 메서드로 어떻게 포함되는지 안되는지 알수 있는지 확인해보자.
먼저 netmask 값이 없다면 단순 string equal 비교가 된다.
그다음은 포함되는지 여부를 알고 싶은 checkingIpAddressString 을 int 타입으로 변환하는 과정이다. 아까와 과정은 똑같으므로 설명은 생략한다.
public boolean contains(String checkingIpAddressString) {
if (this.netmask == -1) {
return StringUtils.equals(convertIpToString(this.ipAddress), checkingIpAddressString);
}
int checkingIpAddress = 0;
String[] octets = checkingIpAddressString.split("\\.");
if (octets.length != 4) {
throw new NumberFormatException("Invalid IP address: " + checkingIpAddressString);
}
int i = 24;
for (int n = 0; n < octets.length; n++) {
int value = Integer.parseInt(octets[n]);
if (value != (value & 0xff)) {
throw new NumberFormatException("Invalid IP address: " + checkingIpAddressString);
}
checkingIpAddress += value << i;
i -= 8;
}
// IpAddressMatcher ipAddressMatcher = new IpAddressMatcher("192.168.254.252/13");
// assertThat(ipAddressMatcher.contains("192.175.254.253"), is(true));
// 11000000.10101000.11111110.11111100
// 11111111.11111000.00000000.00000000
// 11000000.10101000.00000000.00000000 (this.ipAddress & this.netmask 결과)
// 11000000.10101111.11111110.11111101
// 11111111.11111000.00000000.00000000
// 11000000.10101000.00000000.00000000 (checkingIpAddress & this.netmask 결과)
return (this.ipAddress & this.netmask) == (checkingIpAddress & this.netmask);
// 결국 ipAddress 와 checkingIpAddress 는 netmask 부분까지만 같으면 된다.
}
ipAddress(11000000.10101000.11111110.11111100) 와 checkingIpAddress(11000000.10101111.11111110.11111101) 에서 netmask 부분까지만 같으면 포함된것이다.
'Java' 카테고리의 다른 글
The dark side of distributed systems (0) | 2021.11.04 |
---|---|
리스트 객체를 특정 key 기준으로 grouping 그리고 merge (0) | 2021.09.20 |
재귀 호출 최적화(Tail-Call) (2) | 2021.05.11 |
Singleton(면접단골질문) (0) | 2021.05.09 |
Optional 형태가 가지는 의미(Monad) (0) | 2021.05.06 |