IaC GCP

Terraform Module: GCP Cloud Interconnect — VLAN Attachment, Cloud Router, and BGP on Top of a Pre-Ordered Circuit

Quick take — A reusable hashicorp/google ~> 5.0 module for google_compute_interconnect_attachment: builds the VLAN attachment (DEDICATED or PARTNER), a regional Cloud Router with your ASN, and the BGP interface + peer that turn a physical Interconnect into routed hybrid connectivity. New here? Jump to the Quickstart below to deploy it in minutes; read on for how it works and when to reach for it.

Quickstart (copy-paste)

Minimal, runnable configuration — drop this in a .tf file and fill in the "..." placeholders (each required input is commented):

provider "google" {
  project = "my-project"
  region  = "us-central1"
}

module "interconnect" {
  source = "git::https://dev.azure.com/teknohut/kloudvin/_git/terraform-modules//terraform-module-gcp-interconnect?ref=v1.0.0"

  name       = "..."   # Base name for the attachment, router, and BGP peer.
  project_id = "..."   # GCP project that owns the Cloud Router + VLAN attachment.
  region     = "..."   # Region of the Cloud Router (must match the circuit's edge).
  network    = "..."   # Self-link or name of the VPC the router attaches to.

  # Attachment type: "PARTNER" (provider-brokered) or "DEDICATED" (your circuit).
  attachment_type = "..."

  # For DEDICATED only: the self-link of the pre-provisioned physical
  # google_compute_interconnect plus the 802.1Q VLAN tag you were assigned.
  # interconnect  = "..."   # Required when attachment_type = "DEDICATED".
  # vlan_tag8021q = 0       # Required when attachment_type = "DEDICATED".

  # For PARTNER only: which edge availability domain to land in.
  # edge_availability_domain = "AVAILABILITY_DOMAIN_1"
}

Then terraform init && terraform apply. Every other input has a sensible default — see Inputs below to override behaviour.

What this module is

Google Cloud Interconnect gives you a private, high-bandwidth path between your on-prem (or another cloud) network and a Google VPC, bypassing the public internet. There are two flavours: Dedicated Interconnect, where you order a 10G/100G physical circuit into a Google colocation facility and own the google_compute_interconnect object; and Partner Interconnect, where a supported service provider already has the physical link and brokers you a slice of bandwidth (50M–50G) without a colo cross-connect.

The single most important thing to understand is the layering. The physical link — the google_compute_interconnect resource for Dedicated, or the provider’s circuit for Partner — is almost always pre-provisioned out of band: someone orders the port, runs the cross-connect, and waits days or weeks for it to light up. Terraform rarely creates that object from scratch. What you do build in code, repeatedly and per-environment, is the logical overlay: a VLAN attachment (google_compute_interconnect_attachment) that carves a tagged Layer-2 segment out of the physical link, a regional Cloud Router (google_compute_router) that owns your BGP ASN, a router interface bound to the attachment, and a BGP peer (google_compute_router_peer) that exchanges routes with your on-prem edge.

This module wraps exactly that overlay. You hand it a pre-existing physical Interconnect self-link (Dedicated) or just pick an edge availability domain (Partner), and it stitches together the attachment, Cloud Router, interface, and BGP session with the right arguments wired for each type — so a hybrid connection becomes a single reviewed, versioned Terraform change instead of a console clickthrough across four resource pages.

When to use it

Reach for the HA VPN module instead when you do not have (or do not want) a physical circuit and IPsec-over-internet bandwidth is acceptable. This module deliberately owns the attachment-plus-routing overlay and assumes the physical google_compute_interconnect is provisioned elsewhere.

Module structure

terraform-module-gcp-interconnect/
├── versions.tf
├── main.tf
├── variables.tf
└── outputs.tf

versions.tf

terraform {
  required_version = ">= 1.5.0"

  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 5.0"
    }
  }
}

main.tf

locals {
  # Derive consistent child-resource names from a single base name so the
  # router, attachment, interface, and peer are easy to correlate in the console.
  router_name     = coalesce(var.router_name, "${var.name}-router")
  attachment_name = coalesce(var.attachment_name, "${var.name}-vlan")
  interface_name  = "${var.name}-if"
  peer_name       = "${var.name}-peer"

  is_dedicated = var.attachment_type == "DEDICATED"
  is_partner   = var.attachment_type == "PARTNER"
}

