관리 메뉴

HAMA 블로그

[이더리움에서 배우는 Go언어] nat 옵션 이야기 - (1) 본문

Go

[이더리움에서 배우는 Go언어] nat 옵션 이야기 - (1)

[하마] 이승현 (wowlsh93@gmail.com) 2018. 12. 14. 13:56


1. [이더리움에서 배우는 Go언어]   chan chan 이란?
2. [이더리움에서 배우는 Go언어]   1급함수활용과 채널
3. 
[이더리움에서 배우는 Go언어]   nat 옵션 이야기 - (1)

nat 옵션 이야기 - (1)

이번 시리즈에서는 네트워크 인프라의 내부에서 사설IP로 동작하여 공인IP가 없는 노드끼리 어떻게 통신 할 수 있는지에 대해서 알아 본다. 첫번째 글에서는 간략한 정의를 내려보며  이더리움에서 사용되는 옵션들을 살펴볼 것이며 두번째 글에서는 extip와 upnp를 구체적으로 살펴보려 한다. (참고로 nat-pnp는 upnp와 비슷하므로 생략)


홀펀칭

홀펀칭(막힌 구멍을 뚫는) 필요한 이유는 피씨들이 모두 자신의 개인 고정 IP를 사용하는게 아니기 때문이다. 많은 경우 NAT라는 장비를 통해서 앞쪽에 대리자/게이트웨이를 두고, 내부 IP/PORT를 따로 가지고 있기 때문에, 클라이언트들 끼리 직접 연결은 불가능하며 외부와 통신하려면 내부아이디와 외부로 노출되는 IP/PORT간에 매핑등이 되어 뚤려 있어야 하는데 이렇게 뚤어 주는 것을 홀펀칭이라고 한다. 게임등에서 많이 사용되며 이더리움 또한 외부의 무작위 노드들과 서로 양방향 통신을 해야 하기 때문에 반드시 필요하다. 


NAT-PMP 와 UPNP

NAT-PMP 및 UPnP(Universal Plug And Play)는 특히 인터넷 애플리케이션이 홈 라우터 및 게이트웨이를 구성하여 수동 포트 전달 구성을 우회할 수 있도록 하는 기술이다. 동일한 개념이라고 보면 되며 NAT 포트 매핑의 자동화하는 서로 다른 구현 방식일 뿐이다.일반적으로 UPnP 프로토콜은 Windows/BSD/Linux 시스템에서 사용되고 NAT-PMP는 Apple 시스템에서 사용된다. 이러한 방법을 활용하려면 NAT-PMP 또는 UPnP 지원 하드웨어가 있어야 한다. 요즘은 많은 경우 지원한다. 

IpTime 에서 설정 모습

NAT 이란?

NAT동작방식은 위에 간단히 설명한 것처럼, 내부에서 외부로 패킷을 보낼 때, NAT장치는 위부IP와 매핑된 내부를 기록하고 있다가, 해당 외부IP를 통해서 패킷이 날라오면 그것을 해당 내부IP로 보내준다. NAT에 매핑기록이 없으면 보통 드롭된다. 이 얘기는 내부에서 보내야 매핑정보가 남는다는 것인데, 외부에서 내부의 프로그램으로 보낼 땐 어떻게 하냐? 그건 수동으로 매핑정보를 입력해야 한다. 즉 외부52번은 내부 129번으로 매핑한다라는 기록이 있으면, 129번으로 포워딩 해주면 된다. 이후 설명될 NAT-PMP, UPNP는 이 설정이자동으로 할당되며, (인바운드매핑)을 요청하여 기록되게 할 수 있다. 

NAT-PMP 란? 

