@@ -37,6 +37,7 @@ import (
3737 "github.com/apernet/hysteria/app/v2/internal/url"
3838 "github.com/apernet/hysteria/app/v2/internal/utils"
3939 "github.com/apernet/hysteria/core/v2/client"
40+ coreErrs "github.com/apernet/hysteria/core/v2/errors"
4041 "github.com/apernet/hysteria/extras/v2/correctnet"
4142 "github.com/apernet/hysteria/extras/v2/obfs"
4243 "github.com/apernet/hysteria/extras/v2/realm"
@@ -90,6 +91,28 @@ type clientConfig struct {
9091 UDPTProxy * udpTProxyConfig `mapstructure:"udpTProxy"`
9192 TCPRedirect * tcpRedirectConfig `mapstructure:"tcpRedirect"`
9293 TUN * tunConfig `mapstructure:"tun"`
94+ PPP * pppConfig `mapstructure:"ppp"`
95+ }
96+
97+ type pppSSTPConfig struct {
98+ BinaryPath string `mapstructure:"binaryPath"`
99+ Listen string `mapstructure:"listen"`
100+ CertDir string `mapstructure:"certDir"`
101+ Endpoint string `mapstructure:"endpoint"`
102+ User string `mapstructure:"user"`
103+ Password string `mapstructure:"password"`
104+ MSSClamp * int `mapstructure:"mssClamp"` // nil=auto, 0=off, >0=forced
105+ ServerRoute * bool `mapstructure:"serverRoute"`
106+ LogLevel string `mapstructure:"logLevel"`
107+ }
108+
109+ type pppConfig struct {
110+ Mode string `mapstructure:"mode"`
111+ MTU uint32 `mapstructure:"mtu"`
112+ PPPDPath string `mapstructure:"pppdPath"`
113+ PPPDArgs []string `mapstructure:"pppdArgs"`
114+ DataStreams int `mapstructure:"dataStreams"`
115+ SSTP * pppSSTPConfig `mapstructure:"sstp"`
93116}
94117
95118type clientConfigRealm struct {
@@ -618,6 +641,9 @@ func (c *clientConfig) Config() (*client.Config, error) {
618641 return nil , err
619642 }
620643 }
644+ if c .PPP != nil && c .PPP .DataStreams == 0 {
645+ hyConfig .PPPMode = true
646+ }
621647 return hyConfig , nil
622648}
623649
@@ -810,29 +836,69 @@ func runClientCmd(cmd *cobra.Command, args []string) {
810836 runClient (defaultViper )
811837}
812838
839+ // authRetryDelay is how long to wait before asking a second time about a refused
840+ // password. Short enough to stay inside the restart floor that is being spent
841+ // anyway, long enough to outlast the momentary gap it exists to tolerate.
842+ const authRetryDelay = 2 * time .Second
843+
844+ // connectClient builds the client, and when the server refuses the password asks
845+ // once more before believing it.
846+ //
847+ // AuthError carries no more meaning than "the server answered with something
848+ // other than 233". A server whose auth backend is momentarily unavailable serves
849+ // the request through its masquerade handler instead -- a 404 -- which is the
850+ // same answer a genuinely wrong password gets, and the client cannot tell them
851+ // apart. With lazy off this handshake is a single un-retried attempt, and the
852+ // caller turns an AuthError into a status file that makes the netifd handler
853+ // call proto_block_restart. One unlucky moment would then hold the WAN down
854+ // until somebody logged in and ran ifup, where before it recovered by itself.
855+ //
856+ // Asking twice separates the two: a wrong password is refused both times, a gap
857+ // almost never lasts across the delay. Only an auth failure is retried -- every
858+ // other error is already treated as retryable and is left to netifd.
859+ func connectClient (config clientConfig , connected func (client.Client , * client.HandshakeInfo , int )) (client.Client , error ) {
860+ c , err := client .NewReconnectableClient (config .Config , connected , config .Lazy )
861+ var authErr coreErrs.AuthError
862+ if err == nil || ! errors .As (err , & authErr ) {
863+ return c , err
864+ }
865+ logger .Warn ("server refused the password, confirming before reporting it as final" ,
866+ zap .Int ("status" , authErr .StatusCode ))
867+ time .Sleep (authRetryDelay )
868+ return client .NewReconnectableClient (config .Config , connected , config .Lazy )
869+ }
870+
813871func runClient (v * viper.Viper ) {
814872 if err := v .ReadInConfig (); err != nil {
873+ holdPPPRestart ()
815874 logger .Fatal ("failed to read client config" , zap .Error (err ))
816875 }
817876 var config clientConfig
818877 if err := v .Unmarshal (& config ); err != nil {
878+ holdPPPRestart ()
819879 logger .Fatal ("failed to parse client config" , zap .Error (err ))
820880 }
821881
822- c , err := client .NewReconnectableClient (
823- config .Config ,
824- func (c client.Client , info * client.HandshakeInfo , count int ) {
825- connectLog (info , count )
826- // On the client side, we start checking for updates after we successfully connect
827- // to the server, which, depending on whether lazy mode is enabled, may or may not
828- // be immediately after the client starts. We don't want the update check request
829- // to interfere with the lazy mode option.
830- if count == 1 && ! disableUpdateCheck {
831- go runCheckUpdateClient (c )
832- }
833- }, config .Lazy ,
834- )
882+ connected := func (c client.Client , info * client.HandshakeInfo , count int ) {
883+ connectLog (info , count )
884+ // On the client side, we start checking for updates after we successfully connect
885+ // to the server, which, depending on whether lazy mode is enabled, may or may not
886+ // be immediately after the client starts. We don't want the update check request
887+ // to interfere with the lazy mode option.
888+ if count == 1 && ! disableUpdateCheck {
889+ go runCheckUpdateClient (c )
890+ }
891+ }
892+
893+ c , err := connectClient (config , connected )
835894 if err != nil {
895+ // Two unrelated things share this line. The status write is where a
896+ // refused Hysteria2 password is reported from: with lazy off the
897+ // handshake has already happened here, so it never reaches the runner
898+ // below. The hold is for the opposite case -- a dropped upstream fails
899+ // this dial in microseconds, and without it that becomes a restart loop.
900+ writePPPStatus (err )
901+ holdPPPRestart ()
836902 logger .Fatal ("failed to initialize client" , zap .Error (err ))
837903 }
838904 defer c .Close ()
@@ -887,6 +953,11 @@ func runClient(v *viper.Viper) {
887953 return clientTUN (* config .TUN , c )
888954 })
889955 }
956+ if config .PPP != nil {
957+ runner .Add ("PPP" , func () error {
958+ return clientPPP (* config .PPP , c , strings .EqualFold (config .Obfs .Type , "salamander" ))
959+ })
960+ }
890961
891962 signalChan := make (chan os.Signal , 1 )
892963 signal .Notify (signalChan , os .Interrupt , syscall .SIGTERM )
@@ -904,7 +975,30 @@ func runClient(v *viper.Viper) {
904975 if r .OK {
905976 logger .Info (r .Msg )
906977 } else {
978+ // A connect that fails only reaches here in lazy mode, where the
979+ // handshake is deferred until PPP mode asks for the conn. With lazy
980+ // off -- which is what the OpenWrt handler generates, since it never
981+ // writes the key -- the handshake has already happened above, and a
982+ // refused password fails there instead. Both sites write the status.
983+ writePPPStatus (r .Err )
907984 _ = c .Close () // Close the client here as Fatal will exit the program without running defer
985+ // Hand SIGTERM back to the runtime before holding. The deferred Stop
986+ // never runs -- Fatal exits -- so without this an ifdown arriving
987+ // during the hold would sit unread in signalChan and be ignored for
988+ // the length of it, keeping netifd waiting on a process that has
989+ // already decided to die.
990+ signal .Stop (signalChan )
991+ // Stop only restores the default disposition for signals still to
992+ // come. One that arrived while this select was being decided is
993+ // already buffered, and the select is free to have picked the runner
994+ // case instead -- so it has to be looked for, not waited for. Finding
995+ // one means an ifdown is in progress and nothing is going to restart
996+ // us, which is exactly when holding is pure delay.
997+ select {
998+ case <- signalChan :
999+ default :
1000+ holdPPPRestart ()
1001+ }
9081002 if r .Err != nil {
9091003 logger .Fatal (r .Msg , zap .Error (r .Err ))
9101004 } else {
0 commit comments