
    uj                        d Z ddlmZ ddlZddlZddlZddlmZ ddl	m
Z
 e G d d             Z G d d	      Zddd
Zy)uF  
yjm_net_upnp.py

외부 공유 URL 생성 + UPnP 포트포워딩 보조 모듈.

주의:
- UPnP는 라우터에서 켜져 있어야 한다.
- 통신사 CGNAT / 이중 NAT 환경에서는 UPnP가 성공해도 외부 접속이 안 될 수 있다.
- 이 모듈은 포트 매핑만 한다. Windows 방화벽 허용은 별도다.
    )annotationsN)	dataclass)Optionalc                  h    e Zd ZU ded<   ded<   dZded<   dZded<   dZded<   d	Zd
ed<   d	Zd
ed<   y)PortMappingResultboolokstrmessage lan_iprouter_external_ipecho_public_ipr   inthttp_external_portws_external_portN)	__name__
__module____qualname____annotations__r   r   r   r   r        #D:\_St\RTC8790_8791\yjm_net_upnp.pyr   r      sA    HLFC  NCcr   r   c                  x    e Zd ZddZed	d       Zed
dd       Zedd       Z	 	 d	 	 	 	 	 	 	 	 	 	 	 ddZd	dZ	y)UpnpPortMapperNc                .    || _         d | _        g | _        y N)loggerupnpmapped_ports)selfr   s     r   __init__zUpnpPortMapper.__init__"   s    	35r   c                 j   	 t        j                   t         j                  t         j                        5 } | j                  d       | j	                         d   cddd       S # 1 sw Y   yxY w# t
        $ r; 	 t        j                  t        j                               cY S # t
        $ r Y Y yw xY ww xY w)uW   인터넷 연결 시 실제 LAN IP 추정. 실패하면 hostname 기반으로 fallback.)z8.8.8.8P   r   Nz	127.0.0.1)socketAF_INET
SOCK_DGRAMconnectgetsockname	Exceptiongethostbynamegethostname)ss    r   
get_lan_ipzUpnpPortMapper.get_lan_ip'   s    	#v~~v/@/@AQ		/*}}q) BAA  	##++F,>,>,@AA #"#	#sL   2A. $A"	A. "A+'A. +A. .	B28&B!B2!	B.*B2-B..B2c                   | sy	 t         j                  j                  | |      5 }|j                  d      j	                  dd      j                         }d d d        j                         d   S # 1 sw Y   xY w# t        $ r Y yw xY w)Nr   )timeout   zutf-8ignore)errorsr   )urllibrequesturlopenreaddecodestripsplitr*   )urlr0   rtexts       r   fetch_public_ipzUpnpPortMapper.fetch_public_ip4   s}    	''W'=vvc{))'()CIIK >::<?" >=  		s(   !A> 1A2A> 2A;7A> >	B
	B
c                    	 t        j                  | j                               }t	        |j
                  xs3 |j                  xs% |j                  xs |t        j                  d      v       S # t        $ r Y yw xY w)NFz100.64.0.0/10)		ipaddress
ip_addressr9   r*   r   
is_privateis_loopbackis_link_local
ip_network)ip_textips     r   is_private_or_cgnatz"UpnpPortMapper.is_private_or_cgnat?   s{    	%%gmmo6B MM ;~~;; Y))/::	
 	
  		s   #A/ /	A;:A;c                   	 dd l }	 |j                         }d|_        |j                         }	| j                  r| j                  j                  d|	       |j                          || _	        d}
	 t        |j                               }
t        |dd      xs |}t        |      }|d	f|d
ffD ]  \  }}t        |      }	 |j                  |d       |j                  |d|t        |      | d| d      }|st        dd| d| d| d| ||
||      c S | j                   j#                  |df        |r| j%                  |      nd}d| d| d| g}|
r|j#                  d|
        |r|j#                  d|        |
r"| j'                  |
      r|j#                  d       |r|
