IPv6-onlyなEC2からCloudflare WARPでIPv4通信してみた

IPv6-onlyなEC2からCloudflare WARPでIPv4通信してみた

インターネット通信経路をIPv6(Egress-Only Gateway)のみに限定したVPC内のEC2 (Amazon Linux 2023) で検証を実施。Cloudflare WARPによるIPv4 over IPv6トンネルを利用することで、固定費ゼロなIPv4のインターネット通信経路を確保。SSMの利用も確認できました。
2026.01.10

2024年2月1日より、AWSのPublic IPv4アドレスに課金が開始されました。その料金は$0.005/時間、月額(30日)に換算すると約$3.6/アドレスとなり、t4g.nanoの月額料金(約$3.94)に匹敵するコストが発生するようになりました。

一方で、SSM(Systems Manager)など、IPv4のみで提供されるAWSサービスが2025年1月時点では存在。IPv4パブリックアドレスを持たないEC2からこれらのサービスを利用するためには、NAT GatewayやVPCエンドポイントなどが必要でした。

今回、IPv4パブリックIPやNAT Gatewayを省略しても、Cloudflare WARPのトンネル(IPv4 over IPv6)経由でIPv4通信が可能であるか、確認を試みる機会がありましたので、紹介します。

アーキテクチャ概要

今回構築する環境のコンポーネントは以下の通りです。

  • VPC: IPv4/IPv6デュアルスタック(ただしIPv4はプライベートのみ、IGWも省略)
  • Egress-Only Internet Gateway: IPv6アウトバウンド専用
  • S3 Gateway Endpoint: Amazon Linux 2023公式リポジトリからのパッケージ取得や、WARP RPMの仮置場のS3接続経路として利用
  • EC2 Instance Connect Endpoint: Egress-Only Gateway環境で外部からのSSH接続経路として利用
  • Cloudflare WARP: IPv4 over IPv6 実現

なぜこの構成が動くのか

Cloudflare WARPの仕組み

Cloudflare WARPはWireGuardベースのVPNトンネルです。IPv6経由でCloudflareに接続し、CloudflareがIPv4への変換を行うことで、IPv4 over IPv6を実現しています。

環境構築

VPCの構築(vpc-template.yaml)

以下のリソースを含むVPCをCloudFormationで構築しました。

  • VPC(IPv4/IPv6デュアルスタック)
  • プライベートサブネット x 2
  • Egress-Only Internet Gateway
  • S3 Gateway Endpoint
  • EC2 Instance Connect Endpoint
テンプレート全文
AWSTemplateFormatVersion: '2010-09-09'
Description: 'IPv6-only VPC Test - Complete private VPC with IPv6 connectivity'

Parameters:
  ProjectName:
    Type: String
    Default: 'ipv6-vpc-test'
    Description: 'IPv6-only VPC Test - Project name prefix for AWS resources'

Resources:
  # VPC
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: '192.168.0.0/18'
      EnableDnsHostnames: true
      EnableDnsSupport: true
      Tags:
        - Key: Name
          Value: !Ref ProjectName

  # IPv6 CIDR Block
  IPv6CidrBlock:
    Type: AWS::EC2::VPCCidrBlock
    Properties:
      VpcId: !Ref VPC
      AmazonProvidedIpv6CidrBlock: true

  # Private Subnets
  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    DependsOn: IPv6CidrBlock
    Properties:
      VpcId: !Ref VPC
      CidrBlock: '192.168.0.0/20'
      AvailabilityZone: !Select [0, !GetAZs '']
      Ipv6CidrBlock: !Select [0, !Cidr [!Select [0, !GetAtt VPC.Ipv6CidrBlocks], 2, 64]]
      AssignIpv6AddressOnCreation: true
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-private-subnet-1a'

  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    DependsOn: IPv6CidrBlock
    Properties:
      VpcId: !Ref VPC
      CidrBlock: '192.168.16.0/20'
      AvailabilityZone: !Select [1, !GetAZs '']
      Ipv6CidrBlock: !Select [1, !Cidr [!Select [0, !GetAtt VPC.Ipv6CidrBlocks], 2, 64]]
      AssignIpv6AddressOnCreation: true
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-private-subnet-1c'

  # Egress-Only Internet Gateway (IPv6)
  EgressOnlyInternetGateway:
    Type: AWS::EC2::EgressOnlyInternetGateway
    Properties:
      VpcId: !Ref VPC

  # Private Route Table
  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-private-rt'

  # IPv6 Route (via Egress-Only IGW)
  PrivateRouteIPv6:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      DestinationIpv6CidrBlock: '::/0'
      EgressOnlyInternetGatewayId: !Ref EgressOnlyInternetGateway

  # Subnet Associations
  PrivateSubnet1Association:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet1
      RouteTableId: !Ref PrivateRouteTable

  PrivateSubnet2Association:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet2
      RouteTableId: !Ref PrivateRouteTable

  # VPC Endpoint - S3
  S3Endpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcId: !Ref VPC
      ServiceName: !Sub 'com.amazonaws.${AWS::Region}.s3'
      VpcEndpointType: Gateway
      RouteTableIds:
        - !Ref PrivateRouteTable

  # EC2 Instance Connect Endpoint
  EC2InstanceConnectEndpoint:
    Type: AWS::EC2::InstanceConnectEndpoint
    Properties:
      SubnetId: !Ref PrivateSubnet1
      SecurityGroupIds:
        - !Ref EC2InstanceConnectSecurityGroup
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-eice'

  # Security Group for EC2 Instance Connect Endpoint
  EC2InstanceConnectSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for EC2 Instance Connect Endpoint
      VpcId: !Ref VPC
      SecurityGroupEgress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 192.168.0.0/18
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-eice-sg'

