IT 관련/우분투(Ubuntu) 이것저것

WSL2 방화벽 및 포트포워딩,ip 변경

islet2 2025. 6. 19. 16:30

 

윈도우에 WSL2가 설치된 경우 방화벽 및 포트포워딩 설명 입니다.

 

아래 명령어를 WSL이 설치된  윈도우에서 관리자권한으로 실행된 파워쉘에서 실행한다. 

wsl -d Ubuntu ip addr show eth0 | Select-String -Pattern 'inet ' | ForEach-Object {($_ -split '\s+')[2] -split '/' | Select-Object -First 1}

- 위 명령은 WSL의 ip주소를 확인하는 코드로 실행시 주소가 표시됩니다.

netsh advfirewall firewall add rule name="WSL2 Web Access" dir=in action=allow protocol=TCP localport=9090

- 이 명령은 Windows 데스크톱의 9090번 포트로 들어오는 TCP 연결을 허용합니다.  

netsh interface portproxy add v4tov4 listenport=9090 listenaddress=0.0.0.0 connectport=9090 connectaddress=172.19.224.230

- 이 명령은 Windows 데스크톱의 0.0.0.0:9090 (모든 IP의 9090 포트)으로 들어오는 요청을 WSL의 172.19.224.230:9090으로 전달합니다.

 

 

WSL은 부팅시 ip가 변동될 수 있음. 고정이 어려움. Ubuntu 터미널에서 ip확인하는 명령어

ip addr show eth0 | grep -oP 'inet \K[\d.]+'

 

 

ip 변경시 기존 포트 업데이트하는 스크립트

부팅시 ip 변경 가능성이 있으므로 작업스케줄러에 넣어 부팅시 시작하는 것이 좋음.

# 스크립트 이름: Update-WslPortForwarding.ps1
# 설명: WSL2 Ubuntu의 변경된 IP 주소를 감지하여 포트 포워딩 규칙을 자동으로 업데이트합니다.
# 사용법: 관리자 권한으로 실행되도록 작업 스케줄러에 등록합니다.