# Cloud Router owns the Google-side BGP ASN and lives in the same region as the
# VLAN attachment. Reuse an existing router by passing router_name to skip the
# creation here only if you manage the router elsewhere; this module creates it.
resource "google_compute_router" "this" {
  project = var.project_id
  name    = local.router_name
  region  = var.region
  network = var.network

  description = var.description

  # encrypted_interconnect_router is required when the attachment uses IPSEC
  # encryption (HA VPN over Cloud Interconnect). Harmless to leave false otherwise.
  encrypted_interconnect_router = var.encryption == "IPSEC"

  bgp {
    asn               = var.google_asn
    advertise_mode    = var.advertise_mode
    advertised_groups = var.advertise_mode == "CUSTOM" ? var.advertised_groups : []

    dynamic "advertised_ip_ranges" {
      for_each = var.advertise_mode == "CUSTOM" ? var.advertised_ip_ranges : []
      content {
        range       = advertised_ip_ranges.value.range
        description = lookup(advertised_ip_ranges.value, "description", null)
      }
    }
  }
}

# The VLAN attachment carves a tagged segment out of the physical Interconnect.
# DEDICATED  -> you supply the interconnect self-link and the 802.1Q VLAN tag.
# PARTNER    -> Google/your carrier manages the interconnect + VLAN tag upstream;
#               you only choose the edge availability domain.
resource "google_compute_interconnect_attachment" "this" {
  project = var.project_id
  name    = local.attachment_name
  region  = var.region
  router  = google_compute_router.this.id

  type        = var.attachment_type
  description = var.description

  # DEDICATED-only arguments. interconnect must be set for DEDICATED and must
  # NOT be set for PARTNER; vlan_tag8021q is managed upstream for PARTNER.
  interconnect  = local.is_dedicated ? var.interconnect : null
  vlan_tag8021q = local.is_dedicated ? var.vlan_tag8021q : null

  # PARTNER-only argument. Pin a pair of attachments to different domains for HA.
  edge_availability_domain = local.is_partner ? var.edge_availability_domain : null

  # Bandwidth is settable for DEDICATED; for PARTNER it is set by the operator
  # (output only), so we only pass it through on DEDICATED attachments.
  bandwidth = local.is_dedicated ? var.bandwidth : null

  mtu        = var.mtu
  admin_enabled = var.admin_enabled

  # Encryption: NONE (default) or IPSEC for HA VPN over Cloud Interconnect.
  encryption               = var.encryption
  ipsec_internal_addresses = var.encryption == "IPSEC" ? var.ipsec_internal_addresses : null

  stack_type = var.stack_type
}

# Bind a Cloud Router interface to the VLAN attachment. The interface IP is
# allocated by Google from the attachment's link-local /29 unless you pin one.
resource "google_compute_router_interface" "this" {
  project = var.project_id
  name    = local.interface_name
  region  = var.region
  router  = google_compute_router.this.name

  interconnect_attachment = google_compute_interconnect_attachment.this.self_link

  # Optional: pin the Google-side interface address. For PARTNER attachments the
  # addresses are typically auto-allocated, so leave this null.
  ip_range = var.interface_ip_range
}

# BGP session with your on-prem / peer edge. Lower advertised_route_priority
# wins for inbound traffic engineering across a redundant pair of attachments.
resource "google_compute_router_peer" "this" {
  project = var.project_id
  name    = local.peer_name
  region  = var.region
  router  = google_compute_router.this.name

  interface                 = google_compute_router_interface.this.name
  peer_asn                  = var.peer_asn
  advertised_route_priority = var.advertised_route_priority

  # peer_ip_address is required when you pin ip_address on the interface; for
  # auto-allocated PARTNER attachments both are usually left null.
  ip_address      = var.peer_interface_ip_address
  peer_ip_address = var.peer_ip_address

  enable = var.peer_enable

  dynamic "bfd" {
    for_each = var.enable_bfd ? [1] : []
    content {
      session_initialization_mode = var.bfd_session_initialization_mode
      min_receive_interval        = var.bfd_min_receive_interval
      min_transmit_interval       = var.bfd_min_transmit_interval
      multiplier                  = var.bfd_multiplier
    }
  }
}

variables.tf

