기본적으로 알려져있는 포트인 3000번으로 서비스를 배포해놓았더니, 특정 IP에서 공격에 가까운 패킷 전송을 시도하였다.
클라이언트 개발자분들도 아니고, 서버 개발자분들도 아니라고 하는 것을 보아하니 봇이 3000포트가 열려있는 아이피를 조회하여
접속 시도한다는 느낌을 받았다.
생각한 방안은 두 개다.
1. 포트를 바꾼다.
2. 정해진 패킷 형식과 다른 시도를 N번 이상 시도 시 해당 IP를 블락처리 시킨다.
최종적으로는 둘 다 할 것이지만, 일단은 추후에 악성 이용자 밴처리를 생각해서 IP밴을 생각했다.
근데 우리는 모바일 게임이다.
모바일의 IP 변경조건은 다음과 같다
아.. IP밴은 공격 방지용으로만 쓸 수 있겠구나.. 그래도 없는 것보단 나을 것 같아서 만들어봤다.
Node JS 는 Class: net.BlockList 를 내장으로 지원하고 있다.
const blockList = new net.BlockList();
blockList.addAddress('123.123.123.123');
blockList.addRange('10.0.0.1', '10.0.0.10');
blockList.addSubnet('8592:757c:efae:4e45::', 64, 'ipv6');
console.log(blockList.check('123.123.123.123')); // Prints: true
console.log(blockList.check('10.0.0.3')); // Prints: true
console.log(blockList.check('222.111.111.222')); // Prints: false
// IPv6 notation for IPv4 addresses works:
console.log(blockList.check('::ffff:7b7b:7b7b', 'ipv6')); // Prints: true
console.log(blockList.check('::ffff:123.123.123.123', 'ipv6')); // Prints: true
위 사이트에서 예시로 차단 방법에 대하여 설명하고 있다.
사이트 참고하여 아래와 같이 작성하였다.
import net from 'net';
import { onConnection } from './events/onConnection.js';
import { initServer } from './init/initServer.js';
import { config } from './config/config.js';
export const blockList = new net.BlockList();
const server = net.createServer(onConnection);
initServer().then(() => {
server.listen(config.server.port, config.server.host, () => {
console.log(`SERVER ON - ${config.server.host} : ${config.server.port}`);
});
});
export 시켜서 다른 파일에서도 참조할 수 있게 설정해주었다.
export function readBanList() {
try {
const data = fs.readFileSync(filePath, 'utf8');
return JSON.parse(data);
} catch (error) {
logger.error('Error reading the ban list:', error);
return null;
}
}
export const loadBanList = async () => {
const banList = await readBanList();
banList.banned_ips.forEach((ban) => {
blockList.addAddress(ban.ip);
});
};
서버가 실행될 때 banned_ips.json에서 차단된 IP를 읽어 blockList에 등록시킨다.
export const onConnection = (socket) => {
const remoteAddress = socket.remoteAddress;
if (blockList.check(remoteAddress)) {
console.log('Blocked connection from', remoteAddress);
handleErr(socket, { code: ERR_CODES.SOCKET_ERR, message: 'Your IP is banned' });
socket.end();
} else {
console.log('Allowed connection from', remoteAddress);
socket.buffer = Buffer.alloc(0);
socket.clientId = uuidV4();
socket.sequence = 0;
socket.on('data', onData(socket));
socket.on('end', onEnd(socket));
socket.on('error', onError(socket));
}
};
연결 시 소켓의 IP주소를 확인하고 blockList에 있는지 확인하여 여부에 따라 실행한다.
차단할 아이피 등록하면 소켓 연결 거부하니까 언제 이것을 활용할 것인가..
맨 위에 올려놓은 사진을 보면 receivePacket 인자값 확인하라는 멘트가 보일 것이다.
저 부분에 N번 시도 시 카운트 하여 밴 리스트에 등록하는 과정을 가져보겠다.
function writeBanList(banList) {
try {
const data = JSON.stringify(banList, null, 2);
fs.writeFileSync(filePath, data, 'utf8');
} catch (error) {
logger.error('Error write the ban list:', error);
return null;
}
}
export const addBanList = (socket) => {
const remoteAddress = socket.remoteAddress;
blockList.addAddress(remoteAddress);
const banList = readBanList() || { banned_ips: [] };
banList.banned_ips.push({ ip: remoteAddress });
writeBanList(banList);
};
export const recvPacket = (socket, packet) => {
if (!packet) {
++socket.illegalCount;
if (socket.illegalCount >= 5) {
addBanList(socket);
socket.end();
}
throw new Error('receivePacket 인자값 확인하세요.');
}
packetManager.enQueueRecv(socket, packet);
};
정상적으로 패킷이 처리되지 않았을 경우 소켓의 illegalCount를 추가시키고, 그 횟수가 5번이 넘었을 경우
addBanList 함수를 실행시키고 종료시킨다.