diff --git a/main/commands/all/api/api.go b/main/commands/all/api/api.go
index f408fd90..ea43d107 100644
--- a/main/commands/all/api/api.go
+++ b/main/commands/all/api/api.go
@@ -23,5 +23,6 @@ var CmdAPI = &base.Command{
 		cmdRemoveOutbounds,
 		cmdAddRules,
 		cmdRemoveRules,
+		cmdSourceIpBlock,
 	},
 }
diff --git a/main/commands/all/api/source_ip_block.go b/main/commands/all/api/source_ip_block.go
new file mode 100644
index 00000000..1f773187
--- /dev/null
+++ b/main/commands/all/api/source_ip_block.go
@@ -0,0 +1,132 @@
+package api
+
+import (
+	"encoding/json"
+	"fmt"
+	"strings"
+
+	routerService "github.com/xtls/xray-core/app/router/command"
+	cserial "github.com/xtls/xray-core/common/serial"
+	"github.com/xtls/xray-core/infra/conf/serial"
+	"github.com/xtls/xray-core/main/commands/base"
+)
+
+var cmdSourceIpBlock = &base.Command{
+	CustomFlags: true,
+	UsageLine:   "{{.Exec}} api sib [--server=127.0.0.1:8080] -outbound=blocked -inbound=socks 1.2.3.4",
+	Short:       "Drop connections by source ip",
+	Long: `
+Drop connections by source ip.
+Arguments:
+	-s, -server 
+		The API server address. Default 127.0.0.1:8080
+	-t, -timeout
+		Timeout seconds to call API. Default 3
+	-outbound
+		route traffic to specific outbound.
+	-inbound
+		target traffig from specific inbound.
+	-ruletag
+		set ruleTag. Default sourceIpBlock
+	-reset
+		remove ruletag and apply new source IPs. Default false
+
+	Example:
+    {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json c2.json
+`,
+	Run: executeSourceIpBlock,
+}
+
+func executeSourceIpBlock(cmd *base.Command, args []string) {
+	var (
+		inbound  string
+		outbound string
+		ruletag  string
+		reset    bool
+	)
+	setSharedFlags(cmd)
+	cmd.Flag.StringVar(&inbound, "inbound", "", "")
+	cmd.Flag.StringVar(&outbound, "outbound", "", "")
+	cmd.Flag.StringVar(&ruletag, "ruletag", "sourceIpBlock", "")
+	cmd.Flag.BoolVar(&reset, "reset", false, "")
+
+	cmd.Flag.Parse(args)
+
+	unnamedArgs := cmd.Flag.Args()
+	if len(unnamedArgs) == 0 {
+		fmt.Println("reading from stdin:")
+		unnamedArgs = []string{"stdin:"}
+	}
+	conn, ctx, close := dialAPIServer()
+	defer close()
+
+	client := routerService.NewRoutingServiceClient(conn)
+
+	jsonIps, err := json.Marshal(unnamedArgs)
+	if err != nil {
+		fmt.Println("Error marshaling JSON:", err)
+		return
+	}
+
+	jsonInbound, err := json.Marshal([]string{inbound})
+	if inbound == "" {
+		jsonInbound, err = json.Marshal([]string{})
+	}
+	if err != nil {
+		fmt.Println("Error marshaling JSON:", err)
+		return
+	}
+	stringConfig := fmt.Sprintf(`
+	{
+		"routing": {
+			"rules": [
+			  {
+				"ruleTag" : "%s",
+				"inboundTag": %s,		
+				"outboundTag": "%s",
+				"type": "field",
+				"source": %s
+			  }
+			]
+		  }
+	  }
+	  
+	`, ruletag, string(jsonInbound), outbound, string(jsonIps))
+
+	conf, err := serial.DecodeJSONConfig(strings.NewReader(stringConfig))
+	if err != nil {
+		base.Fatalf("failed to decode : %s", err)
+	}
+	rc := *conf.RouterConfig
+
+	config, err := rc.Build()
+	if err != nil {
+		base.Fatalf("failed to build conf: %s", err)
+	}
+	tmsg := cserial.ToTypedMessage(config)
+	if tmsg == nil {
+		base.Fatalf("failed to format config to TypedMessage.")
+	}
+
+	if reset {
+		rr := &routerService.RemoveRuleRequest{
+			RuleTag: ruletag,
+		}
+		resp, err := client.RemoveRule(ctx, rr)
+		if err != nil {
+			base.Fatalf("failed to perform RemoveRule: %s", err)
+		}
+		showJSONResponse(resp)
+
+	}
+	ra := &routerService.AddRuleRequest{
+		Config:       tmsg,
+		ShouldAppend: true,
+	}
+	resp, err := client.AddRule(ctx, ra)
+	if err != nil {
+		base.Fatalf("failed to perform AddRule: %s", err)
+	}
+	showJSONResponse(resp)
+
+}