Feat: Add ai-geoip wasm plugin to search the client's geographic information depending on the client ip address (#1172)

Co-authored-by: Kent Dong <ch3cho@qq.com>
Co-authored-by: 澄潭 <zty98751@alibaba-inc.com>
This commit is contained in:
ran xuxin
2024-09-01 09:04:55 +08:00
committed by GitHub
parent d82c872c13
commit f26cde3b3b
11 changed files with 1708577 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
# 功能说明
`geo-ip`本插件实现了通过用户ip查询出地理位置信息然后通过请求属性和新添加的请求头把地理位置信息传递给后续插件。
# 配置字段
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
| -------- | -------- | -------- | -------- | -------- |
| ip_protocol | string | 否 | ipv4 | 可选值1. ipv4只对ipv4用户请求查找地理位置信息传递给后续插件。而ipv6用户的请求会跳过该插件继续由后续插件处理。 2. ipv6(未来实现后)只对ipv6用户查找地理位置信息传递给后续插件。而ipv4用户的请求会跳过该插件继续由后续插件处理。目前是跳过插件请求由后续插件处理。
| ip_source_type | string | 否 | origin-source | 可选值1. 对端socket ip`origin-source`; 2. 通过header获取`header` |
| ip_header_name | string | 否 | x-forwarded-for | 当`ip_source_type``header`指定自定义IP来源头 |
# 配置示例
```yaml
ip_protocol: ipv4
ip_source_type: header
ip_header_name: X-Real-Ip
```
# 生成geoCidr.txt的说明
在generateCidr目录里包含的ip.merge.txt文件是github上ip2region项目的全世界的ip网段库。 ipRange2Cidr.go 是把ip网段转换成多个cidr的程序。转换出的cidr 和地理位置信息存在 /data/geoCidr.txt文件里。geo-ip插件会在Higress启动读配置阶段读取geoCidr.txt文件并且解析到radixtree数据结构的内存里以便以后查询用户ip对应的地理位置信息。转换程序运行命令如下
```bash
go run generateCidr/ipRange2Cidr.go
```
# property 的使用方式
在geo-ip插件里调用proxywasm.SetProperty() 分别把country、city、province、isp设置进请求属性里以便后续插件可以调用proxywasm.GetProperty()获取该请求的用户ip对应的地理信息。
# ip网段转换成cidr列表的单元测试
在 generateCidr 目录里的 ipRange2Cidr_test.go 是ip网段转换成cidr 列表的单元测试程序。在 generateCidr 目录里运行命令 go test 。通过的情况显示如下:
``bash
PASS
ok higress/plugins/wasm-go/extensions/geo-ip/generateCidr 0.018s
```

View File

@@ -0,0 +1 @@
1.0.0-alpha

View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,160 @@
package main
import (
"bytes"
"fmt"
"log"
"math"
"os"
"strconv"
"strings"
//"strconv"
_ "embed"
)
//go:embed ip.merge.txt
var geoipdata string
var CIDR2MASK = []uint32{
0x00000000, 0x80000000, 0xC0000000, 0xE0000000, 0xF0000000, 0xF8000000,
0xFC000000, 0xFE000000, 0xFF000000, 0xFF800000, 0xFFC00000, 0xFFE00000,
0xFFF00000, 0xFFF80000, 0xFFFC0000, 0xFFFE0000, 0xFFFF0000, 0xFFFF8000,
0xFFFFC000, 0xFFFFE000, 0xFFFFF000, 0xFFFFF800, 0xFFFFFC00, 0xFFFFFE00,
0xFFFFFF00, 0xFFFFFF80, 0xFFFFFFC0, 0xFFFFFFE0, 0xFFFFFFF0, 0xFFFFFFF8,
0xFFFFFFFC, 0xFFFFFFFE, 0xFFFFFFFF,
}
func main() {
outFile := "/data/geoCidr.txt"
f, err := os.Create(outFile)
if err != nil {
log.Println("open file failed.", outFile, err)
return
}
defer f.Close()
geoIpRows := strings.Split(geoipdata, "\n")
for _, row := range geoIpRows {
if row == "" {
log.Println("this row is empty.")
continue
}
log.Println("geoip segment: ", row)
tmpArr := strings.Split(row, "|")
if len(tmpArr) < 7 {
log.Println("geoIp row field number is less than 7 " + row)
return
}
sip := tmpArr[0]
eip := tmpArr[1]
country := tmpArr[2]
province := tmpArr[4]
city := tmpArr[5]
isp := tmpArr[6]
if country == "0" {
country = ""
}
if province == "0" {
province = ""
}
if city == "0" {
city = ""
}
if isp == "0" {
isp = ""
}
if err := parseGeoIpFile(sip, eip, country, province, city, isp, f); err != nil {
log.Printf("parse geo ip file failed, error:%v", err)
return
}
}
}
func range2cidrList(startIp string, endIp string) []string {
cidrList := []string{}
start := uint32(ipToInt(startIp))
beginStart := start
end := uint32(ipToInt(endIp))
for end >= start {
maxSize := 32
for maxSize > 0 {
mask := CIDR2MASK[maxSize-1]
maskedBase := start & mask
if maskedBase != start {
break
}
maxSize--
}
x := math.Log2(float64(end - start + 1))
maxDiff := 32 - int(math.Floor(x))
if maxSize < maxDiff {
maxSize = maxDiff
}
ipStr := intToIP(int(start))
cidr := fmt.Sprintf("%s/%d", ipStr, maxSize)
cidrList = append(cidrList, cidr)
start += uint32(math.Pow(2, float64(32-maxSize)))
//avoid dead loop for 255.255.255.255
if start < beginStart {
break
}
}
return cidrList
}
func parseGeoIpFile(startIp string, endIp string, country string, province string, city string, isp string, f *os.File) error {
cidrList := range2cidrList(startIp, endIp)
for _, cidr := range cidrList {
outRow := fmt.Sprintf("%s|%s|%s|%s|%s", cidr, country, province, city, isp)
_, err := f.WriteString(outRow + "\n")
if err != nil {
log.Println("write string failed.", outRow, err)
return err
}
}
return nil
}
func ipToInt(ipStr string) int {
ipSegs := strings.Split(ipStr, ".")
var ipInt int = 0
var pos uint = 24
for _, ipSeg := range ipSegs {
tempInt, _ := strconv.Atoi(ipSeg)
tempInt = tempInt << pos
ipInt = ipInt | tempInt
pos -= 8
}
return ipInt
}
func intToIP(ipInt int) string {
ipSegs := make([]string, 4)
var len int = len(ipSegs)
buffer := bytes.NewBufferString("")
for i := 0; i < len; i++ {
tempInt := ipInt & 0xFF
ipSegs[len-i-1] = strconv.Itoa(tempInt)
ipInt = ipInt >> 8
}
for i := 0; i < len; i++ {
buffer.WriteString(ipSegs[i])
if i < len-1 {
buffer.WriteString(".")
}
}
return buffer.String()
}

View File

@@ -0,0 +1,90 @@
package main
import (
"errors"
"testing"
//"strconv"
_ "embed"
"github.com/stretchr/testify/assert"
)
func TestRange2CidrList(t *testing.T) {
tests := []struct {
name string
startIp string
endIp string
want map[string]int
}{
{
"test start ip with 0.0.0.0",
"0.0.0.0",
"1.0.0.255",
map[string]int{
"0.0.0.0/8": 1,
"1.0.0.0/24": 1,
},
},
{
"test the same network segment",
"1.0.1.0",
"1.0.1.255",
map[string]int{"1.0.1.0/24": 1},
},
{
"test cross network segment",
"1.0.1.0",
"2.0.1.112",
map[string]int{
"1.0.1.0/24": 1,
"1.0.2.0/23": 1,
"1.0.4.0/22": 1,
"1.0.8.0/21": 1,
"1.0.16.0/20": 1,
"1.0.32.0/19": 1,
"1.0.64.0/18": 1,
"1.0.128.0/17": 1,
"1.1.0.0/16": 1,
"1.2.0.0/15": 1,
"1.4.0.0/14": 1,
"1.8.0.0/13": 1,
"1.16.0.0/12": 1,
"1.32.0.0/11": 1,
"1.64.0.0/10": 1,
"1.128.0.0/9": 1,
"2.0.0.0/24": 1,
"2.0.1.0/26": 1,
"2.0.1.64/27": 1,
"2.0.1.96/28": 1,
"2.0.1.112/32": 1,
},
},
{
"test end ip with 255.255.255.255",
"224.0.0.0",
"255.255.255.255",
map[string]int{"224.0.0.0/3": 1},
},
{
"test start ip is greater than end ip",
"1.0.0.255",
"1.0.0.0",
map[string]int{},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
actual := range2cidrList(test.startIp, test.endIp)
assert.Equal(t, len(test.want), len(actual), "")
for _, v := range actual {
if _, ok := test.want[v]; !ok {
assert.Error(t, errors.New("not match"), "")
}
}
})
}
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,24 @@
module higress/plugins/wasm-go/extensions/geo-ip
go 1.19
require (
github.com/alibaba/higress/plugins/wasm-go v1.4.2
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f
github.com/stretchr/testify v1.8.4
github.com/tidwall/gjson v1.17.3
github.com/zmap/go-iptree v0.0.0-20210731043055-d4e632617837
)
require (
github.com/asergeyev/nradix v0.0.0-20170505151046-3872ab85bb56 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 // indirect
github.com/magefile/mage v1.14.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tidwall/resp v0.1.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@@ -0,0 +1,32 @@
github.com/alibaba/higress/plugins/wasm-go v1.4.2 h1:gH7OIGXm4wtW5Vo7L2deMPqF7OVWNESDHv1CaaTGu6s=
github.com/alibaba/higress/plugins/wasm-go v1.4.2/go.mod h1:359don/ahMxpfeLMzr29Cjwcu8IywTTDUzWlBPRNLHw=
github.com/asergeyev/nradix v0.0.0-20170505151046-3872ab85bb56 h1:Wi5Tgn8K+jDcBYL+dIMS1+qXYH2r7tpRAyBgqrWfQtw=
github.com/asergeyev/nradix v0.0.0-20170505151046-3872ab85bb56/go.mod h1:8BhOLuqtSuT5NZtZMwfvEibi09RO3u79uqfHZzfDTR4=
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/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 h1:IHDghbGQ2DTIXHBHxWfqCYQW1fKjyJ/I7W1pMyUDeEA=
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f h1:ZIiIBRvIw62gA5MJhuwp1+2wWbqL9IGElQ499rUsYYg=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
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.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tidwall/gjson v1.17.3 h1:bwWLZU7icoKRG+C+0PNwIKC6FCJO/Q3p2pZvuP0jN94=
github.com/tidwall/gjson v1.17.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/resp v0.1.1 h1:Ly20wkhqKTmDUPlyM1S7pWo5kk0tDu8OoC/vFArXmwE=
github.com/tidwall/resp v0.1.1/go.mod h1:3/FrruOBAxPTPtundW0VXgmsQ4ZBA0Aw714lVYgwFa0=
github.com/zmap/go-iptree v0.0.0-20210731043055-d4e632617837 h1:DjHnADS2r2zynZ3WkCFAQ+PNYngMSNceRROi0pO6c3M=
github.com/zmap/go-iptree v0.0.0-20210731043055-d4e632617837/go.mod h1:9vp0bxqozzQwcjBwenEXfKVq8+mYbwHkQ1NF9Ap0DMw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -0,0 +1,265 @@
package main
import (
"errors"
"net"
"net/url"
"strings"
_ "embed"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
"github.com/tidwall/gjson"
"github.com/zmap/go-iptree/iptree"
)
//go:embed geoCidr.txt
var geoipdata string
var GeoIpRdxTree *iptree.IPTree
var HaveInitGeoIpDb bool = false
const (
DefaultRealIpHeader = "X-Forwarded-For"
OriginSourceType = "origin-source"
HeaderSourceType = "header"
)
// 根据ip2region 项目里的ip地理位置数据库ip.merge.txt的内网ip网段经过ip网段转换多个cidr的程序 geo-ip/generateCidr/ipRange2Cidr.go 转换成的cidr地址。
var internalIpCidr []string = []string{"0.0.0.0/8", "10.0.0.0/8", "100.64.0.0/11", "100.96.0.0/12",
"100.112.0.0/13", "100.120.0.0/15", "100.122.0.0/16", "100.123.0.0/16", "100.124.0.0/14",
"127.0.0.0/8", "169.254.0.0/16", "172.16.0.0/12", "192.0.0.0/24", "192.0.2.0/24", "192.88.99.0/24",
"192.168.0.0/16", "198.18.0.0/15", "198.51.100.0/24", "203.0.113.0/24", "224.0.0.0/3",
}
func main() {
wrapper.SetCtx(
"geo-ip",
wrapper.ParseConfigBy(parseConfig),
wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),
)
}
type GeoIpConfig struct {
IpProtocol string `json:"ip_protocol"`
IPSourceType string `json:"ip_source_type"`
IPHeaderName string `json:"ip_header_name"`
}
type GeoIpData struct {
Cidr string `json:"cidr"`
Country string `json:"country"`
Province string `json:"province"`
City string `json:"city"`
Isp string `json:"isp"`
}
func parseConfig(json gjson.Result, config *GeoIpConfig, log wrapper.Log) error {
sourceType := json.Get("ip_source_type")
if sourceType.Exists() && sourceType.String() != "" {
switch sourceType.String() {
case HeaderSourceType:
config.IPSourceType = HeaderSourceType
case OriginSourceType:
default:
config.IPSourceType = OriginSourceType
}
} else {
config.IPSourceType = OriginSourceType
}
header := json.Get("ip_header_name")
if header.Exists() && header.String() != "" {
config.IPHeaderName = header.String()
} else {
config.IPHeaderName = DefaultRealIpHeader
}
ipProtocol := json.Get("ip_protocol")
if !ipProtocol.Exists() {
config.IpProtocol = "ipv4"
} else {
switch ipProtocol.String() {
case "ipv6":
config.IpProtocol = "ipv6"
case "ipv4":
default:
config.IpProtocol = "ipv4"
}
}
if HaveInitGeoIpDb {
return nil
}
if err := ReadGeoIpDataToRdxtree(log); err != nil {
log.Errorf("read geoip data failed.%v", err)
return err
}
HaveInitGeoIpDb = true
return nil
}
func ReadGeoIpDataToRdxtree(log wrapper.Log) error {
GeoIpRdxTree = iptree.New()
//eg., cidr country province city isp
geoIpRows := strings.Split(geoipdata, "\n")
for _, row := range geoIpRows {
if row == "" {
log.Infof("parsed empty line.")
continue
}
pureRow := strings.Trim(row, " ")
tmpArr := strings.Split(pureRow, "|")
if len(tmpArr) < 5 {
return errors.New("geoIp row field number is less than 5 " + row)
}
cidr := strings.Trim(tmpArr[0], " ")
geoIpData := &GeoIpData{
Cidr: cidr,
Country: strings.Trim(tmpArr[1], " "),
Province: strings.Trim(tmpArr[2], " "),
City: strings.Trim(tmpArr[3], " "),
Isp: strings.Trim(tmpArr[4], " "),
}
if err := GeoIpRdxTree.AddByString(cidr, geoIpData); err != nil {
return errors.New("add geoipdata into radix treefailed " + err.Error())
}
log.Debugf("added geoip data into radixtree: %v", *geoIpData)
}
return nil
}
// search geodata using client ip in radixtree.
func SearchGeoIpDataInRdxtree(ip string, log wrapper.Log) (*GeoIpData, error) {
val, found, err := GeoIpRdxTree.GetByString(ip)
if err != nil {
log.Errorf("search geo ip data in raditree failed. %v %s", err, ip)
return nil, err
}
if found {
return val.(*GeoIpData), nil
}
return nil, errors.New("geo ip data not found")
}
func parseIP(source string) string {
if strings.Contains(source, ".") {
// parse ipv4
return strings.Split(source, ":")[0]
}
//parse ipv6
if strings.Contains(source, "]") {
return strings.Split(source, "]")[0][1:]
}
return source
}
func isInternalIp(ip string) (string, error) {
if ip == "" {
return "", errors.New("empty ip")
}
ipBt := net.ParseIP(ip)
if ipBt == nil {
return "", errors.New("invalid ip format")
}
ip4B := ipBt.To4()
if ip4B == nil {
return "", errors.New("not ipv4 format")
}
for _, cidr := range internalIpCidr {
_, networkIp, err := net.ParseCIDR(cidr)
if err != nil {
return "", err
}
if networkIp.Contains(ip4B) {
return cidr, nil
}
}
return "", nil
}
func onHttpRequestHeaders(ctx wrapper.HttpContext, config GeoIpConfig, log wrapper.Log) types.Action {
var (
s string
err error
)
if config.IPSourceType == HeaderSourceType {
s, err = proxywasm.GetHttpRequestHeader(config.IPHeaderName)
if err == nil {
s = strings.Split(strings.Trim(s, " "), ",")[0]
}
} else {
var bs []byte
bs, err = proxywasm.GetProperty([]string{"source", "address"})
s = string(bs)
}
if err != nil {
log.Errorf("get client ip failed. %s %v", config.IPSourceType, err)
return types.ActionContinue
}
clientIp := parseIP(s)
//ipv6 will be implemented in the future.
if config.IpProtocol == "ipv6" || strings.Contains(clientIp, ":") {
log.Warnf("ipv6 will be implemented in the future.%s %s", clientIp, config.IpProtocol)
return types.ActionContinue
}
internalCidr, err := isInternalIp(clientIp)
if err != nil {
log.Errorf("check internal ip failed. error: %v", err)
return types.ActionContinue
}
var geoIpData *GeoIpData
if internalCidr != "" {
geoIpData = &GeoIpData{
Cidr: internalCidr,
City: "内网IP",
Province: "内网IP",
Country: "内网IP",
Isp: "内网IP",
}
} else {
geoIpData, err = SearchGeoIpDataInRdxtree(clientIp, log)
if err != nil {
log.Errorf("search geo info failed.%v", err)
return types.ActionContinue
}
}
proxywasm.SetProperty([]string{"geo-city"}, []byte(geoIpData.City))
proxywasm.SetProperty([]string{"geo-province"}, []byte(geoIpData.Province))
proxywasm.SetProperty([]string{"geo-country"}, []byte(geoIpData.Country))
proxywasm.SetProperty([]string{"geo-isp"}, []byte(geoIpData.Isp))
countryEnc := url.QueryEscape(geoIpData.Country)
provinceEnc := url.QueryEscape(geoIpData.Province)
cityEnc := url.QueryEscape(geoIpData.City)
ispEnc := url.QueryEscape(geoIpData.Isp)
proxywasm.AddHttpRequestHeader("X-Higress-Geo-Country", countryEnc)
proxywasm.AddHttpRequestHeader("X-Higress-Geo-Province", provinceEnc)
proxywasm.AddHttpRequestHeader("X-Higress-Geo-City", cityEnc)
proxywasm.AddHttpRequestHeader("X-Higress-Geo-Isp", ispEnc)
return types.ActionContinue
}

View File

@@ -0,0 +1,111 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tests
import (
"net/url"
"testing"
"github.com/alibaba/higress/test/e2e/conformance/utils/http"
"github.com/alibaba/higress/test/e2e/conformance/utils/suite"
)
func init() {
Register(WasmPluginsGeoIPPlugin)
}
var WasmPluginsGeoIPPlugin = suite.ConformanceTest{
ShortName: "WasmPluginsGeoIPPlugin",
Description: "The geo-ip wasm pluin finds the client's geographic information according to the client's ip address.",
Manifests: []string{"tests/go-wasm-geo-ip.yaml"},
Features: []suite.SupportedFeature{suite.WASMGoConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/get",
UnfollowRedirect: true,
Headers: map[string]string{
"X-Forwarded-For": "70.155.208.224,10.1.1.1",
},
},
ExpectedRequest: &http.ExpectedRequest{
Request: http.Request{
Path: "/get",
Host: "foo.com",
Headers: map[string]string{
"X-Higress-Geo-Isp": url.QueryEscape("美国电话电报"),
"X-Higress-Geo-City": "",
"X-Higress-Geo-Province": url.QueryEscape("密西西比"),
"X-Higress-Geo-Country": url.QueryEscape("美国"),
},
},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
},
ExpectedResponseNoRequest: false,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/get",
UnfollowRedirect: true,
Headers: map[string]string{
"X-Forwarded-For": "2.2.128.100,10.1.1.2",
},
},
ExpectedRequest: &http.ExpectedRequest{
Request: http.Request{
Path: "/get",
Host: "foo.com",
Headers: map[string]string{
"X-Higress-Geo-Isp": url.QueryEscape("橘子电信"),
"X-Higress-Geo-City": "",
"X-Higress-Geo-Province": url.QueryEscape("Var"),
"X-Higress-Geo-Country": url.QueryEscape("法国"),
},
},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
},
ExpectedResponseNoRequest: false,
},
},
}
t.Run("WasmPlugins geo-ip", func(t *testing.T) {
for _, testcase := range testcases {
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testcase)
}
})
},
}

View File

@@ -0,0 +1,46 @@
# Copyright (c) 2022 Alibaba Group Holding Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
name: wasmplugin-geo-ip
namespace: higress-conformance-infra
spec:
ingressClassName: higress
rules:
- host: "foo.com"
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: infra-backend-v1
port:
number: 8080
---
apiVersion: extensions.higress.io/v1alpha1
kind: WasmPlugin
metadata:
name: geo-ip
namespace: higress-system
spec:
defaultConfigDisable: true
matchRules:
- config:
ip_source_type: header
ingress:
- higress-conformance-infra/wasmplugin-geo-ip
url: file:///opt/plugins/wasm-go/extensions/geo-ip/plugin.wasm