# PowerShell 콘솔 및 출력 인코딩을 UTF-8로 설정하여 문자 깨짐 방지
[System.Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$OutputEncoding = [System.Text.Encoding]::UTF8

$LogFile = "C:\Scripts\WSL_PortForwarding_Log.txt" # 로그 파일 경로 (원하는 위치로 변경 가능)
$WslDistroName = "Ubuntu" # 사용할 WSL 배포판 이름 (기본 Ubuntu) - 이 값은 고정됩니다.
$PortConfigFile = "C:\Scripts\WSL_PortMappings.json" # 포트 매핑 설정 파일

# 로그 관리 설정
$MaxLogSizeMB = 10         # 로그 파일 최대 크기 (MB)
$MaxLogFiles = 5           # 보관할 로그 파일 개수
$LogCleanupDays = 30         # 로그 파일 보관 기간 (일)

# 기존 netsh 포트 포워딩 규칙에서 포트 매핑 추출
Function Get-ExistingPortMappings {
    try {
        Write-Log "Checking existing netsh port proxy rules..."
        $netshOutput = netsh interface portproxy show all 2>$null
        
        if ($LASTEXITCODE -ne 0 -or -not $netshOutput) {
            Write-Log "No existing port proxy rules found or netsh command failed."
            return @{}
        }
        
        $portMappings = @{}
        
        # netsh 출력 파싱 (v4tov4 규칙만 추출)
        $inV4toV4Section = $false
        foreach ($line in $netshOutput) {
            $line = $line.Trim()
            
            # v4tov4 섹션 시작 확인
            if ($line -match "Listen on ipv4:" -and $line -match "Connect to ipv4:") {
                $inV4toV4Section = $true
                continue
            }
            
            # 다른 섹션이 시작되면 v4tov4 섹션 종료
            if ($line -match "Listen on" -and $line -notmatch "ipv4") {
                $inV4toV4Section = $false
                continue
            }
            
            # v4tov4 섹션 내에서 규칙 파싱
            if ($inV4toV4Section -and $line -match "^\s*(\d+\.\d+\.\d+\.\d+)\s+(\d+)\s+(\d+\.\d+\.\d+\.\d+)\s+(\d+)") {
                $listenAddress = $matches[1]
                $listenPort = [int]$matches[2]
                $connectAddress = $matches[3]
                $connectPort = [int]$matches[4]
                
                # 0.0.0.0으로 리슨하는 규칙만 WSL 관련으로 간주
                if ($listenAddress -eq "0.0.0.0") {
                    $portMappings[$listenPort] = $connectPort
                    Write-Log "Found existing rule: $listenAddress`:$listenPort -> $connectAddress`:$connectPort"
                }
            }
        }
        
        return $portMappings
    }
    catch {
        Write-Log "ERROR: Failed to parse existing port proxy rules. Error: $($_.Exception.Message)"
        return @{}
    }
}

# 포트 매핑을 JSON 파일로 저장
Function Save-PortMappings {
    Param (
        [hashtable]$PortMappings
    )
    
    try {
        # 설정 파일 디렉토리 생성
        $configDir = Split-Path -Path $PortConfigFile -Parent
        if (-not (Test-Path -Path $configDir)) {
            New-Item -Path $configDir -ItemType Directory -Force | Out-Null
        }
        
        # JSON으로 변환하여 저장 (읽기 쉬운 형태로)
        # Hashtable을 PSCustomObject로 변환하여 JSON 직렬화 오류 방지
        # HashTable의 Key를 String으로 변환하여 Json 직렬화에 적합하게 만듬
        $tempObject = New-Object -TypeName System.Collections.Specialized.OrderedDictionary
        foreach ($key in $PortMappings.Keys) {
            $tempObject.Add($key.ToString(), $PortMappings[$key])
        }
        
        $jsonOutput = $tempObject | ConvertTo-Json -Depth 2
        $jsonOutput | Out-File -FilePath $PortConfigFile -Encoding UTF8
        
        Write-Log "Saved $($PortMappings.Count) port mapping(s) to config file: $PortConfigFile"
        return $true
    }
    catch {
        Write-Log "ERROR: Failed to save port mappings to config file. Error: $($_.Exception.Message)"
        return $false
    }
}

# 포트 매핑 설정 파일 로드 또는 생성
Function Load-PortMappings {
    if (Test-Path -Path $PortConfigFile) {
        try {
            Write-Log "Loading port mappings from config file: $PortConfigFile"
            $jsonContent = Get-Content -Path $PortConfigFile -Raw -Encoding UTF8
            $tempObject = ConvertFrom-Json $jsonContent
            
            $portMappings = @{}
            if ($tempObject -is [System.Management.Automation.PSCustomObject] -and $tempObject.PSObject.Properties.Count -gt 0) {
                # JSON에서 로드된 객체의 속성을 순회하며 해시테이블에 추가, 값은 정수형으로 강제 변환
                foreach ($property in $tempObject.PSObject.Properties) {
                    try {
                        # 키는 문자열로, 값은 정수형으로 변환하여 저장
                        $portMappings[$property.Name] = [int]$property.Value
                    } catch {
                        Write-Log "WARN: Failed to convert port mapping value for key '$($property.Name)'. Error: $($_.Exception.Message)"
                    }
                }
            }
            # 빈 설정 파일인 경우 기존 netsh 규칙에서 생성
            if ($portMappings.Count -eq 0) {
                Write-Log "Config file is empty or invalid. Checking existing netsh rules..."
                $existingMappings = Get-ExistingPortMappings
                
                if ($existingMappings.Count -gt 0) {
                    Write-Log "Found $($existingMappings.Count) existing port proxy rule(s). Updating config file..."
                    Save-PortMappings -PortMappings $existingMappings
                    return $existingMappings
                }
                else {
                    Write-Log "No existing rules found. Using default port mappings."
                    $defaultMappings = @{80 = 80; 9090 = 9090}
                    Save-PortMappings -PortMappings $defaultMappings
                    return $defaultMappings
                }
            }
            
            Write-Log "Loaded $($portMappings.Count) port mapping(s) from config file."
            return $portMappings
        }
        catch {
            Write-Log "ERROR: Failed to load port mappings from config file. Error: $($_.Exception.Message)"
            Write-Log "Attempting to generate from existing netsh rules..."
            
            $existingMappings = Get-ExistingPortMappings
            if ($existingMappings.Count -gt 0) {
                Save-PortMappings -PortMappings $existingMappings
                return $existingMappings
            }
            else {
                Write-Log "Using default port mappings."
                $defaultMappings = @{80 = 80; 9090 = 9090}
                Save-PortMappings -PortMappings $defaultMappings
                return $defaultMappings
            }
        }
    }
    else {
        Write-Log "Port mapping config file not found: $PortConfigFile"
        Write-Log "Checking for existing netsh port proxy rules to auto-generate config..."
        
        $existingMappings = Get-ExistingPortMappings
        
        if ($existingMappings.Count -gt 0) {
            Write-Log "Found $($existingMappings.Count) existing port proxy rule(s). Creating config file..."
            Save-PortMappings -PortMappings $existingMappings
            Write-Log "Auto-generated config file from existing netsh rules."
            return $existingMappings
        }
        else {
            Write-Log "No existing port proxy rules found. Creating default config file..."
            $defaultMappings = @{
                80 = 80
                9090 = 9090
            }
            
            Save-PortMappings -PortMappings $defaultMappings
            Write-Log "Created default port mapping config file with ports: $($defaultMappings.Keys -join ', ')"
            Write-Log "You can edit $PortConfigFile to add or remove port mappings."
            return $defaultMappings
        }
    }
}

# 로그 파일 관리 함수
Function Manage-LogFiles {
    try {
        $LogDir = Split-Path -Path $LogFile -Parent
        $LogFileName = [System.IO.Path]::GetFileNameWithoutExtension($LogFile)
        $LogFileExtension = [System.IO.Path]::GetExtension($LogFile)
        
        # 현재 로그 파일 크기 확인
        if (Test-Path -Path $LogFile) {
            $LogSizeMB = (Get-Item $LogFile).Length / 1MB
            
            # 로그 파일이 최대 크기를 초과하면 아카이브
            if ($LogSizeMB -gt $MaxLogSizeMB) {
                $ArchiveFileName = "$LogFileName`_$(Get-Date -Format 'yyyyMMdd_HHmmss')$LogFileExtension"
                $ArchiveFilePath = Join-Path -Path $LogDir -ChildPath $ArchiveFileName
                
                # 현재 로그 파일을 아카이브로 이동
                Move-Item -Path $LogFile -Destination $ArchiveFilePath -Force
                
                # 새 로그 파일에 아카이브 정보 기록
                $Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
                Add-Content -Path $LogFile -Value "[$Timestamp] Log file archived to: $ArchiveFileName (Size: $([math]::Round($LogSizeMB, 2)) MB)" -Encoding UTF8 # UTF8 인코딩 명시
            }
        }
        
        # 오래된 로그 파일 정리
        $ArchiveFiles = Get-ChildItem -Path $LogDir -Filter "$LogFileName`_*$LogFileExtension" -ErrorAction SilentlyContinue
        
        if ($ArchiveFiles.Count -gt 0) {
            # 날짜 기준으로 오래된 파일 삭제
            $CutoffDate = (Get-Date).AddDays(-$LogCleanupDays)
            $OldFiles = $ArchiveFiles | Where-Object { $_.CreationTime -lt $CutoffDate }
            
            foreach ($OldFile in $OldFiles) {
                Remove-Item -Path $OldFile.FullName -Force
                $Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
                Add-Content -Path $LogFile -Value "[$Timestamp] Deleted old log file: $($OldFile.Name) (Created: $($OldFile.CreationTime.ToString('yyyy-MM-dd')))" -Encoding UTF8 # UTF8 인코딩 명시
            }
            
            # 개수 기준으로 오래된 파일 삭제 (날짜 기준 삭제 후에도 초과하는 경우)
            $RemainingFiles = Get-ChildItem -Path $LogDir -Filter "$LogFileName`_*$LogFileExtension" -ErrorAction SilentlyContinue | Sort-Object CreationTime -Descending
            
            if ($RemainingFiles.Count -gt $MaxLogFiles) {
                $FilesToDelete = $RemainingFiles | Select-Object -Skip $MaxLogFiles
                
                foreach ($FileToDelete in $FilesToDelete) {
                    Remove-Item -Path $FileToDelete.FullName -Force
                    $Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
                    Add-Content -Path $LogFile -Value "[$Timestamp] Deleted excess log file: $($FileToDelete.Name) (Keeping only $MaxLogFiles recent files)" -Encoding UTF8 # UTF8 인코딩 명시
                }
            }
        }
    }
    catch {
        # 로그 관리 실패 시에도 스크립트는 계속 진행
        $Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
        Add-Content -Path $LogFile -Value "[$Timestamp] WARNING: Log file management failed: $($_.Exception.Message)" -ErrorAction SilentlyContinue -Encoding UTF8 # UTF8 인코딩 명시
    }
}

Function Write-Log {
    Param (
        [string]$Message
    )
    $Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    
    # 로그 디렉토리 확인 및 생성
    $LogDir = Split-Path -Path $LogFile -Parent
    if (-not (Test-Path -Path $LogDir)) {
        New-Item -Path $LogDir -ItemType Directory -Force | Out-Null
    }
    
    # 로그 파일 관리 (첫 번째 로그 메시지 작성 전에 실행)
    if (-not $Global:LogManagementDone) {
        Manage-LogFiles
        $Global:LogManagementDone = $true
    }
    
    Add-Content -Path $LogFile -Value "[$Timestamp] $Message" -Encoding UTF8 # UTF8 인코딩 명시
    Write-Host "[$Timestamp] $Message"
}

# 관리자 권한 확인
if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
    Write-Log "ERROR: This script must be run as Administrator."
    exit 1
}