variable "name" {
  type        = string
  description = "Base name used to derive the router, attachment, interface, and BGP peer names."

  validation {
    condition     = can(regex("^[a-z]([-a-z0-9]{0,40}[a-z0-9])?$", var.name))
    error_message = "name must be RFC1035: lowercase, start with a letter, end alphanumeric, <= 42 chars (leaves room for suffixes)."
  }
}

variable "project_id" {
  type        = string
  description = "ID of the GCP project that owns the Cloud Router and VLAN attachment."
}

variable "region" {
  type        = string
  description = "Region of the Cloud Router and attachment; must match the Interconnect edge location."
}

variable "network" {
  type        = string
  description = "Self-link or name of the VPC network the Cloud Router attaches to."
}

variable "description" {
  type        = string
  description = "Free-text description applied to the router and attachment."
  default     = "Managed by Terraform (kloudvin terraform-module-gcp-interconnect)."
}

variable "router_name" {
  type        = string
  description = "Explicit Cloud Router name. If null, derived as \"<name>-router\"."
  default     = null
}

variable "attachment_name" {
  type        = string
  description = "Explicit VLAN attachment name. If null, derived as \"<name>-vlan\"."
  default     = null
}

variable "attachment_type" {
  type        = string
  description = "Attachment type: DEDICATED (you own the physical circuit) or PARTNER (broker-provisioned)."

  validation {
    condition     = contains(["DEDICATED", "PARTNER"], var.attachment_type)
    error_message = "attachment_type must be DEDICATED or PARTNER."
  }
}

variable "interconnect" {
  type        = string
  description = "Self-link of the pre-provisioned physical google_compute_interconnect. Required for DEDICATED, must be null for PARTNER."
  default     = null
}

variable "vlan_tag8021q" {
  type        = number
  description = "IEEE 802.1Q VLAN tag (2-4094) for DEDICATED attachments. Managed upstream for PARTNER (leave null)."
  default     = null

  validation {
    condition     = var.vlan_tag8021q == null || (var.vlan_tag8021q >= 2 && var.vlan_tag8021q <= 4094)
    error_message = "vlan_tag8021q must be null or between 2 and 4094."
  }
}

variable "edge_availability_domain" {
  type        = string
  description = "Edge availability domain for PARTNER attachments (AVAILABILITY_DOMAIN_1/2/ANY). Ignored for DEDICATED."
  default     = "AVAILABILITY_DOMAIN_1"

  validation {
    condition = contains(
      ["AVAILABILITY_DOMAIN_ANY", "AVAILABILITY_DOMAIN_1", "AVAILABILITY_DOMAIN_2"],
      var.edge_availability_domain
    )
    error_message = "edge_availability_domain must be AVAILABILITY_DOMAIN_ANY, _1, or _2."
  }
}

variable "bandwidth" {
  type        = string
  description = "Provisioned bandwidth for DEDICATED attachments (e.g. BPS_10G). Output-only for PARTNER."
  default     = "BPS_10G"

  validation {
    condition = contains([
      "BPS_50M", "BPS_100M", "BPS_200M", "BPS_300M", "BPS_400M", "BPS_500M",
      "BPS_1G", "BPS_2G", "BPS_5G", "BPS_10G", "BPS_20G", "BPS_50G",
      "BPS_100G", "BPS_400G",
    ], var.bandwidth)
    error_message = "bandwidth must be a valid BPS_* enum value."
  }
}

variable "mtu" {
  type        = number
  description = "MTU in bytes for the attachment. Valid values are 1440, 1460, 1500, and 8896."
  default     = 1500

  validation {
    condition     = contains([1440, 1460, 1500, 8896], var.mtu)
    error_message = "mtu must be one of 1440, 1460, 1500, or 8896."
  }
}

variable "admin_enabled" {
  type        = bool
  description = "Whether the VLAN attachment is enabled. For PARTNER, true pre-activates the attachment."
  default     = true
}

variable "encryption" {
  type        = string
  description = "Encryption option: NONE (default) or IPSEC for HA VPN over Cloud Interconnect."
  default     = "NONE"

  validation {
    condition     = contains(["NONE", "IPSEC"], var.encryption)
    error_message = "encryption must be NONE or IPSEC."
  }
}

