CVE-2023-33965: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection') (CWE-78)
value: Brook is a cross-platform programmable network tool. The `tproxy` server is vulnerable to a drive-by command injection. An attacker may fool a victim into visiting a malicious web page which will trigger requests to the local `tproxy` service leading to remote code execution. A patch is available in version 20230606.
CWE description: The product constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.
CVSS v3 base score: 8.8
Published: 2023-06-01T15:15Z
Repository: https://github.com/txthinking/brook
Language: Go
Files changed: cli/brook/main.go, cli/brook/static/index.html, quicserver.go, streamclient.go
Commit date: 2023-02-14 12:32:27 +0800
Commit message: tproxy web auth

Diff: diff --git a/cli/brook/main.go b/cli/brook/main.go
index f69b992..b3e7a61 100644
--- a/cli/brook/main.go
+++ b/cli/brook/main.go
@@ -973,11 +973,11 @@ func main() {
 				}
 				h, p, err := net.SplitHostPort(c.String("domainaddress"))
 				if err != nil {
 					return err
 				}
-				s, err := brook.NewQUICServer(":"+p, c.String("password"), h, c.Int("tcpTimeout"), c.Int("udpTimeout"), c.String("blockDomainList"), c.String("blockCIDR4List"), c.String("blockCIDR6List"), c.Int64("updateListInterval"), c.StringSlice("blockGeoIP"), c.Bool("withoutBrookProtocol"))
+				s, err := brook.NewQUICServer(":"+p, c.String("password"), h, c.Int("tcpTimeout"), c.Int("udpTimeout"), c.Bool("withoutBrookProtocol"))
 				if err != nil {
 					return err
 				}
 				if c.String("cert") != "" {
 					b, err := ioutil.ReadFile(c.String("cert"))
@@ -1435,11 +1435,11 @@ func main() {
 					Name:  "bypassGeoIP",
 					Usage: "Bypass IP by Geo country code, such as US",
 				},
 				&cli.BoolFlag{
 					Name:  "enableIPv6",
-					Usage: "Your local and server must support IPv6 both",
+					Usage: "deprecated",
 				},
 				&cli.BoolFlag{
 					Name:  "doNotRunScripts",
 					Usage: "This will not change iptables and others if you want to do by yourself",
 				},
@@ -1512,20 +1512,67 @@ func main() {
 					}
 					var cmd *exec.Cmd
 					lock := &sync.Mutex{}
 					m := http.NewServeMux()
 					m.Handle("/", http.FileServer(http.FS(web)))
+					m.HandleFunc("/hasp", func(w http.ResponseWriter, r *http.Request) {
+						lock.Lock()
+						defer lock.Unlock()
+						_, err := os.Stat("/tmp/.brook.web.password")
+						if os.IsNotExist(err) {
+							w.Write([]byte("no"))
+							return
+						}
+						w.Write([]byte("yes"))
+					})
+					m.HandleFunc("/setp", func(w http.ResponseWriter, r *http.Request) {
+						lock.Lock()
+						defer lock.Unlock()
+						_, err := os.Stat("/tmp/.brook.web.password")
+						if !os.IsNotExist(err) {
+							http.Error(w, "file exsits", 500)
+							return
+						}
+						err = ioutil.WriteFile("/tmp/.brook.web.password", []byte(r.FormValue("p")), 0600)
+						if err != nil {
+							http.Error(w, err.Error(), 500)
+							return
+						}
+						w.WriteHeader(200)
+					})
+					m.HandleFunc("/authp", func(w http.ResponseWriter, r *http.Request) {
+						lock.Lock()
+						defer lock.Unlock()
+						b, err := ioutil.ReadFile("/tmp/.brook.web.password")
+						if err != nil {
+							http.Error(w, err.Error(), 500)
+							return
+						}
+						if string(b) != r.FormValue("p") {
+							http.Error(w, "web ui password wrong", 500)
+							return
+						}
+						w.WriteHeader(200)
+					})
 					m.HandleFunc("/start", func(w http.ResponseWriter, r *http.Request) {
+						b, err := ioutil.ReadFile("/root/.brook.web.password")
+						if err != nil {
+							http.Error(w, err.Error(), 500)
+							return
+						}
+						if string(b) != r.FormValue("p") {
+							http.Error(w, "web ui password wrong", 500)
+							return
+						}
 						s, err := os.Executable()
 						if err != nil {
 							http.Error(w, err.Error(), 500)
 							return
 						}
 						lock.Lock()
 						defer lock.Unlock()
 						cmd = exec.Command("/bin/sh", "-c", s+" tproxy "+r.FormValue("args"))
-						log.Println(s + " tproxy " + r.FormValue("args"))
 						done := make(chan byte)
 						defer close(done)
 						errch := make(chan error)
 						go func() {
 							out, _ := cmd.CombinedOutput()
@@ -1550,10 +1597,19 @@ func main() {
 						case <-time.After(6 * time.Second):
 							w.Write([]byte("connected"))
 						}
 					})
 					m.HandleFunc("/stop", func(w http.ResponseWriter, r *http.Request) {
+						b, err := ioutil.ReadFile("/root/.brook.web.password")
+						if err != nil {
+							http.Error(w, err.Error(), 500)
+							return
+						}
+						if string(b) != r.FormValue("p") {
+							http.Error(w, "web ui password wrong", 500)
+							return
+						}
 						lock.Lock()
 						defer lock.Unlock()
 						if cmd == nil {
 							w.Write([]byte("disconnected"))
 							return
@@ -1563,10 +1619,19 @@ func main() {
 							return
 						}
 						w.Write([]byte("disconnected"))
 					})
 					m.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) {
+						b, err := ioutil.ReadFile("/tmp/.brook.web.password")
+						if err != nil {
+							http.Error(w, err.Error(), 500)
+							return
+						}
+						if string(b) != r.FormValue("p") {
+							http.Error(w, "web ui password wrong", 500)
+							return
+						}
 						lock.Lock()
 						defer lock.Unlock()
 						if cmd == nil {
 							w.Write([]byte("disconnected"))
 							return
diff --git a/cli/brook/static/index.html b/cli/brook/static/index.html
index 5adbee2..5c94409 100644
--- a/cli/brook/static/index.html
+++ b/cli/brook/static/index.html
@@ -33,28 +33,84 @@
                             dnsForDefault: localStorage.getItem("dnsForDefault") ?? "8.8.8.8:53",
                             dnsForBypass: localStorage.getItem("dnsForBypass") ?? "223.5.5.5:53",
                             bypassDomainList: localStorage.getItem("bypassDomainList") ?? "",
                             bypassCIDR4List: localStorage.getItem("bypassCIDR4List") ?? "",
                             bypassCIDR6List: localStorage.getItem("bypassCIDR6List") ?? "",
+                            bypassGeoIP: localStorage.getItem("bypassGeoIP") ?? "",
                             blockDomainList: localStorage.getItem("blockDomainList") ?? "",
-                            enableIPv6: localStorage.getItem("enableIPv6") ? true : false,
+                            disableA: localStorage.getItem("disableA") ? true : false,
+                            disableAAAA: localStorage.getItem("disableAAAA") ? true : false,
+                            hasp: false,
+                            localhasp: false,
+                            p: "",
                             status: "disconnected",
                             ing: false,
                         };
                     },
                     async created() {
                         try {
-                            var r = await fetch("/status");
+                            var r = await fetch("/hasp");
+                            if (r.status != 200) {
+                                throw await r.text();
+                            }
+                            if((await r.text()) == "yes"){
+                                this.hasp = true;
+                            }
+                            if(!this.hasp){
+                                return;
+                            }
+                            if(localStorage.getItem("p")){
+                                this.localhasp = true;
+                            }
+                            if(!this.localhasp){
+                                return;
+                            }
+                            var r = await fetch(`/status?p=${encodeURIComponent(localStorage.getItem('p'))}`);
                             if (r.status != 200) {
                                 throw await r.text();
                             }
                             this.status = await r.text();
                         } catch (e) {
                             alert(`${e}`);
                         }
                     },
                     methods: {
+                        async setp() {
+                            try {
+                                if (!this.p.trim()) {
+                                    return;
+                                }
+                                this.ing = true;
+                                var r = await fetch(`/setp?p=${encodeURIComponent(this.p.trim())}`);
+                                if (r.status != 200) {
+                                    throw await r.text();
+                                }
+                                location.reload();
+                                this.ing = false;
+                            } catch (e) {
+                                alert(`${e}`);
+                                this.ing = false;
+                            }
+                        },
+                        async authp() {
+                            try {
+                                if (!this.p.trim()) {
+                                    return;
+                                }
+                                this.ing = true;
+                                var r = await fetch(`/authp?p=${encodeURIComponent(this.p.trim())}`);
+                                if (r.status != 200) {
+                                    throw await r.text();
+                                }
+                                localStorage.setItem("p", this.p.trim());
+                                location.reload();
+                                this.ing = false;
+                            } catch (e) {
+                                alert(`${e}`);
+                                this.ing = false;
+                            }
+                        },
                         async start() {
                             try {
                                 this.ing = true;
                                 var s = "";
                                 if (this.link) {
@@ -103,23 +159,47 @@
                                     s += ` --bypassCIDR6List '${this.bypassCIDR6List}'`;
                                     localStorage.setItem("bypassCIDR6List", this.bypassCIDR6List);
                                 } else {
                                     localStorage.setItem("bypassCIDR6List", "");
                                 }
+                                if (
+                                    this.bypassGeoIP &&
+                                    this.bypassGeoIP
+                                        .split(",")
+                                        .map((v) => v.trim())
+                                        .filter((v) => v).length
+                                ) {
+                                    this.bypassGeoIP
+                                        .split(",")
+                                        .map((v) => v.trim())
+                                        .filter((v) => v)
+                                        .forEach((v) => {
+                                            s += ` --bypassGeoIP '${v}'`;
+                                        });
+                                    localStorage.setItem("bypassGeoIP", this.bypassGeoIP);
+                                } else {
+                                    localStorage.setItem("bypassGeoIP", "");
+                                }
                                 if (this.blockDomainList) {
                                     s += ` --blockDomainList '${this.blockDomainList}'`;
                                     localStorage.setItem("blockDomainList", this.blockDomainList);
                                 } else {
                                     localStorage.setItem("blockDomainList", "");
                                 }
-                                if (this.enableIPv6) {
-                                    s += ` --enableIPv6`;
-                                    localStorage.setItem("enableIPv6", "true");
+                                if (this.disableA) {
+                                    s += ` --disableA`;
+                                    localStorage.setItem("disableA", "true");
                                 } else {
-                                    localStorage.setItem("enableIPv6", "");
+                                    localStorage.setItem("disableA", "");
                                 }
-                                var r = await fetch(`/start?args=${encodeURIComponent(s)}`);
+                                if (this.disableAAAA) {
+                                    s += ` --disableAAAA`;
+                                    localStorage.setItem("disableAAAA", "true");
+                                } else {
+                                    localStorage.setItem("disableAAAA", "");
+                                }
+                                var r = await fetch(`/start?args=${encodeURIComponent(s)}&p=${encodeURIComponent(localStorage.getItem('p'))}`);
                                 if (r.status != 200) {
                                     throw await r.text();
                                 }
                                 this.status = await r.text();
                                 this.ing = false;
@@ -129,11 +209,11 @@
                             }
                         },
                         async stop() {
                             try {
                                 this.ing = true;
-                                var r = await fetch(`/stop`);
+                                var r = await fetch(`/stop?p=${encodeURIComponent(localStorage.getItem('p'))}`);
                                 if (r.status != 200) {
                                     throw await r.text();
                                 }
                                 this.status = await r.text();
                                 this.ing = false;
@@ -151,11 +231,11 @@
     <body>
         <header>
             <h1>Brook</h1>
             <p>brook tproxy</p>
         </header>
-        <main>
+        <main v-if="hasp && localhasp">
             <p>
                 <label>--link brook link</label><br />
                 <input v-model="link" placeholder="brook://..." />
             </p>
             <p>
@@ -172,10 +252,18 @@
             </p>
             <p>
                 <label>--dnsForBypass DNS server for resolving domains in bypass list</label><br />
                 <input v-model="dnsForBypass" placeholder="223.5.5.5:53" />
             </p>
+            <p>
+                <label>--disableA Disable A query</label><br />
+                <input type="checkbox" v-model="disableA" />
+            </p>
+            <p>
+                <label>--disableAAAA Disable AAAA query</label><br />
+                <input type="checkbox" v-model="disableAAAA" />
+            </p>
             <p>
                 <label>--bypassDomainList Suffix match mode</label><br />
                 <input v-model="bypassDomainList" placeholder="/path/to/local/file/example_domain.txt" />
             </p>
             <p>
@@ -185,21 +273,37 @@
             <p>
                 <label>--bypassCIDR6List</label><br />
                 <input v-model="bypassCIDR6List" placeholder="/path/to/local/file/example_cidr6.txt" />
             </p>
             <p>
-                <label>--blockDomainList Suffix match mode</label><br />
-                <input v-model="blockDomainList" placeholder="/path/to/local/file/example_domain.txt" />
+                <label>--bypassGeoIP Bypass IP by Geo country code, such as CN</label><br />
+                <input v-model="bypassGeoIP" placeholder="ZZ,CN" />
             </p>
             <p>
-                <label>--enableIPv6 Your local and server must support IPv6</label><br />
-                <input type="checkbox" v-model="enableIPv6" />
+                <label>--blockDomainList Suffix match mode</label><br />
+                <input v-model="blockDomainList" placeholder="/path/to/local/file/example_domain.txt" />
             </p>
             <p v-if="ing"><button disabled>Waiting...</button></p>
             <p v-if="!ing && status == 'disconnected'"><button v-on:click="start">Connect</button></p>
             <p v-if="!ing && status =='connected'"><button v-on:click="stop">Disconnect</button></p>
         </main>
+        <main v-if="!hasp">
+            <p>
+                <label>Set password for web UI</label><br />
+                <input v-model="p" />
+            </p>
+            <p v-if="ing"><button disabled>Waiting...</button></p>
+            <p v-if="!ing"><button v-on:click="setp">Save</button></p>
+        </main>
+        <main v-if="hasp && !localhasp">
+            <p>
+                <label>Auth web UI</label><br />
+                <input v-model="p" />
+            </p>
+            <p v-if="ing"><button disabled>Waiting...</button></p>
+            <p v-if="!ing"><button v-on:click="authp">Auth</button></p>
+        </main>
         <footer>
             <p><a href="https://txthinking.com">txthinking.com</a> | <a href="https://github.com/txthinking">github.com/txthinking</a> | <a href="https://talks.txthinking.com">blog</a> | <a href="https://youtube.com/txthinking">youtube</a> | <a href="https://t.me/brookgroup">telegram</a> | <a href="https://t.me/txthinking_news">news</a></p>
         </footer>
     </body>
 </html>
diff --git a/quicserver.go b/quicserver.go
index 9dfbbf2..9992e10 100644
--- a/quicserver.go
+++ b/quicserver.go
@@ -43,11 +43,11 @@ type QUICServer struct {
 	RunnerGroup          *runnergroup.RunnerGroup
 	WithoutBrook         bool
 	UDPServerConnFactory UDPServerConnFactory
 }
 
-func NewQUICServer(addr, password, domain string, tcpTimeout, udpTimeout int, blockDomainList, blockCIDR4List, blockCIDR6List string, updateListInterval int64, blockGeoIP []string, withoutbrook bool) (*QUICServer, error) {
+func NewQUICServer(addr, password, domain string, tcpTimeout, udpTimeout int, withoutbrook bool) (*QUICServer, error) {
 	if err := limits.Raise(); err != nil {
 		Log(&Error{"when": "try to raise system limits", "warning": err.Error()})
 	}
 	if runtime.GOOS == "linux" {
 		c := exec.Command("sysctl", "-w", "net.core.rmem_max=2500000")
diff --git a/streamclient.go b/streamclient.go
index 76165eb..dc971fe 100644
--- a/streamclient.go
+++ b/streamclient.go
@@ -92,11 +92,11 @@ func NewStreamClient(network string, password []byte, src string, server net.Con
 	if c.network == "udp" && i%2 != 1 {
 		i += 1
 	}
 	binary.BigEndian.PutUint32(c.WB[2+16:2+16+4], uint32(i))
 	copy(c.WB[2+16+4:2+16+4+len(dst)], dst)
-	if err := c.WriteL(4 + len(dst)); err != nil {
+	if err := c.Write(4 + len(dst)); err != nil {
 		x.BP12.Put(c.cn)
 		x.BP2048.Put(c.WB)
 		return nil, err
 	}
 
@@ -148,11 +148,11 @@ func (c *StreamClient) Exchange(local net.Conn) error {
 			if c.Timeout != 0 {
 				if err := c.Server.SetDeadline(time.Now().Add(time.Duration(c.Timeout) * time.Second)); err != nil {
 					return
 				}
 			}
-			l, err := c.ReadL()
+			l, err := c.Read()
 			if err != nil {
 				return
 			}
 			if _, err := local.Write(c.RB[2+16 : 2+16+l]); err != nil {
 				return
@@ -167,18 +167,18 @@ func (c *StreamClient) Exchange(local net.Conn) error {
 		}
 		l, err := local.Read(c.WB[2+16 : len(c.WB)-16])
 		if err != nil {
 			return nil
 		}
-		if err := c.WriteL(l); err != nil {
+		if err := c.Write(l); err != nil {
 			return nil
 		}
 	}
 	return nil
 }
 
-func (c *StreamClient) WriteL(l int) error {
+func (c *StreamClient) Write(l int) error {
 	binary.BigEndian.PutUint16(c.WB[:2], uint16(l))
 	c.ca.Seal(c.WB[:0], c.cn, c.WB[:2], nil)
 	NextNonce(c.cn)
 	c.ca.Seal(c.WB[:2+16], c.cn, c.WB[2+16:2+16+l], nil)
 	if _, err := c.Server.Write(c.WB[:2+16+l+16]); err != nil {
@@ -186,11 +186,11 @@ func (c *StreamClient) WriteL(l int) error {
 	}
 	NextNonce(c.cn)
 	return nil
 }
 
-func (c *StreamClient) ReadL() (int, error) {
+func (c *StreamClient) Read() (int, error) {
 	if _, err := io.ReadFull(c.Server, c.RB[:2+16]); err != nil {
 		return 0, err
 	}
 	if _, err := c.sa.Open(c.RB[:0], c.sn, c.RB[:2+16], nil); err != nil {
 		return 0, err

Vulnerable code: 				s, err := brook.NewQUICServer(":"+p, c.String("password"), h, c.Int("tcpTimeout"), c.Int("udpTimeout"), c.String("blockDomainList"), c.String("blockCIDR4List"), c.String("blockCIDR6List"), c.Int64("updateListInterval"), c.StringSlice("blockGeoIP"), c.Bool("withoutBrookProtocol"))
					Usage: "Your local and server must support IPv6 both",
						log.Println(s + " tproxy " + r.FormValue("args"))
                            enableIPv6: localStorage.getItem("enableIPv6") ? true : false,
                            var r = await fetch("/status");
                                if (this.enableIPv6) {
                                    s += ` --enableIPv6`;
                                    localStorage.setItem("enableIPv6", "true");
                                    localStorage.setItem("enableIPv6", "");
                                var r = await fetch(`/start?args=${encodeURIComponent(s)}`);
                                var r = await fetch(`/stop`);
        <main>
                <label>--blockDomainList Suffix match mode</label><br />
                <input v-model="blockDomainList" placeholder="/path/to/local/file/example_domain.txt" />
                <label>--enableIPv6 Your local and server must support IPv6</label><br />
                <input type="checkbox" v-model="enableIPv6" />
func NewQUICServer(addr, password, domain string, tcpTimeout, udpTimeout int, blockDomainList, blockCIDR4List, blockCIDR6List string, updateListInterval int64, blockGeoIP []string, withoutbrook bool) (*QUICServer, error) {
	if err := c.WriteL(4 + len(dst)); err != nil {
			l, err := c.ReadL()
		if err := c.WriteL(l); err != nil {
func (c *StreamClient) WriteL(l int) error {
func (c *StreamClient) ReadL() (int, error) {
Fixed code: 				s, err := brook.NewQUICServer(":"+p, c.String("password"), h, c.Int("tcpTimeout"), c.Int("udpTimeout"), c.Bool("withoutBrookProtocol"))
					Usage: "deprecated",
					m.HandleFunc("/hasp", func(w http.ResponseWriter, r *http.Request) {
						lock.Lock()
						defer lock.Unlock()
						_, err := os.Stat("/tmp/.brook.web.password")
						if os.IsNotExist(err) {
							w.Write([]byte("no"))
							return
						}
						w.Write([]byte("yes"))
					})
					m.HandleFunc("/setp", func(w http.ResponseWriter, r *http.Request) {
						lock.Lock()
						defer lock.Unlock()
						_, err := os.Stat("/tmp/.brook.web.password")
						if !os.IsNotExist(err) {
							http.Error(w, "file exsits", 500)
							return
						}
						err = ioutil.WriteFile("/tmp/.brook.web.password", []byte(r.FormValue("p")), 0600)
						if err != nil {
							http.Error(w, err.Error(), 500)
							return
						}
						w.WriteHeader(200)
					})
					m.HandleFunc("/authp", func(w http.ResponseWriter, r *http.Request) {
						lock.Lock()
						defer lock.Unlock()
						b, err := ioutil.ReadFile("/tmp/.brook.web.password")
						if err != nil {
							http.Error(w, err.Error(), 500)
							return
						}
						if string(b) != r.FormValue("p") {
							http.Error(w, "web ui password wrong", 500)
							return
						}
						w.WriteHeader(200)
					})
						b, err := ioutil.ReadFile("/root/.brook.web.password")
						if err != nil {
							http.Error(w, err.Error(), 500)
							return
						}
						if string(b) != r.FormValue("p") {
							http.Error(w, "web ui password wrong", 500)
							return
						}
						b, err := ioutil.ReadFile("/root/.brook.web.password")
						if err != nil {
							http.Error(w, err.Error(), 500)
							return
						}
						if string(b) != r.FormValue("p") {
							http.Error(w, "web ui password wrong", 500)
							return
						}
						b, err := ioutil.ReadFile("/tmp/.brook.web.password")
						if err != nil {
							http.Error(w, err.Error(), 500)
							return
						}
						if string(b) != r.FormValue("p") {
							http.Error(w, "web ui password wrong", 500)
							return
						}
                            bypassGeoIP: localStorage.getItem("bypassGeoIP") ?? "",
                            disableA: localStorage.getItem("disableA") ? true : false,
                            disableAAAA: localStorage.getItem("disableAAAA") ? true : false,
                            hasp: false,
                            localhasp: false,
                            p: "",
                            var r = await fetch("/hasp");
                            if (r.status != 200) {
                                throw await r.text();
                            }
                            if((await r.text()) == "yes"){
                                this.hasp = true;
                            }
                            if(!this.hasp){
                                return;
                            }
                            if(localStorage.getItem("p")){
                                this.localhasp = true;
                            }
                            if(!this.localhasp){
                                return;
                            }
                            var r = await fetch(`/status?p=${encodeURIComponent(localStorage.getItem('p'))}`);
                        async setp() {
                            try {
                                if (!this.p.trim()) {
                                    return;
                                }
                                this.ing = true;
                                var r = await fetch(`/setp?p=${encodeURIComponent(this.p.trim())}`);
                                if (r.status != 200) {
                                    throw await r.text();
                                }
                                location.reload();
                                this.ing = false;
                            } catch (e) {
                                alert(`${e}`);
                                this.ing = false;
                            }
                        },
                        async authp() {
                            try {
                                if (!this.p.trim()) {
                                    return;
                                }
                                this.ing = true;
                                var r = await fetch(`/authp?p=${encodeURIComponent(this.p.trim())}`);
                                if (r.status != 200) {
                                    throw await r.text();
                                }
                                localStorage.setItem("p", this.p.trim());
                                location.reload();
                                this.ing = false;
                            } catch (e) {
                                alert(`${e}`);
                                this.ing = false;
                            }
                        },
                                if (
                                    this.bypassGeoIP &&
                                    this.bypassGeoIP
                                        .split(",")
                                        .map((v) => v.trim())
                                        .filter((v) => v).length
                                ) {
                                    this.bypassGeoIP
                                        .split(",")
                                        .map((v) => v.trim())
                                        .filter((v) => v)
                                        .forEach((v) => {
                                            s += ` --bypassGeoIP '${v}'`;
                                        });
                                    localStorage.setItem("bypassGeoIP", this.bypassGeoIP);
                                } else {
                                    localStorage.setItem("bypassGeoIP", "");
                                }
                                if (this.disableA) {
                                    s += ` --disableA`;
                                    localStorage.setItem("disableA", "true");
                                    localStorage.setItem("disableA", "");
                                if (this.disableAAAA) {
                                    s += ` --disableAAAA`;
                                    localStorage.setItem("disableAAAA", "true");
                                } else {
                                    localStorage.setItem("disableAAAA", "");
                                }
                                var r = await fetch(`/start?args=${encodeURIComponent(s)}&p=${encodeURIComponent(localStorage.getItem('p'))}`);
                                var r = await fetch(`/stop?p=${encodeURIComponent(localStorage.getItem('p'))}`);
        <main v-if="hasp && localhasp">
            <p>
                <label>--disableA Disable A query</label><br />
                <input type="checkbox" v-model="disableA" />
            </p>
            <p>
                <label>--disableAAAA Disable AAAA query</label><br />
                <input type="checkbox" v-model="disableAAAA" />
            </p>
                <label>--bypassGeoIP Bypass IP by Geo country code, such as CN</label><br />
                <input v-model="bypassGeoIP" placeholder="ZZ,CN" />
                <label>--blockDomainList Suffix match mode</label><br />
                <input v-model="blockDomainList" placeholder="/path/to/local/file/example_domain.txt" />
        <main v-if="!hasp">
            <p>
                <label>Set password for web UI</label><br />
                <input v-model="p" />
            </p>
            <p v-if="ing"><button disabled>Waiting...</button></p>
            <p v-if="!ing"><button v-on:click="setp">Save</button></p>
        </main>
        <main v-if="hasp && !localhasp">
            <p>
                <label>Auth web UI</label><br />
                <input v-model="p" />
            </p>
            <p v-if="ing"><button disabled>Waiting...</button></p>
            <p v-if="!ing"><button v-on:click="authp">Auth</button></p>
        </main>
func NewQUICServer(addr, password, domain string, tcpTimeout, udpTimeout int, withoutbrook bool) (*QUICServer, error) {
	if err := c.Write(4 + len(dst)); err != nil {
			l, err := c.Read()
		if err := c.Write(l); err != nil {
func (c *StreamClient) Write(l int) error {
func (c *StreamClient) Read() (int, error) {