Write-Log "----------------------------------------------------"
Write-Log "WSL Port Forwarding Update Script Started."
Write-Log "Log Management: Max Size=${MaxLogSizeMB}MB, Max Files=${MaxLogFiles}, Cleanup Days=${LogCleanupDays}"

try {
    # 1. 포트 매핑 설정 로드
    $PortMappings = Load-PortMappings
    
    Write-Log "Active port mappings:"
    foreach ($ListenPort in $PortMappings.Keys) {
        # PortMappings[$ListenPort]가 null이거나 비어있을 수 있으므로 기본값 설정
        $connectPortValue = if ($PortMappings[$ListenPort] -ne $null) { $PortMappings[$ListenPort] } else { "(알 수 없음)" }
        Write-Log "  Windows:$ListenPort -> WSL:$connectPortValue"
    }
    
    # 2. WSL 배포판 시작 시도 및 IP 가져오기
    Write-Log "Attempting to start WSL distribution '${WslDistroName}' and get IP address..."
    
    # WSL 배포판을 시작합니다. 이미 실행 중이면 아무 작업도 수행하지 않습니다.
    # -e 옵션으로 간단한 명령을 실행하여 WSL을 활성화하고, 오류 발생 시 바로 종료합니다.
    wsl -d $WslDistroName -e echo "WSL distro '$WslDistroName' is starting or already running." 2>$null
    if ($LASTEXITCODE -ne 0) {
        Write-Log "ERROR: WSL 배포판 '$WslDistroName'을(를) 시작할 수 없습니다. 배포판 이름이 정확한지 또는 WSL이 제대로 설치되어 있는지 확인하세요."
        exit 1
    }
    Start-Sleep -Seconds 5 # WSL이 IP를 할당받을 시간을 줍니다.

    # IP 주소 가져오기
    Write-Log "Getting current WSL IP address for ${WslDistroName}..."
    $WslIpOutput = wsl -d $WslDistroName -e ip addr show eth0 2>$null
    if ($LASTEXITCODE -ne 0) {
        Write-Log "ERROR: WSL에서 네트워크 인터페이스 정보를 가져오지 못했습니다. 'ip addr show eth0' 명령어가 WSL 내에서 작동하는지 확인하세요."
        exit 1
    }
    
    $ipMatch = $WslIpOutput | Select-String -Pattern 'inet (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' | Select-Object -First 1
    if ($ipMatch) {
        $WslIp = $ipMatch.Matches.Groups[1].Value
        Write-Log "Current WSL IP for ${WslDistroName}: ${WslIp}"
    } else {
        Write-Log "ERROR: WSL 네트워크 인터페이스에서 IP 주소를 추출할 수 없습니다. WSL 내에서 'ip addr show eth0' 명령어가 작동하는지 확인하세요."
        exit 1
    }

    if ([string]::IsNullOrWhiteSpace($WslIp)) {
        Write-Log "ERROR: 가져온 IP 주소가 비어 있습니다."
        exit 1
    }
    
    # IP 주소 형식 검증
    if (-not ($WslIp -match '^\d+\.\d+\.\d+\.\d+$')) {
        Write-Log "ERROR: 잘못된 IP 주소 형식: $WslIp"
        exit 1
    }

    # 4. 기존 포트 포워딩 규칙 삭제
    Write-Log "Deleting existing port proxy rules..."
    foreach ($ListenPort in $PortMappings.Keys) {
        $deleteResult = netsh interface portproxy delete v4tov4 listenport=$ListenPort listenaddress=0.0.0.0 2>&1
        if ($LASTEXITCODE -eq 0) {
            Write-Log "Deleted rule for port $ListenPort."
        } else {
            Write-Log "WARN: Could not delete rule for port $ListenPort. It might not exist."
        }
    }

    # 5. 새로운 포트 포워딩 규칙 추가
    Write-Log "Adding new port proxy rules..."
    $SuccessCount = 0
    foreach ($ListenPort in $PortMappings.Keys) {
        $ConnectPort = $PortMappings[$ListenPort]
        # $ConnectPort가 비어있을 수 있으므로 유효성 검사 추가 (선택 사항)
        if ([string]::IsNullOrWhiteSpace($ConnectPort)) {
            Write-Log "ERROR: 포트 $ListenPort에 대한 연결 포트 값이 유효하지 않습니다. 규칙을 추가할 수 없습니다."
            continue
        }

        $addResult = netsh interface portproxy add v4tov4 listenport=$ListenPort listenaddress=0.0.0.0 connectport=$ConnectPort connectaddress=${WslIp} 2>&1

        
        if ($LASTEXITCODE -eq 0) {
            Write-Log "Added rule: listenport=$ListenPort -> connectaddress=${WslIp}:${ConnectPort}"
            $SuccessCount++
        } else {
            Write-Log "ERROR: Failed to add rule for port $ListenPort. Output: $addResult"
        }
    }

    # 6. 결과 확인
    Write-Log "Verifying port proxy rules..."
    $CurrentRules = netsh interface portproxy show v4tov4 2>$null
    if ($CurrentRules) {
        Write-Log "Current active port proxy rules:"
        $CurrentRules | ForEach-Object { Write-Log "  $_" }
    } else {
        Write-Log "No active port proxy rules found."
    }

    Write-Log "WSL Port Forwarding Update Script Finished Successfully. ($SuccessCount/$($PortMappings.Count) rules added)"
}
catch {
    Write-Log "FATAL ERROR: An unhandled exception occurred: $($_.Exception.Message)"
    Write-Log "Stack Trace: $($_.ScriptStackTrace)"
    exit 1
}
finally {
    Write-Log "----------------------------------------------------`n"
}

 