Outputs:
  VPCId:
    Description: 'VPC ID'
    Value: !Ref VPC
    Export:
      Name: !Sub '${ProjectName}-vpc-id'

  PrivateSubnet1Id:
    Description: 'Private Subnet 1 ID'
    Value: !Ref PrivateSubnet1
    Export:
      Name: !Sub '${ProjectName}-private-subnet-1-id'

  PrivateSubnet2Id:
    Description: 'Private Subnet 2 ID'
    Value: !Ref PrivateSubnet2
    Export:
      Name: !Sub '${ProjectName}-private-subnet-2-id'

WARP RPMの準備

Amazon Linux 2023用のCloudflare WARP公式リポジトリは存在しなかったため、CentOS/RHEL8用のものを代用しました。
WARPの依存パッケージ( desktop-file-utils nftables nss-tools)はAmazon Linux 2023の公式リポジトリからインストール。
WARP本体はCloudflareダウンロードページからCentOS/RHEL8用のRPMをダウンロードしてS3に保存、インストールに利用しました。

EC2インスタンスの構築(ec2-warp-launch-template.yaml)

以下のリソースを含むEC2環境をCloudFormationで構築しました。

  • IAMロール(SSM + S3アクセス用)
  • セキュリティグループ(EC2 Instance Connect EndpointからのSSHを許可)
  • Launch Template(UserDataでCloudflare WARPを設定)
  • EC2インスタンス(Launch Templateを利用して起動)
テンプレート全文
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Launch Template with Cloudflare WARP in IPv6-only VPC (ARM64 Optimized)'

Parameters:
  VpcId:
    Type: AWS::EC2::VPC::Id
    Description: 'VPC ID'

  SubnetId:
    Type: AWS::EC2::Subnet::Id
    Description: 'Subnet ID'

  InstanceType:
    Type: String
    Default: 't4g.micro'
    AllowedValues: ['t4g.nano', 't4g.micro', 't4g.small']
    Description: 'EC2 instance type (ARM64 only)'

  KeyPairName:
    Type: AWS::EC2::KeyPair::KeyName
    Description: 'EC2 Key Pair for SSH access'

  WarpRpmS3Uri:
    Type: String
    Description: 'S3 URI for WARP RPM'

  ImageId:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: '/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64'
    Description: 'AMI ID for EC2 instance'