r||
k7  r|j#                  d       t        ddj)                  |      ||
|||      S # t        $ r}t        dd| |      cY d }~S d }~ww xY w# t        $ r d}
Y w xY w# t        $ r Y Uw xY w# t        $ r}t        dd| |      cY d }~S d }~ww xY w)Nr   Fu   miniupnpc import 실패: )r      zUPnP discover: %sr   lanaddrhttpwsTCP-zUPnP u     포트 매핑 실패: external z -> :)r   r   r   r   u   UPnP 성공: z, HTTP z, WS z
router_ip=zecho_ip=u:   주의: router_external_ip가 사설/CGNAT 대역입니다uH   주의: echo IP와 router IP가 다릅니다. 이중 NAT/CGNAT 가능성Tz / )r   r   r   r   r   u   UPnP 실패: )	miniupnpcr*   r   UPnPdiscoverdelaydiscoverr   info	selectigdr   r
   externalipaddressgetattrr   deleteportmappingaddportmappingr    appendr>   rH   join)r!   r   	http_portws_portpublic_ip_urldesc_prefixrQ   excufoundr   upnp_lan_ip
local_portlabelext_portr	   echo_ip	msg_partss                     r   
open_portszUpnpPortMapper.open_portsL   s   	^C	R A!AOJJLE{{  !4e<KKMDI!#(%()<)<)>%?"
 "!Y3=vK%F'0&&9GT?%K!
Ez?''%8 %%
O"m1UG, ,w&FxjPTU[T\\]^h]ij%+=+4)0  !!(((E):;3 &L6 >Kd**=9PRG(	{%yQRI!  :.@-A!BC  8G9!56!d&>&>?Q&R  !]^-'=O2O  !kl$

9%#5&#,!( y  	^$U.Gu,MV\]]	^  (%'"( ! T  	R$UmC5,A&QQ	Rs   G! A(H* 0H 	5H* ?HAH* C
H* !	H*H :H HHH* HH* 	H'#H* &H''H* *	I3I	I	Ic           
        | j                   syg }t        | j                        D ]B  \  }}	 | j                   j                  t	        |      |       |j                  d| d|        D | j                  j                          |rdj                  |      S dS # t        $ r$}|j                  d| d| d|        Y d }~d }~ww xY w)Nu"   닫을 UPnP 매핑이 없습니다u   닫음 /u   닫기 실패 z: z, )	r   listr    rY   r   r[   r*   clearr\   )r!   messagesportprotora   s        r   close_portszUpnpPortMapper.close_ports   s    yy7 1 12KD%H		++CIu='%$ 89 3 	!&.tyy"X4XX  H.qb FGGHs   <B	C"CCr   )returnr
   )g      @)r;   r
   r0   floatrs   r
   )rF   r
   rs   r   )r   yjm_win2rtc)r   r
   r]   r   r^   r   r_   r
   r`   r
   rs   r   )
r   r   r   r"   staticmethodr.   r>   rH   rj   rr   r   r   r   r   r   !   s    6
 
# 
#   

 

"  (PRPR PR 	PR
 PR PR 
PRdYr   r   c                    ddl m} d| dt        |       }|r|d |d|i      z   z  }d|i}|r||d<   d|  dt        |       d	 ||       S )
u  viewer URL 생성.

    주의:
    - WebSocket 인증용 token은 ws= 내부 URL에도 들어간다.
    - HTTP 업로드/transfer-status 인증용 token은 viewer.html의 바깥 query에도 별도로 넣는다.

    이전 버전은 ws= 내부에만 token을 넣어서 화면/제어/클립보드는 되지만,
    파일 업로드 POST /upload?token=... 에 사용할 token을 viewer가 못 찾는 문제가 있었다.
    r   )	urlencodezws://rP   ?tokenrM   zhttp://z/viewer.html?)urllib.parserx   r   )	http_hostr]   ws_hostr^   rz   rx   ws_urlquerys           r   make_viewer_urlr      sr     'WIQs7|n-F#	7E"23336NEgYKqY 0i>N=OPPr   )r   )r|   r
   r]   r   r}   r
   r^   r   rz   r
   rs   r
   )__doc__
__future__r   r@   r%   urllib.requestr4   dataclassesr   typingr   r   r   r   r   r   r   <module>r      sL   	 #    !    HY HYVQr   