실행 명령어

powershell.exe -NoProfile -ExecutionPolicy Bypass -File "C:\Scripts\Update-WslPortForwarding.ps1"

 

작업스케줄러 등록 방법

작업 스케줄러 등록:
  • Windows 검색창에 작업 스케줄러를 입력하여 실행합니다.
  • 오른쪽의 작업 만들기...를 클릭합니다.
  • 일반 탭:
    • 이름: WSL Port Forwarding Updater (원하는 이름)
    • 가장 높은 권한으로 실행: 체크 (필수)
    • 사용자 또는 그룹 변경...: 현재 로그인한 사용자를 선택하거나 SYSTEM 계정을 선택할 수 있습니다. SYSTEM 계정이 더 안전하고 안정적일 수 있습니다. (시스템 재부팅 시 로그인 없이 실행)
  • 트리거 탭:
    • 새로 만들기... 클릭
    • 작업 시작: 시작 시 또는 로그온 시 (시스템 재부팅 후 바로 적용되려면 시작 시 권장)
    • 지연 시간: 짧게 (예: 1분) 설정하여 Windows 네트워크가 완전히 준비된 후에 실행되도록 합니다.
    • 사용: 체크
    • 확인 클릭
  • 동작 탭:
    • 새로 만들기... 클릭
    • 동작: 프로그램 시작
    • 프로그램/스크립트: powershell.exe
    • 인수 추가(옵션): -NoProfile -ExecutionPolicy Bypass -File "C:\Scripts\Update-WslPortForwarding.ps1" (스크립트 파일의 실제 경로로 변경)
    • 확인 클릭
  • 조건 탭:
    • 컴퓨터가 AC 전원에 있을 때에만 시작 (노트북이라면 체크 해제 고려)
    • 네트워크 연결 사용 가능 시에만 시작 (이더넷 연결인 경우)
  • 설정 탭:
    • 요청 시 수동으로 실행하도록 허용
    • 실행되지 않는 경우 즉시 새 작업 시작 (선택 사항)
    • 작업이 실패한 경우 다시 시작 (선택 사항)
  • 확인 클릭 후 사용자 비밀번호 입력 (필요한 경우)