variable "ipsec_internal_addresses" {
  type        = list(string)
  description = "Self-links of reserved internal addresses used when encryption = IPSEC."
  default     = []
}

variable "stack_type" {
  type        = string
  description = "Stack type for the attachment: IPV4_ONLY or IPV4_IPV6."
  default     = "IPV4_ONLY"

  validation {
    condition     = contains(["IPV4_ONLY", "IPV4_IPV6"], var.stack_type)
    error_message = "stack_type must be IPV4_ONLY or IPV4_IPV6."
  }
}

variable "google_asn" {
  type        = number
  description = "BGP ASN for the Google-side Cloud Router (private range, e.g. 16550 for PARTNER, or your own 64512-65534)."
  default     = 16550
}

variable "peer_asn" {
  type        = number
  description = "BGP ASN of the on-prem / peer router."
}

variable "advertised_route_priority" {
  type        = number
  description = "Priority of routes advertised to the peer. Lower wins; use to steer traffic across redundant attachments."
  default     = 100
}

variable "advertise_mode" {
  type        = string
  description = "Cloud Router advertisement mode: DEFAULT (all subnets) or CUSTOM (explicit groups/ranges)."
  default     = "DEFAULT"

  validation {
    condition     = contains(["DEFAULT", "CUSTOM"], var.advertise_mode)
    error_message = "advertise_mode must be DEFAULT or CUSTOM."
  }
}

variable "advertised_groups" {
  type        = list(string)
  description = "Prefix groups to advertise in CUSTOM mode (e.g. [\"ALL_SUBNETS\"])."
  default     = ["ALL_SUBNETS"]
}

variable "advertised_ip_ranges" {
  type = list(object({
    range       = string
    description = optional(string)
  }))
  description = "Explicit IP ranges to advertise in CUSTOM mode, in addition to advertised_groups."
  default     = []
}

variable "interface_ip_range" {
  type        = string
  description = "Optional link-local /29 to pin the Google-side router interface IP. Null = auto-allocate."
  default     = null
}

variable "peer_interface_ip_address" {
  type        = string
  description = "Google-side interface IP (ip_address on the peer). Required if peer_ip_address is set."
  default     = null
}

variable "peer_ip_address" {
  type        = string
  description = "On-prem BGP interface IP. Required when peer_interface_ip_address is set; null for auto-allocated PARTNER."
  default     = null
}

variable "peer_enable" {
  type        = bool
  description = "Whether the BGP peer session is enabled."
  default     = true
}

variable "enable_bfd" {
  type        = bool
  description = "Enable Bidirectional Forwarding Detection for faster failure detection on the BGP session."
  default     = true
}

variable "bfd_session_initialization_mode" {
  type        = string
  description = "BFD session mode: ACTIVE, PASSIVE, or DISABLED."
  default     = "ACTIVE"

  validation {
    condition     = contains(["ACTIVE", "PASSIVE", "DISABLED"], var.bfd_session_initialization_mode)
    error_message = "bfd_session_initialization_mode must be ACTIVE, PASSIVE, or DISABLED."
  }
}

variable "bfd_min_receive_interval" {
  type        = number
  description = "Minimum BFD receive interval in milliseconds."
  default     = 1000
}

variable "bfd_min_transmit_interval" {
  type        = number
  description = "Minimum BFD transmit interval in milliseconds."
  default     = 1000
}

variable "bfd_multiplier" {
  type        = number
  description = "Number of consecutive missed BFD packets before the session is declared down."
  default     = 5
}

outputs.tf

output "router_id" {
  description = "Fully-qualified ID of the Cloud Router."
  value       = google_compute_router.this.id
}

output "router_name" {
  description = "Name of the Cloud Router."
  value       = google_compute_router.this.name
}

output "attachment_id" {
  description = "ID of the VLAN (interconnect) attachment."
  value       = google_compute_interconnect_attachment.this.id
}

output "attachment_self_link" {
  description = "Self-link of the VLAN attachment, for use by router interfaces and monitoring."
  value       = google_compute_interconnect_attachment.this.self_link
}

output "pairing_key" {
  description = "PARTNER-only opaque pairing key to hand to your service provider to provision the circuit. Null for DEDICATED."
  value       = google_compute_interconnect_attachment.this.pairing_key
}

