diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..1527ec1 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,41 @@ +name: Go CI + +on: + push: + branches: + - main + paths: + - main.go + - go.* + - .github/workflows/** + schedule: + - cron: 10 5 * * * + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: '1.22' + + - name: Compile and run main.go + run: | + go run . + + - name: Commit generated files + run: | + git config --global user.name 'github-actions[bot]' + git config --global user.email 'github-actions[bot]@users.noreply.github.com' + git add -u + git diff-index --quiet HEAD || git commit -a -m "[auto] Update generated files" + - name: Push changes + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + branch: main \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6034d84 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 NextDNS + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b2941b9 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ + +# iCloud Private Relay Egress IP Ranges MMDB + +![iCloud Private Relay](https://support.apple.com/library/content/dam/edam/applecare/images/en_US/icloud/icloud-private-relay-how-private-relay-works-path-through-relays.png) + +This project provides an auto-generated iCloud Private Relay egress IP ranges in MMDB format built from the [official feed](https://mask-api.icloud.com/egress-ip-ranges.csv) provided by Apple. + +The source feed is checked daily for updates, and a new version is automatically generated in case of any changes. diff --git a/egress-ip-ranges.etag b/egress-ip-ranges.etag new file mode 100644 index 0000000..e69de29 diff --git a/egress-ip-ranges.mmdb b/egress-ip-ranges.mmdb new file mode 100644 index 0000000..e2b9dff Binary files /dev/null and b/egress-ip-ranges.mmdb differ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..184032b --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module github.com/nextdns/icloud-private-relay-mmdb + +go 1.22.4 + +require ( + github.com/maxmind/mmdbwriter v1.0.0 + github.com/oschwald/maxminddb-golang v1.13.1 // indirect +) + +require ( + go4.org/netipx v0.0.0-20220812043211-3cc044ffd68d // indirect + golang.org/x/sys v0.21.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..b0b2395 --- /dev/null +++ b/go.sum @@ -0,0 +1,16 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/maxmind/mmdbwriter v1.0.0 h1:bieL4P6yaYaHvbtLSwnKtEvScUKKD6jcKaLiTM3WSMw= +github.com/maxmind/mmdbwriter v1.0.0/go.mod h1:noBMCUtyN5PUQ4H8ikkOvGSHhzhLok51fON2hcrpKj8= +github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE= +github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go4.org/netipx v0.0.0-20220812043211-3cc044ffd68d h1:ggxwEf5eu0l8v+87VhX1czFh8zJul3hK16Gmruxn7hw= +go4.org/netipx v0.0.0-20220812043211-3cc044ffd68d/go.mod h1:tgPU4N2u9RByaTN3NC2p9xOzyFpte4jYwsIIRF7XlSc= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..c4a2cfc --- /dev/null +++ b/main.go @@ -0,0 +1,80 @@ +package main + +import ( + "bytes" + "encoding/csv" + "log" + "net" + "net/http" + "os" + + "github.com/maxmind/mmdbwriter" + "github.com/maxmind/mmdbwriter/mmdbtype" +) + +const egressRanges = "https://mask-api.icloud.com/egress-ip-ranges.csv" +const etagFilename = "egress-ip-ranges.etag" +const outputFilename = "egress-ip-ranges.mmdb" + +func main() { + resp, err := http.Get(egressRanges) + if err != nil { + log.Fatalf("Failed to download file: %v", err) + } + defer resp.Body.Close() + + previousETag, _ := os.ReadFile(etagFilename) + currentEtag := []byte(resp.Header.Get("ETag")) + if bytes.Equal(currentEtag, previousETag) { + log.Println("No changes in the egress IP ranges") + return + } + + reader := csv.NewReader(resp.Body) + records, err := reader.ReadAll() + if err != nil { + log.Fatalf("Failed to read CSV file: %v", err) + } + + writer, err := mmdbwriter.New(mmdbwriter.Options{ + DatabaseType: "GeoIP2-Country", + RecordSize: 24, + }) + if err != nil { + log.Fatalf("Failed to create MMDB writer: %v", err) + } + + // Insert records into the MMDB writer + for _, record := range records { + _, ipNet, err := net.ParseCIDR(record[0]) + if err != nil { + log.Fatalf("Failed to parse CIDR: %v", err) + } + + data := mmdbtype.Map{ + "country": mmdbtype.Map{ + "iso_code": mmdbtype.String(record[1]), + }, + } + + err = writer.Insert(ipNet, data) + if err != nil { + log.Fatalf("Failed to insert record into MMDB: %v", err) + } + } + + // Write the MMDB file + mmdbFile, err := os.Create(outputFilename) + if err != nil { + log.Fatalf("Failed to create MMDB file: %v", err) + } + defer mmdbFile.Close() + + if _, err = writer.WriteTo(mmdbFile); err != nil { + log.Fatalf("Failed to write MMDB file: %v", err) + } + + if err = os.WriteFile(etagFilename, currentEtag, 0644); err != nil { + log.Fatalf("Failed to write etag file: %v", err) + } +}