NAT 포트 매핑 프로토콜 (NAT-PMP)은 사용자의 노력없이 NAT (Network Address Translation) 설정 및 포트 전달 구성을 자동으로 설정하기위한 네트워크 프로토콜입니다. 이 프로토콜은 NAT 게이트웨이의 외부 IPv4 주소를 자동으로 결정하고 응용 프로그램이 통신 매개 변수를 피어와 통신 할 수있는 수단을 제공합니다. NAT-PMP는 2005 년에 많은 NAT 라우터에서 구현 된보다 일반적인 ISO 표준 [2] 인터넷 게이트웨이 장치 프로토콜 대신 Apple에 의해 도입되었습니다. 이 프로토콜은 RFC 6886의 IETF (Internet Engineering Task Force)에 의해 RFC (정보 요청 요청)로 게시되었습니다. NAT-PMP는 UDP (User Datagram Protocol)를 통해 실행되며 포트 번호 5351을 사용합니다. 포트 전달은 일반적으로 STUN 방법을 사용하여 수행 할 수없는 활동을 허용하지 않으므로 내장 된 인증 메커니즘이 없습니다. STUN에 비해 NAT-PMP의 이점은 STUN 서버가 필요없고 NAT-PMP 매핑에 알려진 만료 시간이있어 응용 프로그램이 비효율적 인 연결 유지 패킷을 보내지 않도록 할 수 있다는 것입니다. NAT-PMP는 포트 제어 프로토콜 (PCP)의 전신입니다. [3]      - 위키발췌- 

UPNP란? 

유니버설 플러그 앤 플레이 (UPnP)는 UPnP 포럼이 공표한 컴퓨터 네트워크 프로토콜의 집합이다. UPnP의 목표는 장치들을 부드럽게 연결하고 가정 네트워크와 회사 환경의 기능(데이터 공유, 통신, 엔터테인먼트)을 단순화하는 것이다. UPnP는 개방된 인터넷 기반의 통신 표준 기반의 UPnP 장치 제어 프로토콜을 정의하고 출판함으로써 이를 달성한다. UPnP라는 용어는 컴퓨터에 직접 장치를 유동적으로 부착하는 기술인 플러그 앤 플레이에서 비롯한 것이다.  UPNP의 기본 프로토콜이 (SSDP)이 UDP 베이스이다.

이러한 NAT-PNP,UPNP에는 보안문제가 있는데, 이것은 여기서 다루는 범위 밖이라 생략한다.

이더리움에서의 홀펀칭 소스 위치 [소스]

이더리움에서는 노드디스커버리 즉 주변에 접속할 대상을 물색하는데 UDP를 사용하며 이때 홀펀칭이 사용되어 이후 프로토콜에 의한 데이터교환에서는 TCP를 사용한다. 이더리움 코어에서는 p2p 폴더 아래의 nat 폴더에 관련된 기능들을 제공하는 소스가 있다. 참고로 TCP,UDP 모두 디폴트 포트로 30303을 사용한다. 30301은 단지 bootnode에 의해서만 기본 UDP 디스커버리 포트로 사용된다.

이더리움에서 NAT 옵션 설정 

--nat value   NAT port mapping mechanism (any|none|upnp|pmp|extip:<IP>) (default: "any")

extip:77.12.33.4: 주어진 IP로 고정 매핑 되었다고 설정함. port는 수동매핑되있어야하고~
any   : 자동으로 먼저 탐지된 메커니즘을 사용. (디폴트)
upnp : 유니버셜 플러그앤 플레이 프로토콜 사용 
pmp  : 자동으로 감지된 게이트웨이 어드레스를 이용하여 
NAT-PMP 를 사용. 
pmp:192.168.0.1 : 주어진 게이트웨어 어드레스를 이용하여  NAT-PMP를 사용. 
none 또는 off : 설정된게 없다는 의미로. 스스로가 외부접근가능한 IP면 이렇게 하면 된다. 참고로 nat옵션자체를 설정안한것과 같다. 내부적으론 NAT변수에 둘다 nil을 대입함. 

NATFlag = cli.StringFlag{
Name: "nat",
Usage: "NAT port mapping mechanism (any|none|upnp|pmp|extip:<IP>)",
Value: "any",
}

// setNAT creates a port mapper from command line flags.
func setNAT(ctx *cli.Context, cfg *p2p.Config) {
if ctx.GlobalIsSet(NATFlag.Name) {
natif, err := nat.Parse(ctx.GlobalString(NATFlag.Name))
if err != nil {
Fatalf("Option %s: %v", NATFlag.Name, err)
}
cfg.NAT = natif
}
}