output "cloud_router_ip_address" {
  description = "Google-side IPv4 address + prefix configured on the Cloud Router interface for this attachment."
  value       = google_compute_interconnect_attachment.this.cloud_router_ip_address
}

output "customer_router_ip_address" {
  description = "Customer-side IPv4 address + prefix to configure on your on-prem router subinterface."
  value       = google_compute_interconnect_attachment.this.customer_router_ip_address
}

output "interface_name" {
  description = "Name of the Cloud Router interface bound to the attachment."
  value       = google_compute_router_interface.this.name
}

output "peer_name" {
  description = "Name of the BGP peer."
  value       = google_compute_router_peer.this.name
}

output "state" {
  description = "Current operational state of the VLAN attachment."
  value       = google_compute_interconnect_attachment.this.state
}

How to use it

# DEDICATED: you own the physical circuit. Reference its self-link and the
# VLAN tag Google assigned, then bring up a 10G attachment with BGP.
module "interconnect_dedicated" {
  source = "git::https://dev.azure.com/teknohut/kloudvin/_git/terraform-modules//terraform-module-gcp-interconnect?ref=v1.0.0"

  name       = "prod-dc1"
  project_id = "kv-prod-host-9f3a"
  region     = "us-central1"
  network    = module.vpc_network.network_self_link

  attachment_type = "DEDICATED"
  interconnect    = "projects/kv-prod-host-9f3a/global/interconnects/colo-equinix-ch1"
  vlan_tag8021q   = 1010
  bandwidth       = "BPS_10G"
  mtu             = 1500

  google_asn                = 65001
  peer_asn                  = 64512
  advertised_route_priority = 100

  # Pin the BGP /30 from your IPAM-reserved link-local range.
  interface_ip_range        = "169.254.10.1/29"
  peer_interface_ip_address = "169.254.10.1"
  peer_ip_address           = "169.254.10.2"

  advertise_mode = "CUSTOM"
  advertised_ip_ranges = [
    { range = "10.0.0.0/16", description = "prod-supernet" },
  ]
}

# PARTNER: the carrier owns the circuit. You pick an edge domain, apply, then
# hand the pairing_key output to your provider to complete provisioning.
module "interconnect_partner" {
  source = "git::https://dev.azure.com/teknohut/kloudvin/_git/terraform-modules//terraform-module-gcp-interconnect?ref=v1.0.0"

  name       = "prod-partner-a"
  project_id = "kv-prod-host-9f3a"
  region     = "europe-west2"
  network    = module.vpc_network.network_self_link

  attachment_type          = "PARTNER"
  edge_availability_domain = "AVAILABILITY_DOMAIN_1"

  google_asn = 16550 # required ASN for PARTNER attachments
  peer_asn   = 64513
}

# Downstream: surface the pairing key for the operations runbook / ticket.
resource "google_secret_manager_secret_version" "pairing_key" {
  secret      = google_secret_manager_secret.partner_pairing.id
  secret_data = module.interconnect_partner.pairing_key
}

Build a pair of attachments — one per edge availability domain (PARTNER) or one per circuit (DEDICATED) — sharing route priorities so a single edge failure does not sever hybrid connectivity.

With Terragrunt

Terragrunt keeps this module DRY across environments — define the backend and provider once in a root config, then a thin terragrunt.hcl per environment supplies only the inputs that differ.

1. Root configlive/terragrunt.hcl (inherited by every module):

remote_state {
  backend = "gcs"
  generate = { path = "backend.tf", if_exists = "overwrite" }
  config = {
    # ...gcs state bucket/container + key per path...
  }
}

2. Module configlive/prod/interconnect/terragrunt.hcl:

include "root" {
  path = find_in_parent_folders()
}

terraform {
  source = "git::https://dev.azure.com/teknohut/kloudvin/_git/terraform-modules//terraform-module-gcp-interconnect?ref=v1.0.0"
}

inputs = {
  name = "..."
  project_id = "..."
  region = "..."
  network = "..."
  attachment_type = "..."
  peer_asn = 0
}

3. Deploy one environment, or roll out all modules together:

cd live/prod/interconnect && terragrunt apply        # this module
terragrunt run-all apply                      # every module under live/prod

Why Terragrunt here: the backend and provider live in one place instead of being copy-pasted into every module; inputs is overridden per environment (dev / stage / prod) without forking the module; and run-all orchestrates dependencies across modules. Reach for it once you have more than one environment or more than a handful of modules — for a single stack, the plain Quickstart above is enough.

