@@ -38,6 +38,7 @@ import (
3838 "github.com/apernet/hysteria/app/v2/internal/url"
3939 "github.com/apernet/hysteria/app/v2/internal/utils"
4040 "github.com/apernet/hysteria/core/v2/client"
41+ coreErrs "github.com/apernet/hysteria/core/v2/errors"
4142 "github.com/apernet/hysteria/extras/v2/correctnet"
4243 "github.com/apernet/hysteria/extras/v2/obfs"
4344 "github.com/apernet/hysteria/extras/v2/realm"
@@ -92,6 +93,28 @@ type clientConfig struct {
9293 UDPTProxy * udpTProxyConfig `mapstructure:"udpTProxy"`
9394 TCPRedirect * tcpRedirectConfig `mapstructure:"tcpRedirect"`
9495 TUN * tunConfig `mapstructure:"tun"`
96+ PPP * pppConfig `mapstructure:"ppp"`
97+ }
98+
99+ type pppSSTPConfig struct {
100+ BinaryPath string `mapstructure:"binaryPath"`
101+ Listen string `mapstructure:"listen"`
102+ CertDir string `mapstructure:"certDir"`
103+ Endpoint string `mapstructure:"endpoint"`
104+ User string `mapstructure:"user"`
105+ Password string `mapstructure:"password"`
106+ MSSClamp * int `mapstructure:"mssClamp"` // nil=auto, 0=off, >0=forced
107+ ServerRoute * bool `mapstructure:"serverRoute"`
108+ LogLevel string `mapstructure:"logLevel"`
109+ }
110+
111+ type pppConfig struct {
112+ Mode string `mapstructure:"mode"`
113+ MTU uint32 `mapstructure:"mtu"`
114+ PPPDPath string `mapstructure:"pppdPath"`
115+ PPPDArgs []string `mapstructure:"pppdArgs"`
116+ DataStreams int `mapstructure:"dataStreams"`
117+ SSTP * pppSSTPConfig `mapstructure:"sstp"`
95118}
96119
97120type mimicConfig struct {
@@ -644,6 +667,9 @@ func (c *clientConfig) Config() (*client.Config, error) {
644667 return nil , err
645668 }
646669 }
670+ if c .PPP != nil && c .PPP .DataStreams == 0 {
671+ hyConfig .PPPMode = true
672+ }
647673 return hyConfig , nil
648674}
649675
@@ -836,12 +862,46 @@ func runClientCmd(cmd *cobra.Command, args []string) {
836862 runClient (defaultViper )
837863}
838864
865+ // authRetryDelay is how long to wait before asking a second time about a refused
866+ // password. Short enough to stay inside the restart floor that is being spent
867+ // anyway, long enough to outlast the momentary gap it exists to tolerate.
868+ const authRetryDelay = 2 * time .Second
869+
870+ // connectClient builds the client, and when the server refuses the password asks
871+ // once more before believing it.
872+ //
873+ // AuthError carries no more meaning than "the server answered with something
874+ // other than 233". A server whose auth backend is momentarily unavailable serves
875+ // the request through its masquerade handler instead -- a 404 -- which is the
876+ // same answer a genuinely wrong password gets, and the client cannot tell them
877+ // apart. With lazy off this handshake is a single un-retried attempt, and the
878+ // caller turns an AuthError into a status file that makes the netifd handler
879+ // call proto_block_restart. One unlucky moment would then hold the WAN down
880+ // until somebody logged in and ran ifup, where before it recovered by itself.
881+ //
882+ // Asking twice separates the two: a wrong password is refused both times, a gap
883+ // almost never lasts across the delay. Only an auth failure is retried -- every
884+ // other error is already treated as retryable and is left to netifd.
885+ func connectClient (config clientConfig , connected func (client.Client , * client.HandshakeInfo , int )) (client.Client , error ) {
886+ c , err := client .NewReconnectableClient (config .Config , connected , config .Lazy )
887+ var authErr coreErrs.AuthError
888+ if err == nil || ! errors .As (err , & authErr ) {
889+ return c , err
890+ }
891+ logger .Warn ("server refused the password, confirming before reporting it as final" ,
892+ zap .Int ("status" , authErr .StatusCode ))
893+ time .Sleep (authRetryDelay )
894+ return client .NewReconnectableClient (config .Config , connected , config .Lazy )
895+ }
896+
839897func runClient (v * viper.Viper ) {
840898 if err := v .ReadInConfig (); err != nil {
899+ holdPPPRestart ()
841900 logger .Fatal ("failed to read client config" , zap .Error (err ))
842901 }
843902 var config clientConfig
844903 if err := v .Unmarshal (& config ); err != nil {
904+ holdPPPRestart ()
845905 logger .Fatal ("failed to parse client config" , zap .Error (err ))
846906 }
847907
@@ -851,20 +911,32 @@ func runClient(v *viper.Viper) {
851911 mimicInst := config .startMimic ()
852912 defer mimicInst .Close ()
853913
854- c , err := client .NewReconnectableClient (
855- config .Config ,
856- func (c client.Client , info * client.HandshakeInfo , count int ) {
857- connectLog (info , count )
858- // On the client side, we start checking for updates after we successfully connect
859- // to the server, which, depending on whether lazy mode is enabled, may or may not
860- // be immediately after the client starts. We don't want the update check request
861- // to interfere with the lazy mode option.
862- if count == 1 && ! disableUpdateCheck {
863- go runCheckUpdateClient (c )
864- }
865- }, config .Lazy ,
866- )
914+ connected := func (c client.Client , info * client.HandshakeInfo , count int ) {
915+ connectLog (info , count )
916+ // On the client side, we start checking for updates after we successfully connect
917+ // to the server, which, depending on whether lazy mode is enabled, may or may not
918+ // be immediately after the client starts. We don't want the update check request
919+ // to interfere with the lazy mode option.
920+ if count == 1 && ! disableUpdateCheck {
921+ go runCheckUpdateClient (c )
922+ }
923+ }
924+
925+ c , err := connectClient (config , connected )
867926 if err != nil {
927+ // Two unrelated things share this line. The status write is where a
928+ // refused Hysteria2 password is reported from: with lazy off the
929+ // handshake has already happened here, so it never reaches the runner
930+ // below. The hold is for the opposite case -- a dropped upstream fails
931+ // this dial in microseconds, and without it that becomes a restart loop.
932+ writePPPStatus (err )
933+ // And the state file, which reports every failure rather than only the
934+ // permanent ones. A handshake refused here never reaches PPP mode, so
935+ // without this the link's last published state is whatever it was before
936+ // the interface was rebuilt -- which for a link that was working a moment
937+ // ago is "connected".
938+ writePPPStateDown (err )
939+ holdPPPRestart ()
868940 logger .Fatal ("failed to initialize client" , zap .Error (err ))
869941 }
870942 defer c .Close ()
@@ -919,6 +991,11 @@ func runClient(v *viper.Viper) {
919991 return clientTUN (* config .TUN , c )
920992 })
921993 }
994+ if config .PPP != nil {
995+ runner .Add ("PPP" , func () error {
996+ return clientPPP (* config .PPP , c , strings .EqualFold (config .Obfs .Type , "salamander" ))
997+ })
998+ }
922999
9231000 signalChan := make (chan os.Signal , 1 )
9241001 signal .Notify (signalChan , os .Interrupt , syscall .SIGTERM )
@@ -936,7 +1013,31 @@ func runClient(v *viper.Viper) {
9361013 if r .OK {
9371014 logger .Info (r .Msg )
9381015 } else {
1016+ // A connect that fails only reaches here in lazy mode, where the
1017+ // handshake is deferred until PPP mode asks for the conn. With lazy
1018+ // off -- which is what the OpenWrt handler generates, since it never
1019+ // writes the key -- the handshake has already happened above, and a
1020+ // refused password fails there instead. Both sites write the status.
1021+ writePPPStatus (r .Err )
1022+ writePPPStateDown (r .Err )
9391023 _ = c .Close () // Close the client here as Fatal will exit the program without running defer
1024+ // Hand SIGTERM back to the runtime before holding. The deferred Stop
1025+ // never runs -- Fatal exits -- so without this an ifdown arriving
1026+ // during the hold would sit unread in signalChan and be ignored for
1027+ // the length of it, keeping netifd waiting on a process that has
1028+ // already decided to die.
1029+ signal .Stop (signalChan )
1030+ // Stop only restores the default disposition for signals still to
1031+ // come. One that arrived while this select was being decided is
1032+ // already buffered, and the select is free to have picked the runner
1033+ // case instead -- so it has to be looked for, not waited for. Finding
1034+ // one means an ifdown is in progress and nothing is going to restart
1035+ // us, which is exactly when holding is pure delay.
1036+ select {
1037+ case <- signalChan :
1038+ default :
1039+ holdPPPRestart ()
1040+ }
9401041 if r .Err != nil {
9411042 logger .Fatal (r .Msg , zap .Error (r .Err ))
9421043 } else {
0 commit comments