Resources:
  EC2Role:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub '${AWS::StackName}-ec2-role'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
      Policies:
        - PolicyName: WarpS3Access
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action: s3:GetObject
                Resource: arn:aws:s3:::cloudflare-warp-temp/*

  EC2InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      InstanceProfileName: !Sub '${AWS::StackName}-instance-profile'
      Roles:
        - !Ref EC2Role

  EC2SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: 'Security group for WARP EC2 instance'
      VpcId: !Ref VpcId
      SecurityGroupEgress:
        - IpProtocol: -1
          CidrIpv6: '::/0'
          Description: 'All outbound traffic over IPv6'
        - IpProtocol: -1
          CidrIp: '0.0.0.0/0'
          Description: 'All outbound traffic over IPv4'
      Tags:
        - Key: Name
          Value: 'warp-ec2-sg'

  WarpLaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateName: !Sub '${AWS::StackName}-launch-template'
      LaunchTemplateData:
        ImageId: !Ref ImageId
        InstanceType: !Ref InstanceType
        KeyName: !Ref KeyPairName
        IamInstanceProfile:
          Arn: !GetAtt EC2InstanceProfile.Arn
        NetworkInterfaces:
          - DeviceIndex: 0
            SubnetId: !Ref SubnetId
            Ipv6AddressCount: 1
            Groups:
              - !Ref EC2SecurityGroup
        UserData:
          Fn::Base64: !Sub |
            #!/bin/bash
            # Install dependencies
            dnf install -y desktop-file-utils nftables nss-tools
            # Download and install WARP
            aws s3 cp "${WarpRpmS3Uri}" /tmp/cloudflare-warp.rpm
            rpm -ivh /tmp/cloudflare-warp.rpm
            # Start WARP service
            systemctl enable --now warp-svc
            for i in {1..30}; do warp-cli status &>/dev/null && break; sleep 2; done
            # Register and configure WARP
            script -q -c "yes | warp-cli registration new" /dev/null
            warp-cli mode warp
            # Exclude IPv6 from WARP tunnel (IPv4 only through WARP)
            warp-cli tunnel ip add-range ::/0
            warp-cli connect
            # Restart SSM agent
            systemctl restart amazon-ssm-agent

  WarpEC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      LaunchTemplate:
        LaunchTemplateId: !Ref WarpLaunchTemplate
        Version: !GetAtt WarpLaunchTemplate.LatestVersionNumber

Outputs:
  InstanceId:
    Description: 'EC2 Instance ID'
    Value: !Ref WarpEC2Instance
    Export:
      Name: !Sub '${AWS::StackName}-instance-id'

WARP設定のポイント

  • warp-svcの起動には数十秒かかる場合があるため、待機処理を追加しました
# WARPサービスの有効化・起動
systemctl enable --now warp-svc

# サービス起動待機(最大60秒)
for i in {1..30}; do warp-cli status &>/dev/null && break; sleep 2; done
  • warp-cli registration new 時、インタラクティブに規約への同意を求められますが、疑似TTYで対応しました
# WARPの登録
script -q -c "yes | warp-cli registration new" /dev/null
  • IPv6通信は、WARPトンネル対象から除外する、スプリットトンネル設定を実施しました。
# IPv6をWARPトンネルから除外(IPv4のみWARP経由)
warp-cli tunnel ip add-range ::/0

Cloudflare WARPの利用規約や制約は、公式ドキュメント、FAQ(https://developers.cloudflare.com/warp-client/known-issues-and-faq/ )を確認してご利用ください。

動作確認

EC2 Instance Connect Endpoint経由でSSH接続して確認を行いました。

$ aws ec2-instance-connect ssh --instance-id i-xxxxxxxxxxxxx71f5 --region ap-northeast-1

WARPステータス確認

$ warp-cli status
Status update: Connected
Network: healthy

IPv6通信の確認

$ curl -6 -s https://ipv6.icanhazip.com
2406:da14:xxxx:xxxx:xxxx:xxxx:848b:afaa

EC2に割り当てられたグローバルIPv6アドレスが返却されました。これはEgress-Only IGWを経由しています。

IPv4通信の確認

$ curl -4 -s https://ipv4.icanhazip.com
104.xx.xxx.105

Cloudflareが保有するIPv4アドレスが返却されました。WARPトンネルが正常に機能していることを確認できました。

SSM接続の確認

WARPの設定後、SSMの「start-session」が利用できることを確認できました。

$ aws ssm start-session --target i-xxxxxxxxxxxxx71f5 --region ap-northeast-1

パブリックなIPv4アドレスや、NAT Gateway、VPCエンドポイント(ssm、ssmmessages、ec2messages)が存在しないEC2でも、セッションマネージャーを利用した接続ができました。

SSM接続

コスト比較

項目 EC2パブリックIP NAT Gateway Cloudflare WARP
固定費 $0.005/時間(約$3.6/月) $0.062/時間(約$45/月) $0
EC2データ転送(イン) $0 $0 $0
EC2データ転送(アウト) $0.114/GB $0.114/GB $0.114/GB
NAT Gateway処理 - $0.062/GB -

まとめ

IPv6-only、NAT Gatewayの存在しないVPC環境でも、Cloudflare WARPを利用することで、IPv4通信を必要とするSSMなどのサービスが利用できることを確認しました。

IPv6通信を主に利用し、補助用途でIPv4通信を行うワークロード、特に低スペックなインスタンスや、 2026年末まで延長されたt4g.smallの無料枠を活用したコストを抑制した開発環境では、WARP による IPv4 over IPv6 接続が効果的です。

ただし、Cloudflare WARPの動作対象OSにAmazon Linux 2023は含まれておらず、Cloudflare・AWS双方からのサポートも期待できません。今回紹介した構成については、サービス影響のない検証環境や個人利用に留めることを強くおすすめします。

将来的には、AWS公式で低コストで利用できるIPv4 over IPv6の仕組みの提供や、AWSを含めた各社サービスのDualStackサポートが進み、IPv6ネイティブで実用可能な環境が整備されることに期待したいと思います。

参考リンク

https://dev.classmethod.jp/articles/access-ipv4-internet-from-ipv6-only-subnet-via-cloudflare-warp/

この記事をシェアする

FacebookHatena blogX

関連記事