Inputs

Name Type Default Required Description
name string Yes Base name for the router, attachment, interface, and peer.
project_id string Yes Project that owns the Cloud Router and attachment.
region string Yes Region of the router/attachment; matches the Interconnect edge.
network string Yes Self-link or name of the VPC the router attaches to.
attachment_type string Yes DEDICATED or PARTNER.
peer_asn number Yes BGP ASN of the on-prem / peer router.
description string "Managed by Terraform…" No Description on the router and attachment.
router_name string null No Explicit router name; derived as <name>-router if null.
attachment_name string null No Explicit attachment name; derived as <name>-vlan if null.
interconnect string null No Physical Interconnect self-link (required for DEDICATED).
vlan_tag8021q number null No 802.1Q VLAN tag (2–4094) for DEDICATED.
edge_availability_domain string "AVAILABILITY_DOMAIN_1" No Edge domain for PARTNER attachments.
bandwidth string "BPS_10G" No Provisioned bandwidth for DEDICATED.
mtu number 1500 No Attachment MTU (1440/1460/1500/8896).
admin_enabled bool true No Whether the attachment is enabled / pre-activated.
encryption string "NONE" No NONE or IPSEC (HA VPN over Interconnect).
ipsec_internal_addresses list(string) [] No Reserved internal address self-links for IPSEC.
stack_type string "IPV4_ONLY" No IPV4_ONLY or IPV4_IPV6.
google_asn number 16550 No Cloud Router ASN (16550 required for PARTNER).
advertised_route_priority number 100 No Inbound route priority; lower wins.
advertise_mode string "DEFAULT" No DEFAULT or CUSTOM advertisement mode.
advertised_groups list(string) ["ALL_SUBNETS"] No Prefix groups for CUSTOM mode.
advertised_ip_ranges list(object) [] No Explicit ranges for CUSTOM mode.
interface_ip_range string null No Pinned link-local /29 for the router interface.
peer_interface_ip_address string null No Google-side interface IP.
peer_ip_address string null No On-prem BGP interface IP.
peer_enable bool true No Enable the BGP peer session.
enable_bfd bool true No Enable BFD on the BGP session.
bfd_session_initialization_mode string "ACTIVE" No BFD mode: ACTIVE/PASSIVE/DISABLED.
bfd_min_receive_interval number 1000 No BFD min receive interval (ms).
bfd_min_transmit_interval number 1000 No BFD min transmit interval (ms).
bfd_multiplier number 5 No Missed-packet multiplier before session down.

Outputs

Name Description
router_id Fully-qualified Cloud Router ID.
router_name Cloud Router name.
attachment_id VLAN attachment ID.
attachment_self_link Self-link of the VLAN attachment.
pairing_key PARTNER pairing key to hand to your provider (null for DEDICATED).
cloud_router_ip_address Google-side BGP IPv4 + prefix.
customer_router_ip_address Customer-side BGP IPv4 + prefix.
interface_name Router interface name.
peer_name BGP peer name.
state Operational state of the attachment.

Enterprise scenario

A logistics platform runs a Shared VPC host project in kv-prod-host-9f3a and needs resilient hybrid connectivity from two data centres into us-central1 and europe-west2. The network team had already ordered two Dedicated 10G circuits months earlier; the physical google_compute_interconnect objects were lit by the colo provider and live outside Terraform. With this module, each region’s connectivity becomes two module calls — one per circuit, each with a distinct vlan_tag8021q and a shared Cloud Router ASN of 65001 — and the advertised_route_priority is staggered (100 primary, 200 backup) so traffic prefers the primary circuit but fails over automatically. A separate Partner attachment in europe-west2 provides burst capacity; its pairing_key output is pushed to Secret Manager and the carrier completes provisioning from there. The entire hybrid edge — four attachments, two routers, eight BGP sessions with BFD — is one reviewed PR, and the customer_router_ip_address outputs feed straight into the on-prem router automation.

Best practices

TerraformGCPCloud InterconnectModuleIaC
Need this built for real?

Vinod is a Senior Cloud Architect (22+ yrs) — available for Azure / AWS / GCP architecture, landing zones, and migrations.

Work with me

Comments

Keep Reading