[node.js] DNS Proxy Server

By | 2016년 10월 5일

dns server 기능을 하되 특정도메인의 요청의 경우 원하는 아이피를 돌려주는 proxy server를 만들어보도록 한다.

개발 테스트시에 유용할때가 있어 구현해보았다.

node.js로 만들며 native-dns 라는 훌륭한 module이 있어 별어려움없이 구현이 가능하다.

native-dns https://github.com/tjfontaine/node-dns

크게 두가지 기능이면 구현이 가능한데 둘다 이 모듈을 이용하면된다.

  1. 먼저 클라이언트에서 domain 질의를 요청하면 응답하는 기능.

  2. 다른 dns server에 질의를 요청해서 IP정보를 받아오는 기능.

응답 소스

var server = dns.createServer();

server.on('request', function (request, response) {
    var ip = request.address.address;
    var domain = request.question[0].name;

    response.answer.push(dns.A({
        name: domain,
        address: '172.17.50.6',  // 원하는 IP
        ttl: 600,
    }));
    response.send();
});

질의 소스

var question = dns.Question({
    name: domain,
    type: 'A',
});

var start = Date.now();

var req = dns.Request({
    question: question,
    server: { address: '8.8.8.8', port: 53, type: 'udp' },
    timeout: 1000,
});

req.on('timeout', function () {
    console.log('Timeout in making request');
    callback(true, 'Timeout in making request');
});

req.on('message', function (err, answer) {
    addrs = [];
    answer.answer.forEach(function (a) {
        if (a.type==1) {
            addrs.push(a.address);
        }
    });
    callback(false, addrs);
});

req.on('end', function () {
    //var delta = (Date.now()) - start;
    //console.log('Finished processing request: ' + delta.toString() + 'ms');
});

req.send();

아래와 같은 호스트 파일을 구성한다. 기존에 OS에서 사용하는 호스트파일과 똑같다.

www.naver.com 을 요청했을때 실제 아이피인 183.111.23.42 대신에 15.60.68.210를 응답하도록한다.

호스트 정보파일

15.60.68.210    www.naver.com
15.60.68.211    www.daum.net
15.60.68.175    guoo.net
15.60.68.211    memo.heyo.me

여기에 watcher를 추가하여 호스트파일 추가/삭제/변경시 새롭게 읽는 기능만 추가한다.

watcher는 chokidar module을 이용한다.

chokidar https://github.com/paulmillr/chokidar

전체 소스

var chokidar = require('chokidar');
var fs = require('fs');
var path = require('path');

var watcher = chokidar.watch('file, dir, or glob', {
    ignored: /[\/\\]\./, persistent: true
});

var static_domains = {};

function parseHostFile(target, file) {
    if (!static_domains[target]) {
        static_domains[target] = [];
    }
    console.log(file);
    var hostsraw = fs.readFileSync(file, 'utf8');
    var hosts = hostsraw.split('\n');
    hosts.forEach(function(host) {
        if (host && host.substring(0,1)=='#') {
            return;
        } 
        host.replace(/([^\s]+)\s+([^\s]+)/g, function(m, ip, domain, port) {
            if (static_domains[target]===null) {
                static_domains[target] = {};
            }
            // static
            static_domains[target][domain] = ip;
        });
    });
}

function unloadHostFile(target) {
    delete static_domains[target];
}

watcher
  .on('add', function(file) {
      console.log('File '+file+' has been added');
      console.log(path.basename(file));
      var target = path.basename(file);
      unloadHostFile(target);
      parseHostFile(target, file);
      })
  .on('change', function(file) {
      console.log('File '+file+' has been changed');
      var target = path.basename(file);
      unloadHostFile(target);
      parseHostFile(target, file);
      })
  .on('unlink', function(file) {
      console.log('File '+file+' has been removed');
      var target = path.basename(file);
      unloadHostFile(target);
      });

watcher.add('hosts/*');

/*
setInterval(function() {
    console.log('-domains------------------');
    console.log(static_domains);
}, 5000);
*/

var dns = require('native-dns'),
  util = require('util');

function do_question(domain, callback) {
    var question = dns.Question({
        name: domain,
        type: 'A',
    });

    var start = Date.now();

    var req = dns.Request({
        question: question,
        server: { address: '8.8.8.8', port: 53, type: 'udp' },
        timeout: 1000,
    });

    req.on('timeout', function () {
        console.log('Timeout in making request');
        callback(true, 'Timeout in making request');
    });

    req.on('message', function (err, answer) {
        addrs = [];
        answer.answer.forEach(function (a) {
            if (a.type==1) {
                addrs.push(a.address);
            }
        });
        callback(false, addrs);
    });

    req.on('end', function () {
        //var delta = (Date.now()) - start;
        //console.log('Finished processing request: ' + delta.toString() + 'ms');
    });

    req.send();
}

var server = dns.createServer();

server.on('request', function (request, response) {
    if (request.question[0].type != 1) {
        response.send();
        return;
    }

    var ip = request.address.address;
    var domain = request.question[0].name;

    console.log(ip);

    if (static_domains[ip] && static_domains[ip][domain]) {
        // static
        console.log('static');
        response.answer.push(dns.A({
            name: domain,
            address: static_domains[ip][domain],
            ttl: 600,
        }));
        response.send();
    } else {
        // real name server
        console.log('default');
        console.log(domain);
        do_question(domain, function(err, address) {
            if (!err) {
                console.log(address);
                address.forEach(function(item) {
                    response.answer.push(dns.A({
                        name: domain,
                        address: item,
                        ttl: 600,
                    }));
                });
                response.send();
            } else {
                response.send();
            }
        });
    }

});

server.on('error', function (err, buff, req, res) {
    console.log(err.stack);
});

server.serve(53);

One thought on “[node.js] DNS Proxy Server

  1. 밍코

    이거 보고 데이터베이스 연동해서 프록시 서버 만들었는데 1.1.0.6으로 요청하니까 이런게 뜨면서 오류가 나네요. 어떻게 고치나요?

    Error: ipaddr: the address has neither IPv6 nor IPv4 format
    at Object.ipaddr.parse (/home/pi/MincoDNS/node_modules/ipaddr.js/lib/ipaddr.js:929:19)

    이메일 admin@minco.kro.kr
    디스코드 ! (띄어쓰기x30) !#0123

    Reply

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다