GlobalIsSet 함수를 통해 해당 옵션이 커맨드라인에서 세팅되었는지 확인하여 있으면 파싱

func Parse(spec string) (Interface, error) {
var (
parts = strings.SplitN(spec, ":", 2)
mech = strings.ToLower(parts[0])
ip net.IP
)
if len(parts) > 1 {
ip = net.ParseIP(parts[1])
if ip == nil {
return nil, errors.New("invalid IP address")
}
}
switch mech {
case "", "none", "off":
return nil, nil
case "any", "auto", "on":
return Any(), nil
case "extip", "ip":
if ip == nil {
return nil, errors.New("missing IP address")
}
return ExtIP(ip), nil
case "upnp":
return UPnP(), nil
case "pmp", "natpmp", "nat-pmp":
return PMP(ip), nil
default:
return nil, fmt.Errorf("unknown mechanism %q", parts[0])
}
}

커맨드라인에서 읽은 옵션을 그것과 매칭되는 nat.Interface 상속타입을 만들어 cfg.NAT 에 설정 한다.아래에 nat.Interface 를 보면 로컬포트와 인터넷에 접속되는 포트와의 매핑을 추가하는 메소드 (시간이 지나면 자동으로 해제된다) 와 외부 인터넷과 접점인 게이트웨이의 IP를 알 수 있는 메소드가 있다. Golang은 덕타이핑이 기본이므로 해당 메소드를 구현하기만 하면 자바나 C++상속코딩 절차가 필요없이 다형적으로 사용 할 수 있게 된다. 

type Interface interface {

AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) error
DeleteMapping(protocol string, extport, intport int) error

ExternalIP() (net.IP, error)
String() string
}


Any

func Any() Interface {
return startautodisc("UPnP or NAT-PMP", func() Interface {
found := make(chan Interface, 2)
go func() { found <- discoverUPnP() }()
go func() { found <- discoverPMP() }()
for i := 0; i < cap(found); i++ {
if c := <-found; c != nil {
return c
}
}
return nil
})
}

UPnP와 PMP 중 먼저 찾아지는것을 리턴한다. 2개의 버퍼를 가진 채널을 만들어서 for문을 capability (여기서는 2) 만큼 돌면서 먼저 보내지면서 nil 로 보내지지 않은 것을 리턴 해준다.

ExtIp


// ExtIP assumes that the local machine is reachable on the given
// external IP address, and that any required ports were mapped manually.
// Mapping operations will not return an error but won't actually do anything.
func ExtIP(ip net.IP) Interface {
if ip == nil {
panic("IP must not be nil")
}
return extIP(ip)
}

type extIP net.IP

func (n extIP) ExternalIP() (net.IP, error) { return net.IP(n), nil }
func (n extIP) String() string { return fmt.Sprintf("ExtIP(%v)", net.IP(n)) }

// These do nothing.
func (extIP) AddMapping(string, int, int, string, time.Duration) error { return nil }
func (extIP) DeleteMapping(string, int, int) error { return nil }

주어지는 외부IP를 가지고 extIP객체를 만들어 리턴. 외부IP로 고정되어있기 때문에 매핑과 관련된 함수가 의미없다.

UPnP

func UPnP() Interface {
return startautodisc("UPnP", discoverUPnP)
}

노드 디스커버리를 dscoverUPnP 를 가지고 시작. 

PMP


// PMP returns a port mapper that uses NAT-PMP. The provided gateway
// address should be the IP of your router. If the given gateway
// address is nil, PMP will attempt to auto-discover the router.
func PMP(gateway net.IP) Interface {
if gateway != nil {
return &pmp{gw: gateway, c: natpmp.NewClient(gateway)}
}
return startautodisc("NAT-PMP", discoverPMP)
}

노드 디스커버리를 dscoverPMP 를 가지고 시작. 

다음 글에서는 ExtIP 와 UPnP 에 대해서 정밀하게 관찰해보자.


Comments